震驚!小 bug 引發(fā)大災(zāi)難,0.1 + 0.2 的結(jié)果竟然是……

各位觀眾看文章的時(shí)候,我或許正在小黑屋默默的反思自己為什么要成為一個(gè)標(biāo)題黨!

好了,說(shuō)正事

請(qǐng)大家思考一下在 python 控制臺(tái)輸入 0.1 + 0.2 == 0.3 ,最終的結(jié)果為
True 還是 False?
手邊有電腦的同學(xué)可以立即在 python 控制臺(tái)下嘗試一下,對(duì)浮點(diǎn)數(shù)精度不夠了解的同學(xué)可能會(huì)大呼:天啦嚕,夭壽啦,怎么會(huì)是 False !
沒(méi)錯(cuò) ,不管是在 Python 、Java、JavaScript 還是其他任何語(yǔ)言中,都是 False

為什么會(huì)出現(xiàn)這樣的結(jié)果,首先我們要明白,在計(jì)算機(jī)的存儲(chǔ)類型為二進(jìn)制,十進(jìn)制的 0.1 與 0.2 在計(jì)算機(jī)中會(huì)已二進(jìn)制的形式表示,規(guī)則如下:

十進(jìn)制小數(shù)轉(zhuǎn)換成二進(jìn)制小數(shù)采用"乘2取整,順序排列"法。具體做法是:用2乘十進(jìn)制小數(shù),可以得到積,將積的整數(shù)部分取出,再用2乘余下的小數(shù) 部分,又得到一個(gè)積,再將積的整數(shù)部分取出,如此進(jìn)行,直到積中的小數(shù)部分為零,或者達(dá)到所要求的精度為止。

以 0.1 為例,我們做一下轉(zhuǎn)換:

步數(shù) 算式 結(jié)果
1 0.1 * 2 = 0.2 取 0
2 0.2 * 2 = 0.4 取 0
3 0.4 * 2 = 0.8 取 0
4 0.8 * 2 = 1.6 取 1
5 0.6 * 2 = 1.2 取 1
6 0.2 * 2 = 0.4 取 0
7 0.4 * 2 = 0.8 取 0
8 0.8 * 2 = 1.6 取 1
... ...... ...

比較第二步和第六步,可以得知, 已二進(jìn)制 表示 0.1 最終的結(jié)果為一個(gè)無(wú)限循環(huán)的數(shù)
0.0001100110011...... ,但由于計(jì)算機(jī)的存儲(chǔ)位數(shù)是有限的,并不能存儲(chǔ)一個(gè)無(wú)限循環(huán)的數(shù)。對(duì)于 Python 來(lái)說(shuō),浮點(diǎn)數(shù)有 53 位精度。為了把這個(gè)數(shù)存起來(lái),必然會(huì)丟失部分精度,造成誤差,所以最終的近似結(jié)果為:

0.00011001100110011001100110011001100110011001100110011010

同理, 對(duì) 0.2 的處理也是一樣,所以當(dāng)兩個(gè)存在誤差的數(shù)相加,其結(jié)果也必定出現(xiàn)誤差,這也很好的解釋了在計(jì)算機(jī)中為什么 0.1 + 0.2 不等于 0.3 !

浮點(diǎn)數(shù)精度的知識(shí)遠(yuǎn)不止此,攤開(kāi)來(lái)講一本書(shū)也講不完,所以對(duì)于初學(xué)者來(lái)說(shuō)只要知道有這么回事就行了,之后再遇到就不要驚訝了,日常工作中遵循以下準(zhǔn)則:

1、盡量避免使用小數(shù)比較大小,比較兩個(gè)小數(shù)是否相等時(shí)可寫(xiě)成 abs(a - b) < 0.000001
2、確保數(shù)組的索引都是整數(shù)。
3、按分(而不是元)計(jì)算金額。百分比放大100倍計(jì)算以避免出現(xiàn)小數(shù)。
4、Python3 使用除法(/)時(shí)需注意,它的結(jié)果總是小數(shù),整除的符號(hào)是 //。
5、避免在同一個(gè)表達(dá)式中使用相差太大或太小的數(shù)值。將很小的數(shù)值和很大數(shù)值相加,小的數(shù)值很可能被當(dāng)作0。

當(dāng)然,在某些科研、財(cái)務(wù)等對(duì)精度要求比較高的領(lǐng)域中,Python 提供 decimal 模塊準(zhǔn)確控制精度。

它具有以下特點(diǎn):

1、提供十進(jìn)制數(shù)據(jù)類型,并且存儲(chǔ)為十進(jìn)制數(shù)序列;
2、有界精度:用于存儲(chǔ)數(shù)字的位數(shù)是固定的,可以通過(guò) decimal.getcontext().prec=x來(lái)設(shè)定,不同的數(shù)字可以有不同的精度

可以通過(guò)整數(shù)、字符串或者元組構(gòu)建 decimal.Decimal,對(duì)于浮點(diǎn)數(shù)需要先將其轉(zhuǎn)換為字符串


>>>from decimal import *
設(shè)置精度為 7 位
>>>getcontext().prec = 7
>>>Decimal(1) / Decimal(7)
Decimal('0.1428571')

參考文章:

Python官方對(duì)于浮點(diǎn)數(shù)的解釋:https://docs.python.org/2/tutorial/floatingpoint.html
decimal 模塊:https://docs.python.org/2/library/decimal.html
代碼之謎:http://justjavac.iteye.com/blog/1724438
Android計(jì)算器低級(jí)錯(cuò)誤?都是二進(jìn)制惹的禍!:http://www.guokr.com/article/27173/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容