[英]Rules-of-thumb for minimising floating-point errors in C?
關於最小化浮點運算中的錯誤,如果我在C中執行如下操作:
float a = 123.456;
float b = 456.789;
float r = 0.12345;
a = a - (r * b);
如果我將乘法和減法步驟分開,計算結果是否會改變,即:
float c = r * b;
a = a - c;
我想知道CPU是否會以不同方式處理這些計算,從而在一種情況下誤差可能會更小?
如果不是,我認為無論如何,是否有任何良好的經驗法則來緩解浮點錯誤? 我可以按照有用的方式按摩數據嗎?
請不要只說“使用更高的精度” - 這不是我所追求的。
編輯
有關數據的信息,在一般意義上,當操作導致非常大的數字(如123456789)時,錯誤似乎更糟。小數字(例如1.23456789)似乎在操作后產生更准確的結果。 我想象這個,還是擴大數字有助於准確?
注意:這個答案首先是對a = a - (r * b);
之間區別的冗長討論a = a - (r * b);
並float c = r * b; a = a - c;
float c = r * b; a = a - c;
使用符合c99標准的編譯器。 最后討論了關於提高准確性同時避免擴展精度的目標的部分問題。
如果您的C99編譯器將 FLT_EVAL_METHOD
定義為0,那么這兩個計算可以產生完全相同的結果。 如果編譯器將FLT_EVAL_METHOD
定義為1或2,則a = a - (r * b);
將成為的某些值更精確的a
, r
和b
,因為所有的中間計算將在擴展精度(來完成double
為值1,並且long double
為值2)。
程序無法設置FLT_EVAL_METHOD
,但您可以使用命令行選項來更改編譯器使用浮點計算的方式,這將使其相應地更改其定義。
根據您是否在程序中使用#pragma fp_contract
以及編譯器的編譯器默認值,可以將一些復合浮點表達式縮減為單個指令,其行為就像中間結果是以無限精度計算的一樣。 發生這種情況靶向現代處理器時,作為成為你的例子的可能性稠-乘法-加法指令將計算a
直接和盡可能准確允許的浮點類型。
但是,您應該記住,收縮只發生在編譯器的選項上,沒有任何保證。 編譯器使用FMA指令來優化速度,而不是精度,因此轉換可能不會在較低的優化級別進行。 有時可以進行多次轉換(例如a * b + c * d
可以計算為fmaf(c, d, a*b)
或fmaf(a, b, c*d)
),編譯器可以選擇一個或者其他。
簡而言之,浮點計算的收縮並不是為了幫助您實現准確性。 如果您喜歡可重現的結果,也可以確保它被禁用。
但是,在fmaf()
-multiply-add復合操作的特定情況下,您可以使用C99標准函數fmaf()
告訴編譯器通過單個舍入在一個步驟中計算乘法和加法。 如果你這樣做,那么編譯器將不允許產生除了a
的最佳結果之外的任何東西。
float fmaf(float x, float y, float z); DESCRIPTION The fma() functions compute (x*y)+z, rounded as one ternary operation: they compute the value (as if) to infinite precision and round once to the result format, according to the current rounding mode.
注意,如果FMA指令不可用,那么編譯器的函數fmaf()
的實現最多只能使用更高的精度 ,如果在編譯平台上發生這種情況,你可能也會使用類型double
來表示累加器:它比使用fmaf()
更快更准確。 在最壞的情況下,將提供fmaf()
的有缺陷的實現。
如果您的計算涉及長鏈添加,請使用Kahan求和 。 通過簡單地將r*b
項計算為單精度乘積,可以獲得一些精度,假設它們中有許多。 如果你想獲得更高的准確度,你可能想要將r*b
本身精確地計算為兩個單精度數的總和,但如果你這樣做,你也可以完全轉換為雙單數算術。 雙單算法將與此處簡潔描述的雙重雙重技術相同,但使用單精度數字。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.