(Round-Off Erros in Floating Point Numbers)
double a = 1000000; double b = (a/3)*3; double c = (a/7)*7; cout << (a==b ? "true" : "false") << endl; cout << (a==c ? "true" : "false") << endl;
先猜猜看,
最後兩行分別會輸出什麼結果呢?
直覺上來看,
b是a先除3再乘3,c是a先除7再乘7,
兩個變數的計算結果應該都會跟a一樣吧?
然而輸出結果卻分別是true與false。
從數學的角度上來看,應該是a=b=c才正確,
然而在CS的領域裡,卻有精確度的問題,
因為我們只能用有限的bit去儲存資訊。
目前大部分程式語言的浮點數標準由IEEE 754定義,
其中C/C++/Java的float與double分別對應到
IEEE 754標準的single precision(32-bit)、以及double precision(64-bit)。
詳細部分可以參考上方的wiki連結,
或是此篇:Floating Point Standard。
因此在上方的例子中,
我們可能在做除法運算時喪失了原本的精確度。
變數b乘以3後剛好可以還原到原本的值,
然而c乘以7以後還是無法還原回原本的值。
因此,最後是否會還原回原來的值,
答案是不確定,要算過才知道。
double a = NUM1; double b = (a/NUM2)*NUM2; cout << (a==b ? "true" : "false") << endl; // Q: 若NUM1與NUM2分別是某個常數,則此程式的輸出結果為何? // A: 不一定,可能是true或false。
在程式語言中,
我們盡量避免去直接將兩個浮點數做相等關係測試,
因為可能會有無法預期的精確度問題。
取而代之的作法是,
我們會將兩數相減,並判斷結果是否小於一個常數值,
該常數值即為可以容許的誤差。
因此一開始判斷a=b=c的例子,
我們可以修改成:
double a = 1000000; double b = (a/3)*3; double c = (a/7)*7; const double BIAS_1 = 1e-5; cout << (abs(a-b) < BIAS_1 ? "true" : "false") << endl; // true cout << (abs(a-c) < BIAS_1 ? "true" : "false") << endl; // true const double BIAS_2 = 1e-10; cout << (abs(a-b) < BIAS_2 ? "true" : "false") << endl; // true cout << (abs(a-c) < BIAS_2 ? "true" : "false") << endl; // false
我們最後再來看一個有趣的例子,
在程式語言中,結合律(associativity)不一定成立。
double a = -1.5e+38; double b = 1.5e+38; double c = 1.0; cout << (a+b)+c << endl; // 1 cout << a+(b+c) << endl; // 0
沒有留言:
張貼留言