2.39. 浮動小数点数を扱う¶
浮動小数点数は普通の小数のように見えますが、その挙動のいくつかは、これまで遭遇したことのない読者を驚かせます。そしてそうした挙動の1つは、デスクトップのPythonよりもMicroPythonでより顕著に現れます。このページでは、何を予期すべきか、そして黙って誤動作しない浮動小数点コードの書き方を取り上げます。
2.39.1. 精度¶
Pythonの float はIEEE 754のバイナリ浮動小数点数です。ほとんどのMicroPythonビルドでは 単精度 (32ビット)であるのに対し、デスクトップのCPythonは 倍精度 (64ビット)を使います。単精度は約7桁の十進精度を持ち、倍精度は約15桁を持ちます。
>>> 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 ではありません。代わりに 許容誤差チェック を使います。2つの浮動小数点数が同一かどうかではなく、十分に近いかどうかを問うのです。
if abs(a - b) < 1e-6:
# close enough
...
許容誤差の選び方は値のスケールに依存します。固定値の 1e-6 は数値が1のオーダー前後のときにうまく機能します。値が桁違いに変動するときは 相対的な 許容誤差の方が適しています。
math.isclose() は両方を一度に処理します。
from math import isclose
isclose(0.1 + 0.2, 0.3) # True
isclose(1.0e6 + 1, 1.0e6) # True (within default tolerance)
2つのキーワード引数が、どの種類の「近さ」を有効とみなすかを制御します。
rel_tol-- 相対 許容誤差、デフォルトは1e-9です。2つの値の差が大きい方の値のこの割合以内であれば一致とみなされます。任意のスケールにまたがる一般的な比較に適しています。abs_tol-- 絶対 許容誤差、デフォルトは0です。2つの値の差がこの固定量以内であれば一致とみなされます。
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. 累積による誤差¶
浮動小数点数の長い総和は、CPythonよりもMicroPythonでより速く精度を失います。すべての中間結果が32ビット精度に丸め戻されるためです。
total = 0.0
for _ in range(1000000):
total += 0.1
print(total) # noticeably off from 100000.0
精度が重要となる繰り返しの加算には、2つのパターンが役立ちます。
整数に累積する -- 値を整数にスケールできる場合は常に、秒の代わりにミリ秒で、ボルトの代わりにミリボルトで処理し、最後に一度だけ変換します。
より小さなバッチで計算する -- バッチの結果を合計し、各加算が同程度の大きさの値どうしの間で行われるようにします。
整数側にはそのような制限はありません。MicroPythonの整数は、CPythonと同様に任意精度です。選択肢がある場合は、精度の損失が積み重なるような処理には整数演算を優先してください。