前端應該知道的JavaScript浮點數和大數的原理

不知道你們在平時的搬磚中有沒有遇到過一些JavaScript數字相關的坑,好比比較經典的0.1+0.2=0.3000000000000000四、JavaScript有一個Number.MAX_VALUE還有一個Number.MAX_SAFE_INTEGER等等問題。若是這些問題不瞭解清楚,業務開發中頗有可能會出現一些很奇怪的問題。

幾個問題

先拋出幾個問題
git

  1. 爲何0.1+0.2 != 0.3?
  2. 爲何1.005.toFixed(2)=1.00而不是1.01
  3. 爲何會有Number.MAX_VALUE和Number.MAX_SAFE_INTEGER這兩個常量同時存在?

接下來就以這三個問題爲目的來梳理一下前因後果。
github

雙精度存儲

首先在開始以前須要瞭解一下JavaScript的number類型在計算機中是如何存儲的,這也是一切問題的基礎。JavaScript的數字都是number類型的,不論是整數仍是浮點數都以IEEE754雙精度的格式存儲在計算機中,什麼是雙精度呢?就是以64個bit位來存儲,具體的存儲格式是:
瀏覽器



分別是1個符號位+11個指數位+52個尾數位
安全

舉個例子,若是是5.5這個數字的話,則計算過程是這樣的:
5.5 轉二進制 =====> 101.1 科學計數法 =====> 1.011*2^2 
存入計算機: 
符號位:0 
指數位:2 加1023 =====> 1025 轉二進制 =====> 10000000001 
尾數位:1.011 隱去小數點左邊的1 =====> 011

存入計算機,以下圖,截圖來自IEEE754可視化,感興趣能夠把玩一下
測試


接下來進入第一個問題

爲何0.1+0.2 != 0.3?

在瀏覽器控制檯能夠測試一下結果:
debug


下面就剝繭抽絲的講一下爲何會這樣
0.1 轉二進制 =====> 0.0001100110011001100...(1100循環) 
 轉科學計數法 =====> 1.100110011...(1100循環) *2^-4 
數據是無限循環的,可是可供使用的尾數位倒是有限的,只有52位可使用,因此在第53位會被捨去而且進位

最終在計算機中存儲以下圖:
3d


相似的0.2在計算機中的存儲以下圖:
orm


因此最終的計算就是:
cdn

0.00011001100110011001100110011001100110011001100110011010 + 0.0011001100110011001100110011001100110011001100110011010 = 0.0100110011001100110011001100110011001100110011001100111
計算結果轉換爲十進制數字就是0.30000000000000004
因此就是由於,0.1和0.2在計算機中的二進制存儲會讓它們自己損失掉必定的精度,而它們在計算機中的二進制存儲轉換成十進制時已經不是真正的0.1和0.2了,相加的結果也就天然不是0.3了。

問題來了,既然0.1在計算機中的存儲已經有了舍入偏差,那爲何num=0.1能獲得0.1呢?

能夠在控制檯使用 toPrecision看一下0.1在不一樣精度下的返回


能夠看出來其實0.1是截斷了一部分精度後獲得的結果,那麼這個問題就能夠轉化爲:雙精度浮點數是按什麼規則來截斷的呢?
blog

在雙精度浮點數的英文wiki中能夠找到中能夠找到這麼一段話:


大意是:若是一個 IEEE 754 的雙精度浮點數被轉成至少含17位有效數字的十進制數字字符串,當這個字符串轉回雙精度浮點數時,必需要跟原來的數相同;換句話說,若是一個雙精度的浮點數轉爲十進制的數字時,只要它轉回來的雙精度浮點數不變,精度取最短的那個就行。

拿0.1來舉例子,0.1和0.10000000000000001轉成雙精度浮點數的存儲是同樣的,因此取最短的0.1就好了。

接下來是第2個問題

爲何1.005.toFixed(2)=1.00而不是1.01

由於在第一個問題中已經說了,一個十進制數字轉爲雙精度浮點數而後再取出來時,跟原十進制數字可能會有偏差,試一下1.005取20個精度:


很明顯1.005只是一個被截斷後的數字,它的雙精度浮點數表明的20位精度的數字是1.0049999999999998934,因此進行保留2位的四捨五入時,2位後的數字會被所有捨去。

爲何會有Number.MAX_VALUE和Number.MAX_SAFE_INTEGER這兩個常量同時存在?

能夠在控制檯看一下:


爲何最大安全整數是2^53-1?前面說到了JavaScript浮點數存儲是52位尾數位,可是由於科學計數法小數點左側的1會在存儲時省去,因此52位尾數+省去的1位=53個可表示的位數。

二進制的53位全是1時轉換爲十進制既是:2^53-1=9007199254740991

哪爲何2^53-1是最大安全整數呢?比它大會怎樣?

在瀏覽器試一下:


以2^53來講明一下爲何2^53-1是最大安全整數,安全在哪裏
2^53 轉二進制 =====> 100000000000000000000000000000000000000000000000000000(53個0)
 轉爲科學計數法 =====> 1.00000000000000000000000000000000000000000000000000000(53個0)*2^53 
存入計算機 =====> 尾數位只有52位因此截掉末尾的0只能存52個0 
2^53+1 轉二進制 =====> 100000000000000000000000000000000000000000000000000001(52個0) 
轉爲科學計數法 =====> 1.00000000000000000000000000000000000000000000000000001(52個0) 
存入計算機 =====> 尾數位只有52位因此截掉末尾的1只能存52個0
能夠看出來,2^53和2^53+1在計算機中的存儲尾數和指數都相同,因此兩個不一樣的數在計算機中的存儲是同樣的,這樣就很是的不安全了。
因此2^53-1是JavaScript裏面的最大安全整數。至於Number.MAX_VALUE,就是把尾數位和指數位都設爲1再轉爲十進制就行了。

如何解決

上面的說到的浮點數運算、舍入運算、大數運算,均可以使用 bignumber這個庫來解決,打開連接在控制檯就能夠寫demo。

後記

業務中不少難纏的bug每每是基礎知識瞭解的不夠深刻形成的,瞭解這些原理能夠清晰的知道本身在寫什麼,而且快速的debug,不至於陷入cv(control+C、control+V)工程師的循環中。
相關文章
相關標籤/搜索