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 的一樣。在你有選擇的情況下,對於任何精度損失會累積疊加的運算,都優先使用整數運算。