爲何0.1+0.2=0.30000000000000004

​閱讀本文大約須要8分鐘...chrome

問題

在計算機的世界裏,可能有不少常人沒法理解的事情。好比 0.1 + 0.2 = ?。來,告訴我你的答案。瀏覽器

有的朋友看到這就火燒眉毛的說,這麼簡單的問題,很明顯等於 0.3 啊,小學生都會算的好伐。你這是在侮辱個人智商?學習

好吧,我來告訴你一個打臉的事實,0.1 + 0.2 還真不等於 0.3 。先別急着反駁我。code

打開你的任意一個瀏覽器(我用chrome作演示),F12打開console控制檯,輸入 console.log(0.1 + 0.2) 。若是你操做正確的話,你會看到如下的結果。blog

file

是否是感受匪夷所思,what?爲何結果是0.30000000000000004,這是神魔鬼? 難道,我這麼多年學習的數學知識,老師教的都是錯的?數學

彆着急。其實,你的老師教的沒錯,在咱們的世界中,0.1 + 0.2 確實是等於 0.3 的。可是,在計算機中,可就不是這麼一回事了。待我娓娓道來。console

由於,咱們在計算數學問題的時候,用的是十進制,計算出來結果是0.3沒問題。可是,在計算機中用的是二進制,都是由0和1來組成。這就不得不提一下,十進制轉換二進制了。基礎

二進制轉換

十進制小數轉換二進制的步驟:(以10.25爲例)file

1.先轉換整數部分,除2直到商爲0,倒數取餘。循環

10/2 ... 商5...餘數0 
5/2   ...商2...餘數1
2/2   ...商1...餘數0
1/2   ...商0...餘數1

倒數取餘,就是1010

2.再轉換小數部分,乘2取整,直到小數部分爲0.

0.25*2 ... 0.50 ...整數0

0.50*2 ... 1.0   ...整數1

小數部分爲0,結束,即爲01

所以10.25(10)轉換成二進制,結果就是 1010.01(2)

聰明的你,類比以上方法,應該能夠動手去算一下十進制0.1轉成二進制是多少了。

0.1*2 ... 0.2 ...整數0
0.2*2 ... 0.4   ...整數0
0.4*2 ... 0.8 ...整數0
0.8*2 ... 1.6   ...整數1
0.6*2 ... 1.2 ...整數1
0.2*2 ... 0.4   ...整數0

等等,怎麼感受進入死循環了,小數部分乘以2,一直乘不到小數部分爲0

就像十進制中1/3,結果是0.3(3...)這樣的問題同樣,0.1轉成二進制時也會存在精度問題,咱們須要進行取捨。

咱們看一下0.1在計算機中是怎麼存儲的。對此,須要瞭解一下浮點數的概念。

浮點數

浮點數,顧名思義,小數點是浮動的數。千萬不要覺得浮點數就是小數。由於,在js中是沒有整數和小數的概念的,其實整數也是以浮點數的形式表示的,只是小數部分爲0而已。

浮點數簡單理解,就是相似於咱們十進制中的科學計數法。在計算機中通常遵循IEEE 754標準。格式以下:

(-1)^S * M * 2^E
1. S表示符號位,當S=0時,爲正數;當S=1時,爲負數。
2. M表示有效數字(尾數),大於等於1,小於2。
3. E爲指數(也叫階碼)。

所以,上邊的10.25(二進制1010.01)按照此格式表示即爲 1.01001 * 2^3

對於32位浮點數來講,符號位佔一位,指數位佔8位,尾數佔23位
對於64位浮點數來講,符號位佔一位,指數位佔11位,尾數佔52位

IEEE 754標準

注意:IEEE 754標準規定,在保存尾數M時,第一位默認是1,所以能夠被捨去,只存儲後邊的部分。例如,1.01001保存的時候,只保存01001,等到用的時候再把1加上去。這樣,就能夠節省一個位的有效數字。

指數E在存儲的時候也有些特殊。若爲32位,指數佔8位,則可表示的大小範圍爲0-255 。如爲64位,指數佔11位,範圍爲0-2047 。可是,指數是有正有負的,所以實際值須要在此基礎上減去一箇中間數。對於32位,中間數爲127,對於64位,中間數爲1023 。

仍是以1.01001 * 2^3 爲例,若爲32位浮點數,則須要保存成 3+ 127 = 130,即二進制的10000010,若爲64位浮點數,則保存成 3+ 1023 = 1026 ,即二進制的10000000010。

計算步驟

好了。巴拉巴拉了這麼多。終於,要進入咱們今天的正題了。

咱們看一下 0.1 在計算機中是怎麼用 IEEE 754標準存儲的。

十進制0.1轉爲二進制爲0.001100110011(0011循環),即 1.100110011(0011)*2^-3,所以符號位爲0,尾數1.100110011(0011),階碼爲 -4,實際存儲爲 -4+1023 = 1019 的二進制 1111111011

0  01111111011  1001100110011001100110011001100110011001100110011010
S    E指數             M尾數

十進制0.2轉爲二進制爲0.001100110011(0011循環),即 1.100110011(0011)*2^-3 ,存儲時,符號位爲0,尾數 1.100110011(0011),階碼爲-3,實際存儲爲 -3+1023 = 1020 的二進制 1111111100。

0  01111111100  1001100110011001100110011001100110011001100110011010
S     E指數          M尾數

接下來,計算 0.1 + 0.2 。

浮點數進行計算時,須要對階。即把兩個數的階碼設置爲同樣的值,而後再計算尾數部分。其實對階很好理解,就和咱們十進制科學記數法加法一個道理,先把指數部分化成同樣,再計算尾數。

另外,須要注意一下,對階時須要小階對大階。由於,這樣至關於,小階指數乘以倍數,尾數部分相對應的除以倍數,在二進制中即右移倍數位。這樣,不會影響到尾數的高位,只會移出低位,損失相對較少的精度。

所以,0.1的階碼爲 -4 , 須要對階爲 0.2的階碼 -3 。尾數部分總體右移一位。

原來的0.1
0  01111111011  1001100110011001100110011001100110011001100110011010
對階後的0.1
0  01111111100  1100110011001100110011001100110011001100110011001101

而後進行尾數部分相加

0  01111111100   1100110011001100110011001100110011001100110011001101
+  0  01111111100   1001100110011001100110011001100110011001100110011010
=  0  01111111100  10110011001100110011001100110011001100110011001100111

能夠看到,產生了進位。所以,階碼須要 +1,即爲 -2,尾數部分進行低位四捨五入處理。因尾數最低位爲1,須要進位。因此存儲爲:

0  1111111101  1011001100110011001100110011001100110011001100110100

最後把二進制轉換爲十進制,

二進制爲:
1.1011001100110011001100110011001100110011001100110100 * 2^-2
轉爲十進制爲:
2^-2 * (1*2^0 + 1*2^-1 + 0 + 1*2^-3 + 1*2^-4 + ...)
最終結果爲:
0.3000000000000000444089209850062616169452667236328125
由於精度問題,只取到:
0.30000000000000004

問題總結

1.在十進制轉換爲二進制的過程當中,會產生精度的損失。

2.二進制浮點數進行對階運算時,也會產生精度的損失。

所以,最終結果才產生了誤差。

看完的小夥伴,如今應該能理解,爲何0.1 + 0.2 ≠ 0.3 這個問題了吧。

相關文章
相關標籤/搜索