1. 程式人生 > 其它 >Python中浮點數運算不正確問題(慎入)

Python中浮點數運算不正確問題(慎入)

技術標籤:少女的技術文章python

大家好,老 Amy 來了。之前就意識到一個問題,但是最近又有朋友提出來了,所以就想著乾脆記錄下來,分享給大家叭~

啥問題呢?請看題:
在這裡插入圖片描述
也就是說,需要大家計算1.1-1的值,很多朋友會說:“emmm…這還不簡單,玩我呢?不就是0.1嘛”

但是如果你用 python 去執行一下,會發現結果跟你想的不太一樣,如下圖:
在這裡插入圖片描述
這樣大家是不是發現了什麼問題?是的,浮點數在運算過程中並沒有保證完全精確,是什麼原因導致了這種現象呢?很多朋友就會竊喜:“這不就是 Python 的 bug 嘛~”

但實際上,這並不是 Python 中的 bug ,它和計算機硬體中如何處理浮點數有關。浮點數在計算機硬體中以二進位制的形式存在,但是我們現在看到的都是十進位制,而十進位制的浮點數不能都完全精確的表示為二進位制小數。

就比如說我們在十進位制數中無法用小數精確表示 1/3 一樣,在二進位制數中也無法用小數精確表示 1/10。顯然這樣子的說明並沒有十進位制中的 1/3 那麼直觀,接下來我們嘗試去計算一下二進位制中的 1/10 :

十進位制的整數位是二進位制的整數位,十進位制的小數位是二進位制數的小數位。那現在我們拿到0.1

  • 整數部分為0
  • 小數部分為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

有沒有發現?在二進位制下,1/10 是一個無限迴圈小數:0.00011001100110011…,顯然這樣的表示形式無法精確的表示浮點數,最終的結果是近似 1/10 。在使用 IEEE-754 浮點運算標準的計算機硬體上,Python 的浮點數對映為 IEEE-754 雙精度浮點數,共包含 53 位精度(這裡指的是二進位制),在這個範圍下,這個最接近 1/10 的結果是:

3602879701896397/2∗∗55

這表示在計算機硬體中,1/10 的真實十進位制數值為:
0.1000000000000000055511151231257827021181583404541015625

那如何進行精確的浮點數運算呢?有朋友提出四捨五入可以解決。那我們來仔細看一下四捨五入真的可以解決這個問題嗎?

四捨五入進行解決

在 python 中,使用 round(number[, ndigits]) 來進行四捨五入,其中 ndigits 表示保留幾位小數,預設為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

上面程式碼符合我們四捨五入的預期結果,但是不要著急,我們接著往下看:

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)會直接進為1.2,但是此時並沒有,而是變為了0。這是為什麼呢?

如果沒有上面對浮點數的瞭解,僅從表象上很難去解釋。我們已經知道了在計算機內部,對於一些浮點數是無法精確表示的,比如上面程式碼中 1.15,我們可以通過 format() 來看看它在計算機內部更加具體的數值:

In [16]: format(1.15,".51f")
Out[16]: '1.149999999999999911182158029987476766109466552734375'

看到這個結果,我們就恍然大悟,為什麼看到的結果會是1.1了。

但是接下來,可能會更加的困惑,因為對於 0.5 來說,是完全可以直接轉為二進位制表示的。但是round(0.5)結果卻為0?這是因為 round() 的工作原理為:對於 round(number[, ndigits]),如果 number 可以被正常處理,則它的值會被舍入到最接近的 10 的負 ndigits 次冪的倍數上,對於與兩個倍數的差值(差值的絕對值)均相等的情況,則會選擇兩個倍數中的偶數。

# 最接近的10的負0次冪的倍數為0、1,並與0、1差值的絕對值相同,選擇偶數0
>>> round(0.5) 
0
# 最接近的10的負2次冪的倍數為0.12、0.13,並與0.12、0.13的差值的絕對值相同,選擇偶數0.12
>>> round(0.125, 2)  
0.12
# 最接近的10的負2次冪的倍數為0.13
>>> round(0.12548828125, 2)  
0.13

這個規則,用我們熟悉的話來說即為“ 四捨六入五成雙 ”。

使用decimal進行浮點數的精確計算

那我們在 Python 中怎麼進行精確的浮點數計算呢,Python 標準庫為我們提供了decimal 這個模組來解決這個問題,decimal 常用於需要精確處理浮點數的場合,比如銀行賬戶金額、貨幣加減等。

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')

同樣,我們可以使用它來檢視對於不能精確表示的浮點數在計算機內部的具體數值:

In [20]: Decimal.from_float(1.1)
Out[20]: Decimal('1.100000000000000088817841970012523233890533447265625')

In [21]: Decimal.from_float(0.1)
Out[21]: Decimal('0.1000000000000000055511151231257827021181583404541015625')

這樣就可以解決我們的困惑與問題啦~哈哈哈想更多更好的學習python,可以關注微信公眾號<極客夜讀>奧