ヒストリア284143 views
高校生物549842 views
小学社会308636 views
いろは2986023 views
数学講師2852771 views
高校倫理1433119 views
中学英語808712 views
小学算数1194618 views
高校物理158224 views
雑学1472593 views
Help
Tools

English

浮動小数点の誤差と対処法(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('無限ループを回避')
        break

x が正確に 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))  # False

abs_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

対処法の使い分け

math.isclose()

浮動小数点数のまま「だいたい等しい」を判定したいとき。科学計算や物理シミュレーション向き。

decimal.Decimal

10 進小数を正確に扱いたいとき。金額計算、会計処理、為替計算など。

fractions.Fraction

分数のまま正確に計算したいとき。数学的な厳密性が必要な場面。

整数演算

小数点以下の桁数が固定されているとき。最小単位に換算して整数で計算する。

誤差が問題にならないケース

すべての場面で浮動小数点数を避ける必要はない。以下のような場合は float で十分だ。

物理シミュレーション(誤差が結果に大きく影響しない)
機械学習(モデルの精度に比べて誤差は無視できる)
グラフィックス(画素レベルの誤差は人間に知覚できない)
統計計算(十分な桁数があれば問題ない)

重要なのは、浮動小数点数の誤差を理解した上で、用途に応じた適切な型を選ぶことだ。