簡體   English   中英

為什么此C#函數使用System.Drawing溢出來繪制正弦波?

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

因此,我制作了一個繪制正弦波的小窗口窗體程序,但是當我放置某些函數(例如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;
        }

    }

當我執行時,它會運行一段時間,然后顯示以下內容:
確切顯示了什么

任何貢獻表示贊賞!

好吧,事實證明,解決方案是TyCobb的答案和John Wu的答案的結合。 當您對x等遞增值使用Math.Sin時, Math.Sin(x)的輸出大約一半Math.Sin(x)負數。 當您在第一個參數中給它一個負數而在第二個參數中給它一個非整數時, Math.Pow不喜歡它(在這種情況下,它將返回NaN )。 這樣做的原因是因為當您將負數加一個非整數時,結果是一個復數。

現在Graphics.DrawLine不在乎是否將NaN作為第二對float參數( x2, y2 )中的任何一個傳遞x2, y2因為它會不做任何事情而返回。 但是,如果您將NaN作為第一對( x1, y1 )中的一個傳遞,它將拋出OverflowException 我不確定為什么要這樣做,但我願意猜測這是.NET團隊過去的疏忽。

現在,從第一個發現中,您可以看到問題主要出在x為負數時。 一種簡單的解決方案是用Math.Pow(x)替換x ,但這可能會以不反映原始函數意圖的方式更改輸出。 第二種解決方案是簡單地跳過這些迭代,但這會在圖形中留下漏洞。

您可能已經猜到的第三個解決方案是,用Complex.Pow替換Math.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;
}

結果是這樣的:

在此處輸入圖片說明

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

這是一個在線示例

當您嘗試脫掉畫布時,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

問題是Math.Pow有時返回的浮點數不是數字或NaN 特別:

x <0,但不是NegativeInfinity; y不是整數,NegativeInfinity或PositiveInfinity。 = NaN

由於Sin(x)將繪制一條同時在0之上和以下的曲線,因此其某些值將為負,因此Math.Pow調用將產生NaN,這當然不能在笛卡爾空間上繪制。

我建議您按如下方式臨時修改代碼:用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

然后運行您的代碼並查看該行。 該行中的中斷將告訴您域/范圍的哪些部分有問題。 全面了解計算問題后,您可以將笛卡爾空間變換/映射到不會發生溢出的域中,例如,通過向SIN函數的結果中添加常量。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM