Python 也會欺騙你:浮點數的誤差

--

當你在 Python 進行小數點的計算時,也許會發現一些奇怪的事:常常算出來的結果,跟你自己預期的就是有那麼一點點點誤差。這其實是因為浮點數換算所導致的結果,但其中的細節對初學者來說總是覺得奧妙,讓我們來一次說清楚。

你覺得 0.1+0.2 會是多少?答案應該是 0.3,對吧?我們來問問 Python 的看法。

在互動視窗試著執行下面這行運算式:

>>>0.1+0.2
0.30000000000000004

哪尼!是只有差「一點點....點」,但怎麼會這樣?到底發生什麼事?難不成是 Python 有甚麼 Bug ?

不,這可不是 Bug。這是一個浮點數表示誤差(floating-point representation error),發生這個問題的原因和 Python 完全無關,而是浮點數在電腦記憶體內儲存的方式造成的。

你應該知道並非所有數字都可以用小數點表示出來。小數 0.1 我們可以用分數表示為 1/10,0.1和1/10都是十進位(decimal)表示法。但若是分數 1/3 也用十進位的小數來表示,則會是一個無限小數,也就是 1/3=0.3333…,小數點後有無限多個 3。

若你將分數 1/10 改以二進位來表示,就會發生同樣的狀況,結果也是個無限小數 (0011一直循環):

0.00011001100110011001100110011... # 二進位

然而電腦是以二進位(binary)表示法來儲存浮點數的。電腦的記憶體有限,只能盡可能儲存 1/10 的近似值。既然儲存的是近似值,轉換回十進位就一定不會剛好是 0.1,通常會略高於實際值,像是這樣:

0.1000000000000000055511151231257827021181583404541015625

如果你以為事情快水落石出了,別那麼樂觀。你可以在 Python 輸入 0.1 試試看,此時 Python 印出的並不是上面那一大串近似值,而是......對就是 0.1:

>>>0.1
0.1

Python 並不是簡單的覺得後面的位數很累贅、直接砍掉而已,實際發生的事情還有更多細節。

雖然 0.1 在二進位的近似值就是剛剛提到的那一大串小數,但其實附近一小段範圍內的十進位數字,也會對應到相同的二進位近似值,例如,0.1 和0.10000000000000001 的二進位近似值就是同一個,可以在互動視窗測試看看:

>>>0.10000000000000001
0.1

當系統實際儲存的二進位近似值,可以對應到很多不同的十進位數字的時候,Python 只會選擇最短的十進位數來當結果顯示。

這就解釋了最開始的例子,0.1 + 0.2 不等於 0.3 的原因。Python會把 0.1 和 0.2 各自的二進位近似值加起來,計算出來自然也是近似值,而這個近似值對應到的十進位數不是 0.3,而是 0.30000000000000004。類似的狀況還有:

>>>0.1 + 0.7
0.7999999999999999
>>>0.2 + 0.4
0.6000000000000001

但也不是所有浮點數的計算都會有誤差,你可以試試 0.1 + 0.1 或 0.2 + 0.2,結果應該就會符合你的預期。

如果這些問題開始讓你頭暈腦脹,也不用太擔心。因為除非你要寫的是金融或科學運算的程式,不然不太需要擔心浮點運算的不精確,只要大概知道背後原因就可以。

Python 中有滿多這類有趣的小秘辛,若想深入研究 Python 程式的細節,推薦你參考旗標科技《Real Python 人氣站長教你動手寫程式 — 不說教也能心領神會的引導式實作課》一書。

--

--

施威銘研究室

致力開發AI領域的圖書、創客、教具,希望培養更多的AI人才。整合各種人才,投入創客產品的開發,推廣「實作學習」,希望實踐學以致用的理想。