简体   繁体   English

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

[英]Why does this C# function to draw a sine wave using System.Drawing overflows?

So I made this little windows forms program that draws a sine wave but when I put certain functions like sin(x) ^ x it overflows. 因此,我制作了一个绘制正弦波的小窗口窗体程序,但是当我放置某些函数(例如sin(x) ^ x它会溢出)。 Please review this code and help me figure this out! 请查看此代码,并帮助我解决该问题!

    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;
        }

    }

When I execute, it runs for a bit and then displays this: 当我执行时,它会运行一段时间,然后显示以下内容:
确切显示了什么

Any contribution is appreciated! 任何贡献表示赞赏!

Alright, so it turns out the solution is a mix of TyCobb's answer and John Wu's answer. 好吧,事实证明,解决方案是TyCobb的答案和John Wu的答案的结合。 When you use Math.Sin on an increasing value like x , roughly half of the output from Math.Sin(x) is going to be negative. 当您对x等递增值使用Math.Sin时, Math.Sin(x)的输出大约一半Math.Sin(x)负数。 Math.Pow doesn't like it when you give it a negative number in the first parameter and a non-integer as the second parameter (it will return NaN in this case). 当您在第一个参数中给它一个负数而在第二个参数中给它一个非整数时, Math.Pow不喜欢它(在这种情况下,它将返回NaN )。 The reason for this is because when you raise a negative number by a non-integer, the result is a complex number. 这样做的原因是因为当您将负数加一个非整数时,结果是一个复数。

Now Graphics.DrawLine doesn't care if you pass NaN as either of the second pair of float parameters ( x2, y2 ) as it will just return without doing anything. 现在Graphics.DrawLine不在乎是否将NaN作为第二对float参数( x2, y2 )中的任何一个传递x2, y2因为它会不做任何事情而返回。 But if you pass NaN as one of the first pair ( x1, y1 ), it will throw an OverflowException . 但是,如果您将NaN作为第一对( x1, y1 )中的一个传递,它将抛出OverflowException Why it does this, I'm not sure, but I'd be willing to guess that it was an oversight on the part of the .NET team back in the day. 我不确定为什么要这样做,但我愿意猜测这是.NET团队过去的疏忽。

Now from the first discovery, you can see the problem is mainly whenever x is negative. 现在,从第一个发现中,您可以看到问题主要出在x为负数时。 The easy solution to that would be to replace x with Math.Pow(x) , but that might alter the output in a way that doesn't reflect the intentions of the original function. 一种简单的解决方案是用Math.Pow(x)替换x ,但这可能会以不反映原始函数意图的方式更改输出。 A second solution would be to simply skip those iterations, but that will leave holes in your graph. 第二种解决方案是简单地跳过这些迭代,但这会在图形中留下漏洞。

A third solution you may have guessed already, but that is to replace Math.Pow with Complex.Pow . 您可能已经猜到的第三个解决方案是,用Complex.Pow替换Math.Pow This will give you the support you need for the potential inputs you may get to your power function by returning a complex number. 通过返回一个复数,这将为您可能需要的幂输入提供支持。 Once you've done that, you can choose what you want to do with the result. 完成此操作后,您可以选择要对结果执行的操作。 (I just took the Real part of the result as my y and discarded the Imaginary portion.) (我只是将结果的“ 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;
}

The result is this: 结果是这样的:

在此处输入图片说明

Validate your values for the DrawLine . 验证DrawLine的值。

You do a check for the Width at the loop, but you do not validate your calculations before you call DrawLine . 您在循环处检查Width ,但是在调用DrawLine之前不验证计算。 Here's an example without the draw: 这是一个没有抽奖的例子:

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 will not help you out when you try to draw off the canvas, in fact it will normally go boom. 当您尝试脱掉画布时,GDI不会帮您,实际上,它通常会蓬勃发展。 You should always limit the values to be within the height and width. 您应该始终将值限制在高度和宽度之内。

Here's some highlights of the example: 这是该示例的一些要点:

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

The problem is that Math.Pow sometimes returns a float that is not a number or NaN . 问题是Math.Pow有时返回的浮点数不是数字或NaN Specifically: 特别:

x < 0 but not NegativeInfinity; x <0,但不是NegativeInfinity; y is not an integer, NegativeInfinity, or PositiveInfinity. y不是整数,NegativeInfinity或PositiveInfinity。 = NaN = NaN

Since Sin(x) will draw a curve that is both above and below 0, some of its values will be negative, thus the Math.Pow call will yield NaN, which of course cannot be drawn on Cartesian space. 由于Sin(x)将绘制一条同时在0之上和以下的曲线,因此其某些值将为负,因此Math.Pow调用将产生NaN,这当然不能在笛卡尔空间上绘制。

I suggest you temporarily modify your code as follows: Wrap the draw call with a try block. 我建议您按如下方式临时修改代码:用try块包装draw调用。

try
{
    g.DrawLine(pen, x1 * eF, y1 * eF + yEx, x * eF, y2 * eF + yEx);
}
catch {}  //Not usually a great idea to eat all exceptions, but for troubleshooting purposes this'll do

Then run your code and view the line. 然后运行您的代码并查看该行。 The breaks in the line will tell you which parts of the domain/range are problematic. 该行中的中断将告诉您域/范围的哪些部分有问题。 Once you have a holistic view of the computational issues, you may be able to transform/map your Cartesian space into a domain where the overflows don't happen, eg by adding a constant to the result of the SIN function. 全面了解计算问题后,您可以将笛卡尔空间变换/映射到不会发生溢出的域中,例如,通过向SIN函数的结果中添加常量。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM