各位觀眾看文章的時(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/