JavaScript 數字精度問題
雙精度浮點數表示法
JavaScript 數字採用的是 IEEE-754 標準 的雙精度浮點數表示法。雙精度儲存佔用 64bit,其中尾數佔 52bit,這決定了它能表示的最大安全整數為 253-1。所以說,JavaScript 能夠準確表示的整數範圍在 -253 到 253 之間(不含兩個端點),超過這個範圍就無法精確表示,比如:
2**53 // 9007199254740992
9007199254740992 // 9007199254740992
9007199254740993 // 9007199254740992
事實上,出現這個問題的機率極小,畢竟很少有業務需要用到超大整數。相對而言,另一個問題比它嚴重的多。另一個問題是,二進位制浮點數表示法不能精確的表示十進位制小數,比如十進位制小數 0.1 轉為二進位制就成了 0.0001100110011…(無限迴圈)。因此,JavaScript 在進行小數計算的時候會出現一些問題,比如:
.2 - .1 // 0.1
.3 - .2 // 0.09999999999999998
.4 - .3 // 0.10000000000000003
儘管計算結果非常接近最終值,但在某些特定場景下,比如進行重要的金融計算,這個問題就不容小覷了。
Number.prototype.toFixed() 方法的問題
toFixed() 方法使用定點數表示法來格式化一個數字,返回值為 String 型別。值得注意的是該方法的舍入法則並不是四捨五入,很多人對此有誤解。而且,不同瀏覽器對 toFixed() 方法的處理結果是不同的。比如:(2.445).toFixed(2)
在 IE 中的執行結果為 "2.45"
,但是在 Chrome 中的執行結果為 "2.44"
Math.round() 方法的問題
Math.round() 函式返回一個數字四捨五入後最接近的整數。不同於 toFixed() 方法,Math.round() 方法本身沒有問題,你可以放心的使用它對一個數字進行四捨五入。但是實際場景中,我們往往需要處理的是計算結果而非字面量。問題就出在這個計算上,比如 2.445 * 100
的計算結果不是 244.5
而是 244.49999999999997
,這個時候再使用 Math.round() 方法,其結果自然和預期的不一樣了。
Math.round(244.5) // 245
Math.round(2.445 * 100) // 244
2.445 * 100 === 244.5 // false
使用大整數進行計算來解決問題
或許 JavaScript 未來會支援十進位制數字型別以避免這些問題。在這之前,我們需要使用大整數進行計算來解決問題。比如,要使用整數“分”而不是小數“元”進行基於貨幣單位的運算。下面例子演示將 2445 釐表示成 x.xx 元(四捨五入)。
(Math.round(2445 / 10) / 100).toFixed(2) // "2.45"