2017-01-13 52 views
0

所以我做了這個繪製正弦波的小窗口窗體程序,但是當我把某些函數如sin(x)^x溢出時。 請檢查此代碼,並幫助我弄清楚這一點!爲什麼這個C#函數使用System.Drawing溢出來繪製正弦波?

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

任何貢獻表示讚賞!

+0

用於'float'的最大值是與「Int32」相同。如果結果大於2,147,483,647,則會出現溢出錯誤。您是否嘗試過使用'double'(與Int64相同的最大值 - 9,223,372,036,854,775,807)?或者檢查值以處理溢出。 – Tim

+1

你沒有驗證你的輸入到筆中。你應該確保它們在畫布的範圍之內,因爲它不喜歡畫成任何東西。 – TyCobb

+0

那麼寬度的值是多少?把它放入Visual Studio並使用1920這樣的值,然後打印'y2'會給我很多結果(其中許多是'NaN'),但我從來沒有發生過溢出。這是有道理的,真的 - sin(x)^ x'的範圍將被限制在(-1,1),所以不應該溢出。 – Abion47

回答

1

好吧,事實證明這個解決方案是TyCobb的答案和John Wu的答案的結合。當您在x等遞增值上使用Math.Sin時,Math.Sin(x)的約一半輸出將爲負值。 Math.Pow在第一個參數中給它一個負數並且將第二個參數設爲非整數(在這種情況下它將返回NaN)並不喜歡它。原因是因爲當你用一個非整數來產生負數時,結果是一個複數。

現在Graphics.DrawLine不關心你是否通過NaN作爲第二對float參數(x2, y2),因爲它只會返回而不做任何事情。但是如果你通過NaN作爲第一對(x1, y1)之一,它會拋出OverflowException。爲什麼它這樣做,我不確定,但我願意猜測這是.NET團隊當天的疏忽。

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

您可能已經猜到了第三種解決方案,但這是將Math.Pow替換爲Complex.Pow。這將爲您提供所需的支持,以便通過返回複雜的數字來實現您可能獲得的權力功能的潛在投入。一旦你完成了,你可以選擇你想要做的結果。 (I只是把結果的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

+0

很不錯。不知道.net有這個複雜的類。 –

+0

布拉沃,我的朋友。真的很有幫助。我不會猜測C#有辦法用Complex.Pow處理這個問題。謝謝! –

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 
+0

替換'g.DrawLine(Pens.Black,-1,-1,1200,800);'(在一個大小爲1024x768的'Bitmap'上)的for循環的內部運行得很好,我認爲這不會影響在這種情況下,畫布邊緣是問題 – Abion47

+0

@ Abion47我更新了我的示例和代碼演示 – TyCobb

0

的問題是,有時Math.Pow返回浮動是not a number or NaN。具體來說:

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

由於的sin(x)將繪製的曲線是上方和下方0,它的一些值將是負的,因此,Math.Pow呼叫將產生NaN時,這當然不能在笛卡爾空間被吸入。

我建議你暫時修改你的代碼,如下所示:用try塊封裝繪圖調用。

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函數的結果添加一個常量。

+0

使用'NaN'作爲Graphics.DrawX方法的輸入不起作用,但它也可以它不會拋出異常,它什麼都不做 – Abion47

+0

說到底片,當你傳遞負數時,GDI也會傾向於拋出內存不足的異常,當你認爲你合法用完了時,這些都是有趣的調試時間。記憶體;) – TyCobb