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

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

浮動小数点数floatの誤差 2つの値が近似しているか判定: math.isclose() 許容誤差を指定: 引数rel_tol, abs_tol 浮動小数点数floatを0と比較する場合は注意

浮動小数点数floatを2進数表記や16進数表記に変換したい場合は

浮動小数点数floatの誤差

浮動小数点数floatはコンピュータの内部では2進数で表現されているため、10進数の小数と厳密に同じ値を表現できません。 例えば0.1をprint()で出力すると0.1と表示されるが、これは扱いやすい範囲に桁数が丸められているから。format()で表示桁数を増やすと実際は誤差を含んでいることが分かる。

print(0.1)
# 0.1

print(format(0.1, '.20f'))
# 0.10000000000000000555

より詳しい説明はPythonの公式ドキュメントを参照。

  1. 浮動小数点演算、その問題と制限 — Python 3.7.2 ドキュメント

多くの場合、そのような誤差を気にする必要はないが、浮動小数点数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()を使うとシンプルに書けます。

math.isclose() --- 数学関数 — Python 3.7.2 ドキュメント

Python3.5で追加された関数なのでそれより前のバージョンでは使えない。 以下のように、第一引数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を指定する必要があります。

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
Last Updated: 6/26/2019, 10:34:03 PM