Pythonで浮動小数点数floatの誤差を考慮して比較(math.isclose)
浮動小数点数float
はコンピュータの内部では2進数で表現されているため、10進数の小数と厳密に同じ値を表現できない。このため、特にif文の条件式で浮動小数点数float
を比較して判定する場合などに想定外の結果となることがある。
浮動小数点数float
を2進数表記や16進数表記に変換したい場合は以下の記事を参照。
NumPyの関数np.isclose()
, np.allclose()
については以下の記事を参照。
浮動小数点数floatの誤差
浮動小数点数float
はコンピュータの内部では2進数で表現されているため、10進数の小数と厳密に同じ値を表現できない。
例えば0.1
をprint()
で出力すると0.1
と表示されるが、これは扱いやすい範囲に桁数が丸められているから。format()
で表示桁数を増やすと実際は誤差を含んでいることが分かる。
print(0.1)
# 0.1
print(format(0.1, '.20f'))
# 0.10000000000000000555
より詳しい説明はPythonの公式ドキュメントを参照。
多くの場合、そのような誤差を気にする必要はないが、浮動小数点数float
を演算した結果を比較して判定したい場合などに想定外の結果となることがある。
print(0.1 + 0.1 + 0.1)
# 0.30000000000000004
print(0.1 + 0.1 + 0.1 == 0.3)
# False
print((19 / 155) * (155 / 19))
# 0.9999999999999999
print((19 / 155) * (155 / 19) == 1)
# False
このような場合、round()
で結果を丸めてから比較したり、abs()
で差分の絶対値をとって適当な小さい値と比較する方法が考えられる。
print(round(0.1 + 0.1 + 0.1, 10) == round(0.3, 10))
# True
print(abs((0.1 + 0.1 + 0.1) - 0.3) < 1e-10)
# True
なお、1e<数字>
は浮動小数点数float
の指数表記。1e<数字>
は10
の<数字>
乗を表す。e
と書くがネイピア数(自然対数の底)ではないので注意。
print(1e5)
# 100000.0
print(1e-3)
# 0.001
2つの値が近似しているか判定: math.isclose()
上の例のような浮動小数点数float
の比較は、mathモジュールの関数isclose()
を使うとシンプルに書ける。
以下のように、第一引数a
と第二引数b
に指定した値が厳密に等価でなくても近似値であればTrue
を返す。
import math
print(math.isclose(0.1 + 0.1 + 0.1, 0.3))
# True
print(math.isclose((19 / 155) * (155 / 19), 1))
# True
許容誤差を指定: 引数rel_tol, abs_tol
どれくらいの誤差を許容するかを引数rel_tol
, abs_tol
で指定する。デフォルトはrel_tol=1e-9
, abs_tol=0.0
。
rel_tol
は相対的な許容差。許容する差を2つの数値の絶対値の大きい方に対する割合で指定する。
abs(a - b) <= rel_tol * max(abs(a), abs(b))
print(math.isclose(1, 1.001))
# False
print(math.isclose(1, 1.001, rel_tol=0.01))
# True
abs_tol
は絶対的な許容差。許容する差を絶対値で指定する。
abs(a - b) <= abs_tol
相対許容差と絶対許容差の大きい方との比較となる。
abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
0と比較する場合は注意が必要。例えばb=0
とすると相対許容差rel_tol
に対する判定式は以下のようになってしまうため、rel_tol=0
でない限りTrue
にならない。
abs(a) <= rel_tol * abs(a)
絶対許容差abs_tol
を指定する必要がある。デフォルトはabs_tol=0.0
なので注意。
print(math.isclose(0, 0.001))
# False
print(math.isclose(0, 0.001, rel_tol=0.01))
# False
print(math.isclose(0, 0.001, abs_tol=0.01))
# True
浮動小数点数floatを0と比較する場合は注意
上述のように、浮動小数点数float
の演算結果を0
と比較する場合は、==
はもちろんmath.isclose()
をデフォルトで使っても想定外の結果となる可能性があるので要注意。
浮動小数点数float
を2進数で表現することによる誤差のほか、例えば円周率πなどのようにそもそも誤差を含んでしまう無理数を扱った演算でも同じ。
print(math.sin(math.pi))
# 1.2246467991473532e-16
print(math.sin(math.pi) == 0)
# False
print(math.isclose(math.sin(math.pi), 0))
# False
math.isclose()
の引数abs_tol
を指定するほか、round()
で丸めて比較、0
ではなく極小の値と比較といった方法がある。
print(math.isclose(math.sin(math.pi), 0, abs_tol=1e-10))
# True
print(round(math.sin(math.pi), 10) == 0)
# True
print(abs(math.sin(math.pi)) < 1e-10)
# True