先看以下計算的輸出: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"
谷歌瀏覽器已經支持了,能夠嘗試下~
獲取更多幹貨分享,請【掃碼關注】~