浮動小数点の誤差と対処法(isclose, Decimal)|Python
Python の float は IEEE 754 倍精度浮動小数点数であり、10 進小数を 2 進数で近似している。そのため、人間には単純に見える計算でも誤差が生じることがある。この誤差を理解し、適切に対処することはプログラマにとって重要なスキルだ。
誤差が生じる例
0.1 は 10 進数では有限小数だが、2 進数では無限小数になる。そのため、メモリ上では近似値として保存される。
print(0.1 + 0.1 + 0.1) # 0.30000000000000004
print(0.1 + 0.2) # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # Falseこの結果は直感に反するが、浮動小数点数の仕組みを知れば納得できる。0.1 を 2 進数に変換すると 0.0001100110011... と循環小数になり、有限ビットでは打ち切らざるを得ない。
内部表現の確認
float.hex() で 16 進浮動小数点表記を確認できる。
print((0.1).hex()) # 0x1.999999999999ap-4
print((0.2).hex()) # 0x1.999999999999ap-3
print((0.3).hex()) # 0x1.3333333333333p-2末尾の 999... が循環を示唆している。0.1 + 0.2 の結果と 0.3 は内部表現がわずかに異なる。
等価比較の問題
浮動小数点数同士を == で比較すると、誤差により意図しない結果になることがある。
# 計算結果が期待値と一致しない
result = 0.1 * 3
expected = 0.3
print(result == expected) # False
# ループでも問題が起きる
x = 0.0
while x != 1.0:
x += 0.1
if x > 1.5: # 安全のため脱出条件を追加
print('無限ループを回避')
breakx が正確に 1.0 にならないため、x != 1.0 が永遠に真になる可能性がある。
math.isclose() で許容誤差を指定
math.isclose() は 2 つの数値が「十分に近い」かどうかを判定する。相対許容誤差(rel_tol)と絶対許容誤差(abs_tol)を指定できる。
import math
a = 0.1 + 0.2
b = 0.3
print(a == b) # False
print(math.isclose(a, b)) # Trueデフォルトでは rel_tol=1e-9(相対誤差 10 億分の 1)、abs_tol=0.0 が設定されている。
import math
# 相対許容誤差を指定
print(math.isclose(1000.0, 1000.001, rel_tol=1e-5)) # True
print(math.isclose(1000.0, 1000.001, rel_tol=1e-7)) # False
# 絶対許容誤差を指定(0 に近い値の比較に有用)
print(math.isclose(0.0, 1e-10, abs_tol=1e-9)) # True
print(math.isclose(0.0, 1e-10, abs_tol=1e-11)) # Falseabs_tol は 0 に近い値を比較するときに重要だ。相対誤差だけでは、0 との比較で常に False になってしまう。
対処法 1:整数で計算して最後に変換
金額計算など、小数点以下の桁数が決まっている場合は、整数(円やセントの最小単位)で計算し、表示時にだけ小数に変換する方法がある。
# 悪い例:浮動小数点で計算
price = 19.99
quantity = 3
total = price * quantity
print(total) # 59.970000000000006
# 良い例:整数(セント単位)で計算
price_cents = 1999
quantity = 3
total_cents = price_cents * quantity
total_dollars = total_cents / 100
print(f'${total_dollars:.2f}') # $59.97対処法 2:Decimal を使う
decimal.Decimal は 10 進数を正確に表現できる。詳細は別記事「高精度な小数計算(decimal.Decimal)」を参照。
from decimal import Decimal
a = Decimal('0.1') + Decimal('0.2')
b = Decimal('0.3')
print(a == b) # True金融計算や会計処理では Decimal を使うべきだ。
対処法 3:Fraction を使う
有理数の範囲であれば fractions.Fraction で誤差なく計算できる。詳細は別記事「分数で正確に計算する(fractions.Fraction)」を参照。
from fractions import Fraction
a = Fraction(1, 10) + Fraction(2, 10)
b = Fraction(3, 10)
print(a == b) # True対処法の使い分け
浮動小数点数のまま「だいたい等しい」を判定したいとき。科学計算や物理シミュレーション向き。
10 進小数を正確に扱いたいとき。金額計算、会計処理、為替計算など。
分数のまま正確に計算したいとき。数学的な厳密性が必要な場面。
小数点以下の桁数が固定されているとき。最小単位に換算して整数で計算する。
誤差が問題にならないケース
すべての場面で浮動小数点数を避ける必要はない。以下のような場合は float で十分だ。
重要なのは、浮動小数点数の誤差を理解した上で、用途に応じた適切な型を選ぶことだ。












