簡體   English   中英

最小化C中浮點錯誤的經驗法則?

[英]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); 將成為的某些值更精確的arb ,因為所有的中間計算將在擴展精度(來完成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.

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