考慮以下兩種情況:
// Case 1
double val { initial_value };
for (int i { 0 }; i < n; i) {
val = step;
foo(val);
}
// Case 2
for (int i { 0 }; i < n; i) {
double val = initial_value i * step;
foo(val);
}
其中n是步數,initial_value是型別的一些給定的值double,step是型別的一些預定值double并且val是在后續呼叫的函式所使用的變數foo。哪種情況會產生較少的浮點錯誤?我的猜測是第二個,因為只有一個加法和乘法,而第一種情況會導致所有n加法的浮點表示錯誤。我問這個問題是因為我不知道要搜索什么。對于這些案例,是否存在一些很好的參考?
在實踐中,變數val將在兩種情況的回圈中使用。我沒有包含任何示例,因為我只對浮點錯誤感興趣。
uj5u.com熱心網友回復:
選項 2 的誤差明顯較低。
多少?好吧,讓我們承擔initial_value的0為簡單起見,在第一。您有 53 個有效位,您看到舍入錯誤的速度取決于我們在加法程序中設法將它們移出遠端的速度。
因此,讓我們挑step使得顯著位理想的全1: 0.999999999999999999999999。
現在舍入誤差是每次加法程序中log2(val/step)來自遠端的位step。在第一次迭代期間并不多,但錯誤很快就會變得明顯。
選擇一個巨大initial_value的錯誤可能會變得非常極端。對于initial_value >= pow(2, 53) * step,您的第一個回圈甚至val在迭代之間根本無法更改。
您的第二個回圈仍然可以正確處理。
uj5u.com熱心網友回復:
考慮到評論的supercat(重點煤礦):
關鍵是在許多情況下,人們可能需要在指定的起點和終點之間均勻間隔的一系列值。使用第二種方法將產生的值在起點和接近所需值的結束值之間盡可能均勻地間隔,但可能不完全匹配。
還有一個由Bathsheba 寫的:
兩者都有缺陷。您應該計算開始和結束,然后將每個值計算為這些值的函式。第二種方法的問題是你一步一步地乘以錯誤。前者累積錯誤。
我建議幾個選擇。
從 C 20 開始,標準庫提供std::lerp,其中
std::lerp(a, b, t)回傳“引數 t 的 a 和 b 之間的線性插值(或外推,當 t 超出范圍 [0,1])”。類似的公式
value = (a * (n - i) b * i) / n;可能會導致中間值的1分布更均勻。
(1)在這里,我嘗試針對不同的極端情況和樣本點數量測驗所有這些方法。該程式比較每個演算法在以相反方向應用時生成的值(首先從左到右,然后從右到左)。它顯示了中間點值之間絕對差之和的平均值和方差。
其他指標可能會產生不同的結果。
uj5u.com熱心網友回復:
考慮一個極端情況。假設它initial_value比 大得多step。很多很多。initial_value step == initial_value由于浮點表示的限制,如此之大。但是,我們不希望這種“極端”情況變得過于極端。戴上帽子initial_value,說保持小到可以有initial_value (2*step) != initial_value。(有些人可能會稱此為step介于某個 epsilon 和該 epsilon 的一半之間,但我會將術語混淆。)現在運行您的代碼。
在第一個回圈中,val將等于initial_value每次迭代,因為沒有執行會更改其值的操作。相反,val如果有足夠的迭代,第二個回圈最終將具有不同的值。因此,initial_value i * step在這種極端情況下,計算的第二個選項更準確。
我們還應該看看相反的極端。假設它initial_value相對于step那個那么小initial_value step == step。在這種情況下,initial_value也可能為零,問題簡化為詢問是否有i*step比乘以i和更準確的計算方法step。(如果有,我可能想要一個新的編譯器。)因此,在這種極端情況下,第二個選項并不比第一個更差。
極端案例分析不是結論性的,但它往往能揭示趨勢。我把計算推到了相反的極端,第二個選項從絕對更好到絕對不差。我愿意得出結論,第二個選項產生的錯誤更少。
警告:可能是錯誤的大小可以忽略不計,不值得編碼。此外,該問題的范圍有限,忽略了其他考慮因素(例如step從哪里來;如果是除以 的結果n,可能還有更好的選擇)。盡管如此,在問題提出的狹窄場景中,計算initial_value i*step每次迭代看起來像是獲得最小數值誤差的方法。
uj5u.com熱心網友回復:
關于這個問題的大多數答案和共識是“乘法更好”。在這些答案/評論中爭論了這種觀點,但我想提出至少一個具體案例,其中加法可以比乘法更“精確”。也就是說,實際上不能聲稱乘法在每種情況下都會更精確。我希望這里的討論能增加價值。特別是,它認為在重復添加至少六次之前沒有區別,這是一個有趣的花絮。
讓我們考慮一個相關的問題,我們可以根據實際數字提供具體的證據:
是否有一個整數
n和一個 IEEE-754 雙精度值x,這樣加上x自身的n時間會產生比 更精確的結果n*x?假設舍入的默認舍入到最近關系到偶數方案。
當以這種方式提出時,這成為我們可以向現代 SMT 求解器 ( http://smtlib.cs.uiowa.edu )提出的問題,該求解器具有 IEEE754 浮點邏輯和自動求解器的形式化。
我使用 Microsoft 的 z3 SMT 求解器 ( https://github.com/Z3Prover/z3/wiki ) 進行了實驗。事實證明,當n最多為 時,重復加法和直接乘法之間沒有區別5。他們第一次不同意是什么時候n是 6。這是求解器找到的例子:
x = -5.795207520198469e230
結果:
x (x (x (x (x x)))) = -3.477124512119081e231
6 * x = -3.477124512119082e231
注意指數前最后一位數字的微分。(1對比2。)
這些結果中哪一個更好?我們可以通過對相同運算式使用任意精度計算器來確定。準確的結果是:
-3.4771245121190814e 231
在這種情況下,重復添加似乎更好!
但是,您不應該由此得出結論,即重復加法總是更好的近似值。這只是一個例子,總的來說乘法確實可以更好。
總結,與該數字重復 5 次加法或乘法沒有區別。如果您允許n至少為6,那么您可以獲得不同的結果。其他答案認為乘法通常更好,但這個例子表明這并非普遍正確。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/340741.html
下一篇:優化之間的C 程式行為不同
