當前位置:
首頁 > 知識 > 為什麼0.1+0.2不等於0.3?

為什麼0.1+0.2不等於0.3?

在python的控制台也是輸出這個數:


在C裡面運行以下代碼,指定輸出小數位為57位:


printf("%.57f", 0.1 + 0.2);


將得到:


0.300000000000000044408920985006261616945266723632812500000

那我們的問題來了,為什麼計算機計算的0.1加0.2會不等於0.3?


首先我們來看一下JS能夠表示最大數是多少,如下所示,列印Number.MAX_SAFE_INTEGER和Number.MAX_VALUE:

為什麼0.1+0.2不等於0.3?



JS能表示的最大整數為9e16,能表示的最大正數為1.79e308,這兩個數是怎麼得來的呢?先來看一下整數在計算機的存儲方式。


我們知道計算機是使用二進位存儲數據的,整數也是同樣的道理,整數可以分成短整型、基本型、長整型,佔用的存儲空間分別為16位、32位、64位,如果操作系統是32位的,那麼使用長整型將會慢於短整型,因為一個數它需要分兩次取,而在64位的操作系統,一次就可以取到8個位元組或64位的數據,所以使用長整型不會有性能問題。另外,32位的操作系統內存只能識別到2 ^ 32 = 4G,而現在的電腦內存動不動就是8G、16G,所以現在的電腦基本都是64位的,不比前幾年。


32位有符號整型的存儲方式如下圖所示:


第一位0表示正數,1表示負數,剩下的31位表示數值,所以32位有符號整數最大值為:


即21億多,如果要表示全球人口,那麼32位整型是不夠的。同理,64位有符號整型能表示的最大值為:


這是一個19位數,mysql資料庫的id欄位就經常用長整型表示,那為什麼JS能表示的最大整數只有16位,而不是19位呢?這個要先說一下浮點型在計算機的存儲方式。

現在浮點型的存儲實現基本按IEEE754標準,符點數分為單精度和雙精度,單精度為32位,雙精度為64位。


在十進位裡面,一個小數如0.75可以表示成7.5 * 10 ^ -1,同樣地在二進位裡面,0.75可以表示成:


0.75 = 1.1 * 2 ^ -1


即0.75 = (1 + 1 * 2 ^ -1) * 2 ^ -1,其中冪次方-1用階碼錶示,而1.1由於二進位整數部分都是1,所以去掉1留下0.1作為尾數部分(因為都是1點多的形式,所以這個1就沒必要存了)。因此0.75在單精度浮點數是這樣表示的:

為什麼0.1+0.2不等於0.3?



注意階碼要加上一個基數,這個基數為2 ^ (n – 1) – 1,n為階碼的位數,32位的階碼為8位,所以這個基數為127,8位階碼能表示的最小整數為0,最大整數為255,所以能表示的指數範圍為:(0 – 127) ~ (255 – 127)即-127~128,上面要表示指數為-1,需要加上基數127,就變成126,如上圖所示。


而尾數為0.1,所以尾數的最高位為1,後面的值填充0.


反過來,如果知道一個二進位的存儲方式,同樣地可以轉換成10進位,如上圖的計算結果應為:


(1 + 1 * 2 ^ – 1) * 2 ^ (126 – 127) = 1.5 * 2 ^ -1 = 0.75

那麼0.1又該如何表示成一個二進位呢?


由於0.75 = 1 * 2 ^ -1 + 1 * 2 ^ -2,剛好可以被二進位精確表示,那0.1呢?沒辦法了,0.1無法被表示成這種形式,只能是用另外一個數儘可能地接近0.1(同理1/3無法在10進位精確表示,但是可以在3進位精確表示,只是我們習慣了10進位)。


我們可以用一小段C代碼來研究一下0.1被存儲成什麼了,如下代碼所示:


因為C可以讀取到原始的內存內容,所以可以列印每位的數據是什麼。如上代碼列印的結果如下:


雙精度浮點數用11位表示階碼,52位表示尾數,如下圖所示:


尾數約為0.6:


由於這個精度不夠,我們要找一個高精度的計算器,如筆者找的這個:

為什麼0.1+0.2不等於0.3?



有了這個尾數之後,再讓它乘以指數,得到結果為:

也就是說0.1的實際存儲要比0.1大,大概大了5.5e-17.


注意到,0.2和0.1的區別在於0.2比0.1的階碼大了1,其它的完全一樣。所以,0.2也是偏大了:


兩個數相加的結果為:

為什麼0.1+0.2不等於0.3?



但是注意到0.1 + 0.2並不是上面的結果,要比上面的大:

為什麼0.1+0.2不等於0.3?



這又是為什麼呢?因為浮點數相加,需要先比較階碼是否一致,如果一致則尾數直接相加,如果不一致,需要先對階,小階往大階看齊,即把小階的指數調成和大階的一樣大,然後把它的尾數向右移相應的位數。如上面的0.1是小階,需要對它進行處理,如下:


需要把0.1的小數點向右移一位變成:

向右移一位導致尾數需要進行截斷,由於最後一位剛好是0,所以這裡直接捨棄,如果是1,那麼尾數加1,類似於十進位的四捨五入,避免誤差累積。現在0.1和0.2的階碼一樣了,尾數可以進行相加減了,如下把它們倆的尾數相加:


可以看到,發生了進位,變成了53位,已經超過了尾數52位的範圍,所以需要把階碼進一位,即指數加1,兩數和的尾數右移一位,即除以2,由於尾數的最後一位是1,進行「四捨五入」,即捨棄最後一位後再加上1,最後尾數變成了如下圖所示:


而指數加1,變成了-2,所以最後的計算結果為:


這個就和控制台的輸出一致了,並且和C的輸出完全一致。到此,我們就回答了為什麼0.1加0.2不等於0.3了。上面還提出了兩個問題,其中一個是:為什麼JS的最大正數是1.79e308呢?這個數其實就是雙精度浮點數所能表示最大正值,如下使用python的輸出:


那為什麼JS的最大正整數不是正常的64位的長整型所能表示的19位呢?因為JS的正整數是用的尾數的長度表示的,由於尾數是52位,加上整數的一位,它所能表示的最大的整數為:


為什麼JS要用這種方式呢?因為JS的整型和浮點型在計算過程中可以隨時自動切換,應該是考慮到了這個原因,所以才拿浮點型的大小限制來做整型大小的限制。


由於後端的資料庫的ID欄位可能會大於這個值,如果傳來了一個很大的數,在調JSON.parse的時候將會丟失精度,ID就不對了,所以如果出現這種情況,應該讓後端把ID當成字元串的方式傳給你。


但是會有一種情況精度要求很高,15位精度會不夠用,例如計算天體運算。那怎麼辦呢?有一種絕對精準的方式就是用分數表示,例如0.1 + 0.2 = 3/10,計算的過程和最後的結果都用分數表示,分數的結果,你需要精確到多少位都可以取到。這個在matlab/maple等科學計算軟體都有實現。


最後怎麼判斷兩個小數是否相等呢?用等號肯定是不行的了,判斷兩個小數是否相等要用它們的差值和一個很小的小數進行比較,如果小於這個小數,則認為兩者相等,ES6新增了一個Math.EPSILON屬性,如比較0.1 + 0.2是否等0.3應該這麼操作:

為什麼0.1+0.2不等於0.3?



作者:會編程的銀豬


原文:http://www.renfed.com/2017/05/13/float-number/


---- 廣告----


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 JavaScript 的精彩文章:

怎樣實現前端裁剪上傳圖片功能
最終,JavaScript成為了一流語言
JavaScript非同步與Promise實現
這真的是我見過的最全的全套視頻資源
如何看待浙江大學申請雙向綁定技術專利

TAG:JavaScript |

您可能感興趣

6GB+驍龍660,1399元的360N6為什麼賣不好?
360手機2017年銷量500萬,360為什麼要做手機
為什麼1200X600瓷磚成了「萬人迷」
小姐姐穿什麼鞋 「2018 Vol.12」
比領克01漂亮10倍,1.6T配全景天窗,售14萬,還看什麼VV7?
從2015到2018,小米為什麼選擇現在上市?
「2018,遇見英國」2018年英國的熱門資產類別是什麼?
最便宜驍龍660手機,6GB的360N6為什麼賣不好?
今天你穿了什麼鞋之網友投稿「2018 Vol.13」
都是天絲,40S、60S、80S有什麼區別?
2018什麼手機續航好?10款續航能力4000mAh以上手機推薦
每天被100000萬細菌環繞,是什麼感覺?
小米為什麼能支撐起這1000億市值?
夏朝距今只有4000年,為什麼我們說5000年中華文明史?
1944年諾曼底登陸德國為什麼用450架飛機對戰盟軍13700架飛機?
小米憑什麼估值1000億美元?蘋果表示不服!
測試| 2018年什麼顏色旺你!
不平等條約為什麼要簽訂99年?比100年更屈辱
100斤和120斤真的沒什麼區別!?讓這些男神、女神的黑歷史告訴你...
宇宙直徑有930億光年,它在什麼時候會達到1000億光年呢?