聊聊Python中的浮點(diǎn)數(shù)運(yùn)算不準(zhǔn)確問題
大家好,老 Amy 來了。之前就意識(shí)到一個(gè)問題,但是最近又有朋友提出來了,所以就想著干脆記錄下來,分享給大家叭~
啥問題呢?請(qǐng)看題:
也就是說,需要大家計(jì)算1.1-1的值,很多朋友會(huì)說:“emmm…這還不簡(jiǎn)單,玩我呢?不就是0.1嘛”
但是如果你用 python 去執(zhí)行一下,會(huì)發(fā)現(xiàn)結(jié)果跟你想的不太一樣,如下圖:
這樣大家是不是發(fā)現(xiàn)了什么問題?是的,浮點(diǎn)數(shù)在運(yùn)算過程中并沒有保證完全精確,是什么原因?qū)е铝诉@種現(xiàn)象呢?很多朋友就會(huì)竊喜:“這不就是 Python 的 bug 嘛~”
但實(shí)際上,這并不是 Python 中的 bug ,它和計(jì)算機(jī)硬件中如何處理浮點(diǎn)數(shù)有關(guān)。浮點(diǎn)數(shù)在計(jì)算機(jī)硬件中以二進(jìn)制的形式存在,但是我們現(xiàn)在看到的都是十進(jìn)制,而十進(jìn)制的浮點(diǎn)數(shù)不能都完全精確的表示為二進(jìn)制小數(shù)。
就比如說我們?cè)谑M(jìn)制數(shù)中無法用小數(shù)精確表示 1/3 一樣,在二進(jìn)制數(shù)中也無法用小數(shù)精確表示 1/10。顯然這樣子的說明并沒有十進(jìn)制中的 1/3 那么直觀,接下來我們嘗試去計(jì)算一下二進(jìn)制中的 1/10 :
十進(jìn)制的整數(shù)位是二進(jìn)制的整數(shù)位,十進(jìn)制的小數(shù)位是二進(jìn)制數(shù)的小數(shù)位。那現(xiàn)在我們拿到0.1
整數(shù)部分為0
小數(shù)部分為0.1,并順序取值
0.1*2=0.2<1取0 0.2*2=0.4<1取0 0.4*2=0.8<1取0 0.8*2=1.6>1取1 0.6*2=1.2>1取1 0.2*2=0.4<1取0 …
有沒有發(fā)現(xiàn)?在二進(jìn)制下,1/10 是一個(gè)無限循環(huán)小數(shù):0.00011001100110011…,顯然這樣的表示形式無法精確的表示浮點(diǎn)數(shù),最終的結(jié)果是近似 1/10 。在使用 IEEE-754 浮點(diǎn)運(yùn)算標(biāo)準(zhǔn)的計(jì)算機(jī)硬件上,Python 的浮點(diǎn)數(shù)映射為 IEEE-754 雙精度浮點(diǎn)數(shù),共包含 53 位精度(這里指的是二進(jìn)制),在這個(gè)范圍下,這個(gè)最接近 1/10 的結(jié)果是:
3602879701896397/2∗∗55
這表示在計(jì)算機(jī)硬件中,1/10 的真實(shí)十進(jìn)制數(shù)值為:
0.1000000000000000055511151231257827021181583404541015625
那如何進(jìn)行精確的浮點(diǎn)數(shù)運(yùn)算呢?有朋友提出四舍五入可以解決。那我們來仔細(xì)看一下四舍五入真的可以解決這個(gè)問題嗎?
四舍五入進(jìn)行解決
在 python 中,使用 round(number[, ndigits]) 來進(jìn)行四舍五入,其中 ndigits 表示保留幾位小數(shù),默認(rèn)為0。
我們來看代碼如下:
In [10]: round(0.6) Out[10]: 1 In [11]: round(0.65,1) Out[11]: 0.7 In [12]: round(0.64,1) Out[12]: 0.6
上面代碼符合我們四舍五入的預(yù)期結(jié)果,但是不要著急,我們接著往下看:
In [13]: round(1.15,1) Out[13]: 1.1 In [14]: round(0.5) Out[14]: 0 In [15]: round(1.5) Out[15]: 2
這樣看是不是有些問題,什么問題呢?按照四舍五入的話,round(1.15)會(huì)直接進(jìn)為1.2,但是此時(shí)并沒有,而是變?yōu)榱?.1。這是為什么呢?
如果沒有上面對(duì)浮點(diǎn)數(shù)的了解,僅從表象上很難去解釋。我們已經(jīng)知道了在計(jì)算機(jī)內(nèi)部,對(duì)于一些浮點(diǎn)數(shù)是無法精確表示的,比如上面代碼中 1.15,我們可以通過 format() 來看看它在計(jì)算機(jī)內(nèi)部更加具體的數(shù)值:
In [16]: format(1.15,".51f") Out[16]: '1.149999999999999911182158029987476766109466552734375'
看到這個(gè)結(jié)果,我們就恍然大悟,為什么看到的結(jié)果會(huì)是1.1了。
但是接下來,可能會(huì)更加的困惑,因?yàn)閷?duì)于 0.5 來說,是完全可以直接轉(zhuǎn)為二進(jìn)制表示的。但是round(0.5)結(jié)果卻為0?這是因?yàn)?round() 的工作原理為:對(duì)于 round(number[, ndigits]),如果 number 可以被正常處理,則它的值會(huì)被舍入到最接近的 10 的負(fù) ndigits 次冪的倍數(shù)上,對(duì)于與兩個(gè)倍數(shù)的差值(差值的絕對(duì)值)均相等的情況,則會(huì)選擇兩個(gè)倍數(shù)中的偶數(shù)。
# 最接近的10的負(fù)0次冪的倍數(shù)為0、1,并與0、1差值的絕對(duì)值相同,選擇偶數(shù)0 >>> round(0.5) 0 # 最接近的10的負(fù)2次冪的倍數(shù)為0.12、0.13,并與0.12、0.13的差值的絕對(duì)值相同,選擇偶數(shù)0.12 >>> round(0.125, 2) 0.12 # 最接近的10的負(fù)2次冪的倍數(shù)為0.13 >>> round(0.12548828125, 2) 0.13
這個(gè)規(guī)則,用我們熟悉的話來說即為“ 四舍六入五成雙 ”。
使用decimal進(jìn)行浮點(diǎn)數(shù)的精確計(jì)算
那我們?cè)?Python 中怎么進(jìn)行精確的浮點(diǎn)數(shù)計(jì)算呢,Python 標(biāo)準(zhǔn)庫為我們提供了decimal 這個(gè)模塊來解決這個(gè)問題,decimal 常用于需要精確處理浮點(diǎn)數(shù)的場(chǎng)合,比如銀行賬戶金額、貨幣加減等。
In [17]: from decimal import Decimal In [18]: 0.1-0.09 Out[18]: 0.010000000000000009 In [19]: Decimal('0.1')-Decimal('0.09') Out[19]: Decimal('0.01')
同樣,我們可以使用它來查看對(duì)于不能精確表示的浮點(diǎn)數(shù)在計(jì)算機(jī)內(nèi)部的具體數(shù)值:
In [20]: Decimal.from_float(1.1) Out[20]: Decimal('1.100000000000000088817841970012523233890533447265625') In [21]: Decimal.from_float(0.1) Out[21]: Decimal('0.1000000000000000055511151231257827021181583404541015625')
這樣就可以解決我們的困惑與問題啦~
補(bǔ)充:python做浮點(diǎn)數(shù)運(yùn)算時(shí)的坑記錄
很顯然,這個(gè)計(jì)算結(jié)果是不對(duì)的,而且偏離實(shí)際值十分遠(yuǎn)。。。。。。。。
太坑人了這。
本來想自動(dòng)截取計(jì)算得到的圖片尺寸,但是這計(jì)算結(jié)果,坑害了半天的查找錯(cuò)誤過程!?。?!
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
- 淺談python浮點(diǎn)數(shù)比較的三種方法
- python中精確的浮點(diǎn)數(shù)運(yùn)算示例
- python 如何將浮點(diǎn)數(shù)尾部無效0去掉和無效的‘.’號(hào)
- python 工具 字符串轉(zhuǎn)numpy浮點(diǎn)數(shù)組的實(shí)現(xiàn)
- Python浮點(diǎn)數(shù)四舍五入問題的分析與解決方法
- Python判斷字符串是否為字母或者數(shù)字(浮點(diǎn)數(shù))的多種方法
- python十進(jìn)制和二進(jìn)制的轉(zhuǎn)換方法(含浮點(diǎn)數(shù))
- python中實(shí)現(xiàn)精確的浮點(diǎn)數(shù)運(yùn)算詳解
- python中浮點(diǎn)數(shù)比較判斷!為什么不能用==(推薦)
相關(guān)文章
Python統(tǒng)計(jì)日志中每個(gè)IP出現(xiàn)次數(shù)的方法
這篇文章主要介紹了Python統(tǒng)計(jì)日志中每個(gè)IP出現(xiàn)次數(shù)的方法,實(shí)例分析了Python基于正則表達(dá)式解析日志文件的相關(guān)技巧,需要的朋友可以參考下2015-07-07python自動(dòng)12306搶票軟件實(shí)現(xiàn)代碼
這篇文章主要為大家詳細(xì)介紹了python自動(dòng)12306搶票軟件的實(shí)現(xiàn)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02Python matplotlib實(shí)現(xiàn)散點(diǎn)圖的繪制
Matplotlib作為Python的2D繪圖庫,它以各種硬拷貝格式和跨平臺(tái)的交互式環(huán)境生成出版質(zhì)量級(jí)別的圖形。本文將利用Matplotlib庫繪制散點(diǎn)圖,感興趣的可以了解一下2022-03-03Python全局鎖中如何合理運(yùn)用多線程(多進(jìn)程)
這篇文章主要介紹了Python全局鎖中如何合理運(yùn)用多線程(多進(jìn)程),需要的朋友可以參考下2019-11-11使用python實(shí)現(xiàn)簡(jiǎn)單去水印功能
這篇文章主要為大家詳細(xì)介紹了使用python實(shí)現(xiàn)簡(jiǎn)單去水印功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05