Pythonで浮動小数点数floatの誤差を考慮して比較(math.isclose)

Modified: | Tags: Python, 数値

浮動小数点数floatはコンピュータの内部では2進数で表現されているため、10進数の小数と厳密に同じ値を表現できない。このため、特にif文の条件式で浮動小数点数floatを比較して判定する場合などに想定外の結果となることがある。

浮動小数点数floatを2進数表記や16進数表記に変換したい場合は以下の記事を参照。

NumPyの関数np.isclose(), np.allclose()については以下の記事を参照。

浮動小数点数floatの誤差

浮動小数点数floatはコンピュータの内部では2進数で表現されているため、10進数の小数と厳密に同じ値を表現できない。

例えば0.1print()で出力すると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

関連カテゴリー

関連記事