【3分鐘技能get】javascript浮點數精度處理問題

先看以下計算的輸出:javascript

0.1 + 0.2

顯然是0.3。可是在javascript中,結果是什麼呢?java

0.30000000000000004

這是程序語言在數值計算中很容易出現的精度問題,以下圖餓了麼帳單頁金額顯示。 瀏覽器

問題產生的緣由

先來看對Number類型數值二進制的表示,由3部分組成:安全

符號位 * 指數位 * 尾數位

因爲js採用64位雙精度浮點數編碼,實際存儲時爲了節省空間,採用科學計數法表示,其二進制構成以下: 編碼

符號位佔1位,指數位佔11位,尾數佔52位。spa

問題分解

0.1的二進制表示爲:code

0.0001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 ...

其科學計數法表示爲:blog

1.1001... * 2^-4

其中指數位採用偏置碼處理,-4即爲:01111111011。簡單介紹下(自行百度):ip

雙精度採用的偏置碼爲1023,
好比指數位:01111111011,其值爲1019,
1019 - 1023 = -4

因爲尾數位僅爲52位,所以須要截取前52位,而且如若第53位爲1則進1,反之捨去,所以0.1的尾數位截取後爲:開發

//10011001 10011001 1001100 110011001 10011001 10011001 10011001...
//因爲53位爲1,進1,即爲:
10011001 10011001 10011001 10011001 10011001 10011001 1010

能夠看到0.1的值其實已經不許確了,較原值偏大。其對應的二進制存儲表示以下:

有的童鞋可能注意到了,尾數位存儲的是小數部分,這是由於規格化後的值通式爲1.x,所以能夠略去1,節省了一個bit位空間。

同理0.2的二進制以下:

0.001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001...

科學計數法處理後的二進制存儲爲:

至此,已經清楚了javascript對數值的存儲方式。

進制轉換網址參見:http://www.binaryconvert.com/

0.3的二進制表示爲:

0.010011001100110011001100110011001100110011001100110011...

使用科學計數法表示後存儲爲:

而計算機在處理0.1+0.2時(上面已經知道了其分別對應的二進制存儲方式),須要經過對階、尾數求和、規格化、舍入等操做(這裏再也不贅述),最終獲得:

0.010011001100110011001100110011001100110011001100110100
//轉爲10進制即爲:0.30000000000000004

能夠知道,計算機在進行浮點數加減運算時,包括對階、規格化過程均可能產生精度偏差,核心仍是由於尾數位的位數有限,1進0舍導入的偏差。

如何在開發中避免此類問題?

1. 取固定精度

有的童鞋可能會採用toFixed()獲取固定精度,以下

(0.1+0.2).toFixed(1) = 0.3;

對於精度要求不高的話,這種經過4舍5入獲取固定精度的方式通常能夠知足需求。

2. 先將小數轉爲整數再進行計算

0.1 + 0.2
//將二者都轉化爲整數的最小公倍數:RATE = 10
(0.1*RATE + 0.2*RATE)/RATE
= 0.3

這是平常開發中最經常使用的方式,推薦。

擴展

若是清楚上面講解的數值存儲方式,那麼能夠知道js的安全整數範圍爲:

Math.pow(2, 53) - 1  
// 可表示的安全整數範圍:
// Number.MIN_SAFE_INTEGER ~ Number.MAX_SAFE_INTEGER
-9007199254740991 ~ 9007199254740991

超出這個範圍的整數計算會出現精度丟失問題。

須要處理較大值的話,能夠參考bignumber.js等;另外ES2020,加入了BigInt類型:

let number1 = BigInt(123);  //方式1
let number2 = 123n;  //方式2
number1 == number2;  //true
typeof number1; //"bigint"

谷歌瀏覽器已經支持了,能夠嘗試下~

獲取更多幹貨分享,請【掃碼關注】~
head.png

相關文章
相關標籤/搜索