为什么这个C#函数使用System.Drawing溢出来绘制正弦波?

时间:2017-01-13 21:25:04

标签: c# winforms graph drawing sine

所以我制作了一个绘制正弦波的小窗口窗体程序但是当我放置某些函数如sin(x) ^ x时它会溢出。 请查看此代码并帮我解决这个问题!

    private void button1_Click(object sender, EventArgs e)
    {
        if (Completed) return;
        Completed = true;
        Graphics g = this.CreateGraphics();
        Pen pen = new Pen(Brushes.Black, 2.0F);

        float x1 = 0;
        float y1 = 0;

        float y2 = 0;

        float yEx = 300;
        float eF = 50;


        for (float x = 0; x < Width; x += 0.005F)
        {
            y2 = (float)Math.Pow(Math.Sin(x) , x);

            g.DrawLine(pen, x1 * eF, y1 * eF + yEx, x * eF, y2 * eF + yEx);
            x1 = x;
            y1 = y2;
        }

    }

当我执行时,它会运行一段时间然后显示:
what is exactly displayed

赞赏任何贡献!

3 个答案:

答案 0 :(得分:1)

好吧,事实证明,解决方案是TyCobb的回答和John Wu的回答。当您对Math.Sin等增加值使用x时,Math.Sin(x)的输出中约有一半将为负值。 Math.Pow在第一个参数中给出一个负数而第二个参数为非整数时会不喜欢它(在这种情况下它会返回NaN)。这是因为当你用一个非整数提出一个负数时,结果是一个复数。

现在Graphics.DrawLine并不关心您是否将NaN作为第二对float参数(x2, y2)之一传递,因为它只会返回而不做任何事情。但是,如果您将NaN作为第一对(x1, y1)之一传递,则会抛出OverflowException。为什么会这样做,我不确定,但我愿意猜测这是.NET团队当天的疏忽。

现在从第一次发现开始,您可以看到问题主要是x为负。对此的简单解决方案是将x替换为Math.Pow(x),但这可能会以不反映原始函数意图的方式改变输出。第二种解决方案是简单地跳过这些迭代,但这会在图中留下漏洞。

您可能已经猜到的第三种解决方案,但那就是将Math.Pow替换为Complex.Pow。这将通过返回复数来为您提供所需的支持,以获得您可能获得的功率输入。完成后,您可以选择要对结果执行的操作。 (我刚刚将结果的Real部分作为我的y并放弃了Imaginary部分。)

for (float x = 0; x < Width; x += 0.005F)
{
    y2 = (float)Complex.Pow(Math.Sin(x), x).Real;

    g.DrawLine(pen, x1 * eF, y1 * eF + yEx, x * eF, y2 * eF + yEx);
    x1 = x;
    y1 = y2;
}

结果如下:

enter image description here

答案 1 :(得分:0)

验证DrawLine的值。

您在循环中检查Width,但在致电DrawLine之前未验证计算。这是一个没有绘图的例子:

float x1 = 0;
float y1 = 0;

float y2 = 0;

float yEx = 300;
float eF = 50;

const int width = 1024;
const int height = 1024;

for (float x = 0; x < width; x += 0.005F)
{
    y2 = (float)Math.Pow(Math.Sin(x) , x);

    var a = x1 * eF;
    var b = y1 * eF + yEx; 
    var c = x * eF;
    var d = y2 * eF + yEx;

    if(a < 0 || a >= width || b < 0 || b >= height || c < 0 || c >= width || d < 0 || d > height)
        Console.WriteLine("Snap! {0} | {1} | {2} | {3}", a, b, c, d);
    x1 = x;
    y1 = y2;
}

Here's an online example

当你试图画出画布时,GDI不会帮助你,事实上它通常会繁荣。您应始终将值限制在高度和宽度范围内。

以下是该示例的一些要点:

Snap! 1101,215 | NaN | 1101,465 | NaN
Snap! 1101,465 | NaN | 1101,715 | NaN
Snap! 1101,715 | NaN | 1101,965 | NaN
Snap! 1101,965 | NaN | 1102,215 | NaN
Snap! 1102,215 | NaN | 1102,465 | NaN
Snap! 1102,465 | NaN | 1102,715 | NaN
Snap! 1102,715 | NaN | 1102,965 | NaN
Snap! 1102,965 | NaN | 1103,215 | NaN
Snap! 1103,215 | NaN | 1103,465 | NaN
Snap! 1103,465 | NaN | 1103,715 | NaN
Snap! 1103,715 | NaN | 1103,965 | NaN
Snap! 1103,965 | NaN | 1104,215 | NaN
Snap! 1104,215 | NaN | 1104,465 | NaN
Snap! 1104,465 | NaN | 1104,715 | NaN
Snap! 1104,715 | NaN | 1104,965 | NaN
Snap! 1104,965 | NaN | 1105,215 | NaN

答案 2 :(得分:0)

问题是Math.Pow有时会返回not a number or NaN的浮点数。具体做法是:

  

x&lt; 0但不是NegativeInfinity; y不是整数,NegativeInfinity或PositiveInfinity。 = NaN

由于Sin(x)将绘制一条高于和低于0的曲线,因此其某些值将为负值,因此while调用将产生NaN,当然不能在笛卡尔空间上绘制。

我建议你暂时修改你的代码,如下所示:用try块包装绘图调用。

Math.Pow

然后运行您的代码并查看该行。该行中的断点将告诉您域/范围的哪些部分存在问题。一旦您对计算问题有了全面的了解,您就可以将笛卡尔空间转换/映射到不会发生溢出的域,例如:通过在SIN函数的结果中添加常量。