標籤:
問題
用js進行浮點數計算,結果可能會“超出預期”,大部分計算結果還是對的,但是我們可不想在計算這麼嚴謹的事情上還有意外的驚喜。比如:
- 0.3 + 0.6 = 0.8999999999999999
- 0.3 - 0.2 = 0.09999999999999998
- 0.3 * 1.5 = 0.44999999999999996
- 0.3 / 0.1 = 2.9999999999999996
看完這幾個計算結果,如果你沒用過js,你可能會有點崩潰。我只能說,這就是js的魅力所在。
分析
在這之前,你需要知道以下幾點:
- js中數字類型只有Number;
- js的Number是IEEE 754標準的64-bits的雙精確度數值
網上有很多關於此問題的解釋,由於電腦是用二進位來儲存和處理數字,不能精確表示浮點數,而js中沒有相應的封裝類來處理浮點數運算,直接計算會導致運算精度丟失。其實進階語言(c#,java)也存在此問題,只不過它們自己內部做了處理,把這種精度差異給屏蔽掉了。有些小數轉換為二進位位元是無窮的(有迴圈),但是64位中小數最多隻有52位,因此對於位元超過的相當於被截取了,導致了精度的丟失。這個地址可以用來浮點數和IEEE 754標準的64-bits的互轉(背後是二進位的轉換),用這個我們來驗證下0.3-0.2。
- 0.3轉換後為0.299999999999999988897769753748
- 0.2轉換後為0.200000000000000011102230246252
- 0.299999999999999988897769753748-0.200000000000000011102230246252=0.099999999999999977795539507496
這和js直接計算的結果0.09999999999999998想吻合。
分析下來,終於明白並不是js自身發育不良,只是沒有及時補充營養,我們只能另想出路了。
解決方案
網上已經存在很多解決方案了,我這裡也沒有特別的方法,但是網上有很多方法只搞定了一半,仍然存在bug。大部分解決方案的思路是將浮點數計算轉換為整數計算,整數計算當然是沒有bug的啦。前面說網上部分方法只搞對了一半,對的一半是乘除法,加減法仍然有問題,因為加減法還存在浮點數的直接運算。
附:沒有bug的代碼。
function add(a, b) { var c, d, e; try { c = a.toString().split(".")[1].length; } catch (f) { c = 0; } try { d = b.toString().split(".")[1].length; } catch (f) { d = 0; } return e = Math.pow(10, Math.max(c, d)), (mul(a, e) + mul(b, e)) / e;}function sub(a, b) { var c, d, e; try { c = a.toString().split(".")[1].length; } catch (f) { c = 0; } try { d = b.toString().split(".")[1].length; } catch (f) { d = 0; } return e = Math.pow(10, Math.max(c, d)), (mul(a, e) - mul(b, e)) / e;}function mul(a, b) { var c = 0, d = a.toString(), e = b.toString(); try { c += d.split(".")[1].length; } catch (f) {} try { c += e.split(".")[1].length; } catch (f) {} return Number(d.replace(".", "")) * Number(e.replace(".", "")) / Math.pow(10, c);}function div(a, b) { var c, d, e = 0, f = 0; try { e = a.toString().split(".")[1].length; } catch (g) {} try { f = b.toString().split(".")[1].length; } catch (g) {} return c = Number(a.toString().replace(".", "")), d = Number(b.toString().replace(".", "")), mul(c / d, Math.pow(10, f - e));}
JS浮點計算問題