2012-03-08 44 views
11

我正在寫一個模擬程序,以分立的步驟進行。仿真由許多節點組成,每個節點都有一個與其相關的浮點值,並在每個步驟重新計算。結果可以是正數,負數或零。如何在使用浮動時獲得一致的程序行爲?

在結果爲零或更少的情況下發生的情況。到目前爲止,這看起來很簡單 - 我可以做這樣的事情對每個節點:

if (value <= 0.0f) something_happens(); 

一個問題但是出現了,經過最近我在我重新安排的順序,節目做出了一些改變,其中某些計算已完成。在完美的世界中,這些重新排列後的值仍然會出現,但由於浮點表示的不精確性,它們會出現非常不同的結果。由於每個步驟的計算取決於前一步驟的結果,因此隨着模擬的進行,結果中的這些輕微變化可以累積成更大的變化。

以下是一個演示的現象,我描述一個簡單的例子程序:

float f1 = 0.000001f, f2 = 0.000002f; 
f1 += 0.000004f; // This part happens first here 
f1 += (f2 * 0.000003f); 
printf("%.16f\n", f1); 

f1 = 0.000001f, f2 = 0.000002f; 
f1 += (f2 * 0.000003f); 
f1 += 0.000004f; // This time this happens second 
printf("%.16f\n", f1); 

這個程序的輸出是

0.0000050000057854 
0.0000050000062402 

即使加法是可交換所以無論結果應該是相同的。注意:我完全理解爲什麼會發生這種情況 - 這不是問題。問題在於,這些變化可能意味着有時候一個在步驟N中出現負值的值會觸發something_happens(),現在可能會提前一兩步出現負值,這可能會導致非常不同的整體模擬結果,因爲something_happens()有很大的作用。

我想知道的是,是否有一個很好的方法來決定何時觸發something_happens(),這將不會受到重新排序操作導致的計算結果中的微小變化的影響,從而使行爲更新版本的程序將與舊版本保持一致。

唯一的解決辦法,我至今能想到的是使用一些價值小量這樣的:

if (value < epsilon) something_happens(); 

但由於結果的微小變化隨着時間積累,我需要做出相當小量大(相對來說),以確保變化不會導致something_happens()在不同的步驟被觸發。有沒有更好的辦法?

我讀過this excellent article浮點比較,但我沒有看到任何比較方法描述可以幫助我在這種情況下。

注意:使用整數值不是一個選項。


編輯用雙打,而不是浮動的可能性已經提高。這不會解決我的問題,因爲變化仍然存在,他們只是一個較小的數量級。

+4

如果微小的變化會導致輸出的巨大變化,是不是隻是告訴你,你的結果準確度低? (另外:爲什麼float不是double?) – 2012-03-08 05:34:37

+3

小心:'printf(「%。16f \ n」,f1);'這是一個意想不到的副作用:它會將你的float轉換爲一個加上無效數字的double。我認爲,浮點數最多爲7位精度。 – 2012-03-08 05:52:25

+4

標準的方法是對浮點值進行求和的方法是:對它們進行排序並從最小值到最大值進行求和,這樣就會降低最低精度。也使用雙不浮動。 – 2012-03-08 05:54:04

回答

0

我建議您單步執行 - 最好在彙編模式下 - 通過計算,同時在計算器上執行相同的算術運算。您應該能夠確定哪些計算順序會產生比您預期的質量更差的結果以及哪些工作成果。你將從中學習,並且可能會在未來編寫更好的計算。

最後 - 給出您使用的數字示例 - 您可能需要接受一個事實,即您將無法進行等式比較。

至於epsilon方法,通常每個可能的指數需要一個epsilon。對於單精度浮點格式,由於指數爲8位寬,因此需要256個單精度浮點值。有些指數是異常的結果,但爲了簡單起見,最好有256個成員矢量,而不是做很多測試。

要做到這一點的一種方法可能是在指數爲0的情況下確定您的基本epsilon,即要比較的值在1.0 < = x < = 2.0的範圍內。優選地,ε應該被選擇爲適應基2,即可以以單精度浮點格式精確表示的值 - 這樣,您可以確切地知道您正在測試的內容,並且不必考慮將ε中的舍入問題視爲好。對於指數-1,您可以使用基數ε除以2,將-2除以4,依此類推。當您接近指數範圍的最低和最高部分時,您會逐漸失去精度 - 因此您需要注意極值可能會導致epsilon方法失敗。

4

我已經使用了2年的仿真模型,並且epsilon方法是比較您的浮動的最好方法。

0

如果它絕對必須漂浮,那麼使用epsilon值可能會有所幫助,但可能無法消除所有問題。我會建議使用雙打代碼,你知道肯定會有變化的地方。

另一種方法是使用浮動來模擬雙打,有很多技術,最基本的是使用2浮點數並做一點數學運算來將大部分數字保存在一個浮點數中,剩下的另一個(看到了一個很好的指導,如果我找到它,我會鏈接它)。

3

通常,如果您需要使用浮點數,則使用合適的epsilon值是要走的路。這裏有幾件事可能有所幫助:

  • 如果你的值在一個已知的範圍內,你並不需要劃分,你可能能夠擴展問題並使用整數的精確操作。一般來說,這些條件不適用。
  • 變化是使用有理數做精確計算。這對可用的操作仍然有限制,並且通常會對性能產生嚴重影響:您爲了準確性而交易性能。
  • 舍入模式可以更改。這可以用來計算間隔而不是單個值(可能有3個值由四捨五入,四捨五入和最接近的四捨五入)。再說一遍,它不適用於所有事情,但你可能會得到一個錯誤估計。保持跟蹤值和一些操作(可能的多個計數器)也可以用來估計當前的錯誤大小。
  • 爲了可能試驗不同的數字表示(float,double,間隔等),您可能需要將模擬實現爲參數化爲數值類型的模板。
  • 在使用浮點運算時,有許多關於估計和最小化錯誤的書籍。這是數學數學的主題。

大多數情況下,我很清楚上述一些方法的實驗,並得出結論,該模型無論如何都不準確,並且不費心費力。另外,除了使用float之外的其他功能,可能會產生更好的結果,但速度太慢,即使使用double也是如此,原因是內存佔用空間增加了一倍,並且使用SIMD操作的機會較小。

+0

在某些情況下,「標準化」(?)可以提供幫助。假設一個對象正在移動或旋轉,並且每次都基於最後的座標重新計算其新座標。隨着時間的推移,隨着錯誤的累積,對象可能會開始失去原有的形狀或尺寸。考慮到預期的形狀和大小,可以通過適當地重新計算/調整座標(每次或每隔一段時間)來解決這個問題。 – 2012-03-08 08:55:23

0

當然,你應該使用雙打而不是浮動。這可能會顯着減少翻轉節點的數量。

通常,使用epsilon閾值僅在您比較兩個浮點數的平等性時纔有用,而不是在您比較兩個浮點數時是否比較大。所以(對於大多數模型來說,至少)使用epsilon並不會爲你帶來任何好處 - 它只會改變一組翻轉的節點,它不會讓這個設置變小。如果你的模型本身是混亂的,那麼它是混亂的。

相關問題