1. 程式人生 > >深度剖析0.1 +0.2===0.30000000000000004的原因

深度剖析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類問題

鳴謝單位:

segmentfault.com/a/119000000…

demon.tw/copy-paste/…

www.ruanyifeng.com/blog/2010/0…

www.css88.com/archives/73…

www.ecma-international.org/ecma-262/8.…

en.wikipedia.org/wiki/Floati…