誰動了我的浮點數
- 下面是一段很簡單的javascript代碼,我們把10個0.1累加起來,并且每次都輸出中間的結果:
function sumFloat(){
var sum = 0;
for(var i=0; i<10; i++){
sum += 0.1;
alert(sum);
}
}
- 輸出的sum應該從0.1到1.0,每次增長0.1,然而出乎意料的是,我們看到的是這樣的輸出:
0.1
0.2
0.3000000000000004
0.4
0.5
0.6
0.7
0.7999999999999999
0.8999999999999999
0.9999999999999999
幕後黑手
- 現象很靈異,然而原因很簡單——0.1這個浮點數在二進制中無法精确表示。
- 我們知道,計算機内部的所有數值計算都是基于二進制的,十進制的0.1對應的二進制是0.00001111……,後面的1無限循環,而javascript中浮點數是64位(IEEE 754标準),對位數的截斷帶來了精度的丢失,是以使得累加的結果超出了我們的預料。
權宜之計
- 那麼,既然這個問題來自于進制表示,我們就沒有辦法解決它了嗎?
- 目前我能想到的隻有一個權宜之計——乘一個足夠大的系數讓浮點數變成整數,累加完成後,再除以它得到實際結果。
- 為解釋清楚,還是看修改後的javascript代碼:
function sumFloat(){
var sum = 0;
var ratio = 10;
for(var i=0; i<10; i++){
sum += 0.1 * ratio ;
alert(sum / ratio);
}
sum /= ratio;
}
- 在這個簡單的例子裡,系數為10便足以解決浮點數不精确的問題,但在更複雜的情況下,系數的有效選取是十分困難甚至不可能的問題,目前還沒有想到很好的辦法。
- 另外,若有其它的解決之道,也歡迎補充。
- [我補充一下,最最徹底理想的解決方案:使用字元串表示浮點數,并且編寫相應的計算函數,前提是不要求性能。]
不止伸向javascript的黑手
double sum = 0.0;
for(int i=0; i<10; i++){
sum += 0.1;
System.out.println(sum);
}
- 是否輸出與上面的javascript代碼一樣呢?
- 我們解釋這個問題的時候并沒有涉及到javascript的實作,而是将原因歸咎于進制問題,是以不出意外,我們将在所有具有原生浮點類型的程式設計語言中遇到這個精度丢失的現象,大家可以用各種語言實驗類似的代碼。
- 另外,把java代碼中的double換成float(0.0、0.1改為0.0f、0.1f),看看結果有什麼不一樣?