js中精度問題以及解決方案


js中的數字按照IEEE 754的標准,使用64位雙精度浮點型來表示。其中符號位S,指數位E,尾數位M分別占了1,11,52位,並且在ES5規范中指出了指數位E的取值范圍是[-1074, 971]

精度問題匯總

想用有限的位來表示無窮的數字,顯然是不可能的,因此會出現一些列精度問題:

  • 浮點數精度問題,比如0.1 + 0.2 !== 0.3
  • 大數精度問題,比如9999 9999 9999 9999 == 1000 0000 0000 0000 1
  • toFixed四舍五入結果不准確,比如1.335.toFixed(2) == 1.33

浮點數精度和toFixed其實屬於同一類問題,都是由於浮點數無法精確表示引起的,如下:

    
(1.335).toPrecision(20);    // "1.3349999999999999645"

而關於大數精度問題,我們可以先看下面這個代碼片段:

/ 能精確表示的整數范圍上限,S為1個0,E為11個0,S為53個1
Math.pow(2, 53) - 1 === Number.MAX_SAFE_INTEGER    // true
// 能精確表示的整數范圍下限,S為1個1,E為11個0,S為53個1
-(Math.pow(2, 53) - 1) === Number.MIN_SAFE_INTEGER    // true
// 能表示的最大數字,S為1個0,E為971,S為53個1
(Math.pow(2, 53) - 1) * Math.pow(2, 971) === Number.MAX_VALUE    // true
// 能表示的最接近於0的正數,S為1個0,E為-1074,S為0
Math.pow(2, -1074) === Number.MIN_VALUE // true

通過以上可以明白,[MIN_SAFE_INTEGER, MAX_SAFE_INTEGER]的整數都可以精確表示,但是超出這個范圍的整數就不一定能精確表示。這樣就會產生所謂的大數精度丟失問題。

解決思路

首先考慮的是如何解決浮點數運算的精度問題,有3種思路:

  1. 考慮到每次浮點數運算的偏差非常小(其實不然),可以對結果進行指定精度的四舍五入,比如可以parseFloat(result.toFixed(12));
  2. 將浮點數轉為整數運算,再對結果做除法。比如0.1 + 0.2,可以轉化為(1*2)/3
  3. 把浮點數轉化為字符串,模擬實際運算的過程。

先來看第一種方案,在大多數情況下,它可以得到正確結果,但是對一些極端情況,toFixed到12是不夠的,比如:

210000 * 10000  * 1000 * 8.2    // 17219999999999.998
parseFloat(17219999999999.998.toFixed(12));    // 17219999999999.998,而正確結果為17220000000000

上面的情況,如果想讓結果正確,需要toFixed(2),這顯然是不可接受的。

再看第二種方案,比如number-precision這個庫就是使用的這種方案,但是這也是有問題的,比如:

// 這兩個浮點數,轉化為整數之后,相乘的結果已經超過了MAX_SAFE_INTEGER
123456.789 * 123456.789     // 轉化為(123456789 * 123456789)/1000000,結果是15241578750.19052

所以,最終考慮使用第三種方案,目前已經有了很多較為成熟的庫,比如bignumber.jsdecimal.js,以及big.js等。我們可以根據自己的需求來選擇對應的工具。並且,這些庫不僅解決了浮點數的運算精度問題,還支持了大數運算,並且修復了原生toFixed結果不准確的問題。

題外話

還有另外一個與js計算相關的問題,即Math.round(x),它雖然不會產生精度問題,但是它有一點小陷阱容易忽略。下面是它的舍入的策略:

  • 如果小數部分大於0.5,則舍入到下一個絕對值更大的整數。
  • 如果小數部分小於0.5,則舍入到笑一個絕對值更小的整數。
  • 如果小數部分等於0.5,則舍入到下一個正無窮方向上的整數

所以,對Math.round(-1.5),其結果為-1,這可能不是我們想要的結果。

當然,上面提到的big.js等庫,都提供了自己的round函數,並且可以指定舍入規則,以避免這個問題。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM