深度剖析0.1 +0.2===0.30000000000000004的原因
用一句話概括就是: EcmaScrpt規範定義Number的型別遵循了IEEE754-2008中的64位浮點數規則定義的小數後的有效位數至多為52位導致計算出現精度丟失問題!
如果你看不懂這句話,仔細閱讀本篇部落格就對了!
首先看下10進位制轉換為2進位制的方法。
數字邏輯電路上的演算法是 (0.1)10 = (0.0)2。
吐槽一句,大二的專業課數字邏輯電路終於用在工作上了。
0.1*2 = 0.2 ,整數位為0,且精度只到十分位,因此是0.0。
如果是不限精度的話,轉換後的二進位制數應該是:0.000110011001100110011(0011)無限迴圈。
如果表示成一個奇怪的形式,則是(-1)^0*1.100110011(0011)* 2^-4
上述式子可類比十進位制科學計數法公式。
0.0001234567 = 1.234567 * 10^-4
為什麼要這樣表示?
-1的0次冪又是什麼意思?
這是國際標準組織IEEE754對於浮點數表示方式的一種定義。
格式為;
(-1)^S x Mx 2^E
各符號的意思如下: S,是符號位,決定正負,0時為正數,1時為負數。 M,是指有效位數,大於1小於2。 E,是指數位。
因此才有了下面的形式:
(-1)^0*1.100110011(無限迴圈0011) * 2^-4
S = 0,M = 1.100110011(無限迴圈0011),E =-4
複製程式碼
對應的0.2為:
(-1)^0*1.100110011(無限迴圈0011) * 2^-3
S = 0 ,M = 1.100110011(無限迴圈0011),E =-3
複製程式碼
那麼這和javascript有什麼關係呢?
因為IEEE754標準裡,還有兩種特殊的定義。
IEEE 754規定,對於32位的浮點數,最高的1位是符號位S,接著的8位是指數E,剩下的23位為有效數字M。
對於64位的浮點數,最高的1位是符號位S,接著的11位是指數E,剩下的52位為有效數字M。
問題還是一樣,這和我們的javascript有什麼關係呢?
因為javascript中Number型別,就是嚴格按照IEEE754標準來定義的。下面給出了最新版的ecma-262版本中關於Number型別的定義。
6.1.6 The Number Type
* The Number type has exactly 18437736874454810627 (that is, ) values, representing the double-precision 64-bit format IEEE 754-2008 values as specified in the IEEE Standard for Binary Floating-Point Arithmetic, except that the 9007199254740990 (that is, ) distinct “Not-a-Number” values of the IEEE Standard are represented in ECMAScript as a single special value.
再看一下wiki百科給出的IEEE754標準:
因此,javascript的Number型別, 最高的1位是符號位S,接著的11位是指數E,剩下的52位為有效數字M。
拿0.1舉例來說:
(-1)^0*1.100110011(無限迴圈0011) * 2^-4
S = 0,M = 1.100110011(無限迴圈0011),E =-4
這裡的無限迴圈就有限了,迴圈位數最多隻能有52位.
JS中的0.1,在引擎中運算時,實質上會編譯成:
1.1001100110011001100110011001100110011001100110011001*2^-4
0.2同理,會編譯成:
1.1001100110011001100110011001100110011001100110011001*2^- 3
拿出關鍵的指數部分和有效位部分:
-4 0.1001100110011001100110011001100110011001100110011001 ①
-3 0.1001100110011001100110011001100110011001100110011001 ②·
複製程式碼
①式轉化為純小數,小數最低位的1001被高位的0000擠出有效範圍,得到③式
②式轉化為純小數,小數最低位的001被高位的000擠出有效範圍,得到④式
原因是什麼?
原因就是JS中的Number型別,二進位制小數的有效位數只有52位,從0到51位(包括邊界)。
在chrome控制檯輸入(0.1).toString('2')
並列印結果為:"0.0001100110011001100110011001100110011001100110011001101"
不多不少,小數部分剛好52位,與規範以及我們的猜想完全契合。
回到0.1+0.2===0.30000000000000004這個經典問題。
在EcmaScript中,無關Browser環境,還是Nodejs環境,0.1+0.2的實際計算過程如下:
0.0000100110011001100110011001100110011001100110011001 ③
+0.0001001100110011001100110011001100110011001100110011 ④
---------------------------------------------------------------------------------------------------
=0.0100110011001100110011001100110011001100110011001100 ⑤
複製程式碼
最後得到的⑤式其實0.300000000000000004(17位十進位制數)的二進位制形式。
這就是0.1+0.2 ===0.300000000000000004的原因。
雖然我們期望的理想結果是返回0.3,恰恰印證了現實往往很骨感的說法。
有沒有讓0.1+02返回為0.3的辦法呢?
因為不只是這一個精度丟失特例,還有很多情況都會造成精度丟失,比如:
0.3 / 0.1===2.9999999999999996以及0.7 * 180==125.99999999998等等。
那麼有沒有辦法解決這個問題呢?
移步下一篇部落格:如何解決0.1 +0.2===0.30000000000000004類問題
鳴謝單位:
www.ruanyifeng.com/blog/2010/0…