2.39. 使用浮點數

浮點數看起來像普通的小數,但其中有幾項行為會讓沒遇過它們的讀者感到意外 -- 而其中一項行為在 MicroPython 上比在桌面版 Python 上更為明顯。本頁說明該預期什麼,以及如何撰寫不會悄悄出錯的浮點數程式碼。

2.39.1. 精度

Python 的 float 是一個 IEEE 754 二進位浮點數。在大多數 MicroPython 版本上它是 單精度(32 位元),而桌面版 CPython 使用 雙精度(64 位元)。單精度約攜帶七位十進位有效數字;雙精度則約攜帶十五位。

>>> 0.1 + 0.2
0.3000000

>>> 1.0 / 3.0
0.3333333

>>> 1e30 * 1e30
inf

可表示的範圍在兩端也較窄:數量級大於約 3.4e38 的數字會溢位成 inf,而小於約 1.2e-38 的數字會捨入為零。

2.39.2. 比較浮點數

最常見的陷阱是用 == 來測試相等:

>>> 0.1 + 0.2 == 0.3
False

兩個運算式看起來都應該相等,但 0.1 + 0.2 的結果是最接近的可表示值,而它並不剛好等於 0.3。請改用 容差檢查 -- 詢問兩個浮點數是否夠接近,而非是否完全相同:

if abs(a - b) < 1e-6:
    # close enough
    ...

容差的選擇取決於數值的尺度。當數字落在 1 的數量級左右時,固定的 1e-6 效果良好;當數值跨越數個數量級而變化時,相對 容差則較佳。

math.isclose() 一次處理這兩種情況:

from math import isclose

isclose(0.1 + 0.2, 0.3)         # True
isclose(1.0e6 + 1, 1.0e6)       # True (within default tolerance)

兩個關鍵字引數控制哪一種「接近」算數:

  • rel_tol -- 相對 容差,預設 1e-9。如果兩個值的差距在兩者中較大者的這個比例之內,它們便視為相符。適用於跨任何尺度的一般比較。

  • abs_tol -- 絕對 容差,預設 0。如果兩個值的差距在這個固定量之內,它們便視為相符。

math.isclose() 只要任一容差被滿足就回傳 True。對大多數成對的非零數字而言,預設值都沒問題;陷阱在於其中一個值可能剛好是零的時候。相對容差檢查相當於「差距 ≤ rel_tol × 較大值」,而較大值是零,所以檢查永遠會失敗:

>>> isclose(0.0, 1e-12)
False

絕對容差檢查則沒有這種問題 -- 只要零是你可能拿來比較的值,就傳入一個 abs_tol

>>> isclose(0.0, 1e-12, abs_tol=1e-9)
True

2.39.3. 累加漂移

浮點數的長串加總在 MicroPython 上比在 CPython 上更快失去精度,因為每個中間結果都會被捨回 32 位元精度:

total = 0.0
for _ in range(1000000):
    total += 0.1

print(total)        # noticeably off from 100000.0

對於精度很重要的重複加總,有兩種模式可幫上忙:

  • 累加成整數 -- 只要值能被縮放成整數,就改以毫秒而非秒、或以毫伏而非伏特來運算,最後再一次轉換回來。

  • 以較小的批次計算 並把各批次結果加總,使每次加法都發生在數量級相近的值之間。

整數這一邊則沒有這種限制 -- MicroPython 的整數是任意精度的,就和 CPython 的一樣。在你有選擇的情況下,對於任何精度損失會累積疊加的運算,都優先使用整數運算。