2014-06-20

浮點數的精確度問題

首先我們先由一段C++程式碼來看浮點數的精確度問題。
(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一樣吧?
然而輸出結果卻分別是truefalse

從數學的角度上來看,應該是a=b=c才正確,
然而在CS的領域裡,卻有精確度的問題,
因為我們只能用有限的bit去儲存資訊。

目前大部分程式語言的浮點數標準由IEEE 754定義,
其中C/C++/Java的floatdouble分別對應到
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

沒有留言:

張貼留言