浮動小数点数と等号の比較が丸めと精度の問題のために少々手間がかかることはよく知られています。
たとえば、次のようになります。 https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
Pythonでこれに対処するための推奨方法は何ですか?
きっとこれのための標準ライブラリ関数はどこかにありますか?
Python 3.5では PEP 485 で説明されているように math.isclose
とcmath.isclose
関数 が追加されています。
以前のバージョンのPythonを使用している場合は、同等の機能が ドキュメント にあります。
def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
rel_tol
は相対的な許容誤差であり、2つの引数のうち大きい方の値で乗算されます。値が大きくなるにつれて、まだ等しいと見なしながら、許容差も大きくなります。
abs_tol
は、すべての場合にそのまま適用される絶対許容誤差です。差がこれらの許容値のどちらよりも小さい場合、値は等しいと見なされます。
次のような単純なものでは不十分ですか。
return abs(f1 - f2) <= allowed_error
私は、Garethの答えがおそらく軽量の機能/解決策として最も適切であることに同意するでしょう。
しかし、私はあなたがNumPyを使っているか、それを考えているなら、これのためにパッケージ化された関数があることに注意することは有益であると思いました。
numpy.isclose(a, b, rtol=1e-05, atol=1e-08, equal_nan=False)
ちょっとした免責事項:NumPyをインストールすることはあなたのプラットフォームによっては自明でない経験になることがあります。
Pythonの decimal
モジュールを使用してください。これはDecimal
クラスを提供します。
コメントから:
あなたが数学を多用する仕事をしていて、あなたが絶対的に10進数からの精度を必要としないなら、これは本当に物事を台無しにすることができることは注目に値する。フロートは、やり方、やり方が速いですが、不正確です。小数は非常に正確ですが遅いです。
私は、DawsonのAlmostEqual2sComplement
関数を実装するPython標準ライブラリ(または他の場所)には何も知りません。それがあなたが望む種類の振る舞いなら、あなたは自分でそれを実装しなければなりません。 (その場合、Dawsonの巧妙なビット単位のハックを使用するよりも、おそらくif abs(a-b) <= eps1*(abs(a)+abs(b)) + eps2
または同様の形式の従来のテストを使用した方がよいでしょう。Dawsonのような動作を得るには、if abs(a-b) <= eps*max(EPS,abs(a),abs(b))
いくつかの小さな固定EPS
;については、これはDawsonとまったく同じではありませんが、精神的には似ています。
浮動小数点数が等しいかどうか比較できないという一般的な見解は不正確です。浮動小数点数は整数と違いはありません: "a == b"を評価すると、それらが同一の数であれば真となり、そうでなければ偽となります(2つのNaNはもちろん同一の数ではないことを理解して)。
実際の問題はこれです:私がいくつかの計算をした、そして私が比較しなければならない2つの数が正確に正しいという確信がないならば、それから何?この問題は、整数の場合と同じように浮動小数点の場合も同じです。整数式 "7/3 * 3"を評価しても、 "7 * 3/3"と等しいとは比較されません。
それでは、「整数を等しいかどうか比較するにはどうすればよいですか」と尋ねたとします。そのような状況では。唯一の答えはありません。何をすべきかは、具体的な状況、特にどのようなエラーがあり、何を達成したいのかによって異なります。
ここにいくつかの可能な選択肢があります。
数学的に正確な数が等しい場合に「真」の結果を得たい場合は、実行した計算のプロパティを使用して、2つの数で同じエラーが発生することを証明します。それが実行可能であり、正確に計算された場合に等しい数を与える式から生じる2つの数を比較すると、その比較から "true"が得られます。もう1つの方法は、計算の特性を分析して、誤差が特定の量、おそらく絶対量または入力の1つまたは出力の1つを基準とした量を超えないことを証明することです。その場合、2つの計算された数が最大でその量だけ異なるかどうかを尋ねることができ、それらが間隔内にある場合は「true」を返します。エラー範囲を証明できない場合は、推測して最善の結果を期待することができます。推測の1つの方法は、多数の無作為標本を評価し、結果にどのような分布があるかを確認することです。
もちろん、数学的に正確な結果が等しい場合にのみ「true」になるという要件を設定しているので、たとえそれらが等しくなくても「true」になる可能性を残しました。 (実際、常に "true"を返すことで要件を満たすことができます。これにより計算は単純になりますが、一般的には望ましくないので、以下で状況の改善について説明します。)
数学的に正確な数が等しくない場合に「偽」の結果を得たい場合は、数学的に正確な数が等しくない場合に数値の評価が異なる数になることを証明する必要があります。これは多くの一般的な状況で実用的な目的のために不可能かもしれません。それでは、代替案を考えてみましょう。
数学的に正確な数が一定量を超えて異なっているならば、我々が「偽の」結果を得るという有用な要件はあるかもしれません。たとえば、コンピュータゲームで投げられたボールがどこを移動したかを計算し、それがバットを打ったかどうかを知りたいとします。この場合、ボールがバットに当たった場合は必ず「true」になり、ボールがバットから離れている場合は「false」になります。ボールがバットから離れている場合は誤った「true」の回答を受け入れることができます。数学的に正確なシミュレーションはバットを逃したが、バットを打ってから1ミリメートル以内です。その場合、私たちはボールの位置とバットの位置の計算がせいぜい1ミリメートルの合計誤差(興味のあるすべての位置に対して)を持っていることを証明(または推測/推定)する必要があります。これにより、ボールとバットが1ミリメートル以上離れている場合は常に "false"が返され、接触している場合は "true"が返され、許容できるほど近い場合は "true"が返されます。
したがって、浮動小数点数を比較するときに何を返すかを決定する方法は、特定の状況によって大きく異なります。
計算の誤差範囲を証明する方法については、それが複雑な問題になる可能性があります。丸め - 最近接モードでIEEE 754規格を使用する浮動小数点実装は、基本演算(特に乗算、除算、加算、減算、平方根)の正確な結果に最も近い浮動小数点数を返します。 (tie、roundの場合は下位ビットが偶数になるようにしてください。)(平方根と除算には特に注意してください。あなたの言語実装はIEEE 754に準拠していないメソッドを使用するかもしれません。)単一の結果の誤差は、最下位ビットの値の1/2以下です。 (それ以上の場合は、丸めは値の1/2以内の異なる数値になります。)
そこから先へ進むと、かなり複雑になります。次のステップでは、入力の1つにすでにエラーがある操作を実行します。単純な表現では、これらの誤差は計算によって追跡され、最終的な誤差の限界に達することがあります。実際には、これは質の高い数学ライブラリで作業するなど、いくつかの状況でのみ行われます。そしてもちろん、どの操作を実行するかを正確に制御する必要があります。高水準言語は多くの場合コンパイラにゆるみを与えますので、どの順序で操作が実行されるのかわからないかもしれません。
この話題についてもっと多くのことが書かれている可能性がありますが、それをやめる必要があります。要約すると、答えは次のとおりです。ライブラリルーチンに入れる価値があるほとんどのニーズを満たす単一のソリューションがないため、この比較のためのライブラリルーチンはありません。 (相対または絶対エラー間隔と比較しても十分な場合は、ライブラリルーチンを使用せずに簡単に実行できます。)
テスト/ TDDのコンテキストでこれを使用したい場合は、これが標準的な方法だと思います。
from nose.tools import assert_almost_equals
assert_almost_equals(x, y, places=7) #default is 7
以下の比較が参考になりました。
str(f1) == str(f2)
math.isclose() は、そのためにPython 3.5に 追加 されました( ソースコード )。これはPython 2への移植です。Mark Ransomのワンライナーとの違いは、 "inf"と "-inf"を適切に処理できることです。
def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
'''
Python 2 implementation of Python 3.5 math.isclose()
https://hg.python.org/cpython/file/tip/Modules/mathmodule.c#l1993
'''
# sanity check on the inputs
if rel_tol < 0 or abs_tol < 0:
raise ValueError("tolerances must be non-negative")
# short circuit exact equality -- needed to catch two infinities of
# the same sign. And perhaps speeds things up a bit sometimes.
if a == b:
return True
# This catches the case of two infinities of opposite sign, or
# one infinity and one finite number. Two infinities of opposite
# sign would otherwise have an infinite relative tolerance.
# Two infinities of the same sign are caught by the equality check
# above.
if math.isinf(a) or math.isinf(b):
return False
# now do the regular computation
# this is essentially the "weak" test from the Boost library
diff = math.fabs(b - a)
result = (((diff <= math.fabs(rel_tol * b)) or
(diff <= math.fabs(rel_tol * a))) or
(diff <= abs_tol))
return result
ソース数の表現に影響を与えることができるいくつかのケースでは、整数分子と分母を使用して、浮動小数点数の代わりに分数として表現することができます。そうすれば、正確な比較ができます。
詳細については、fractionモジュールの Fraction を参照してください。
2つの数が「精度まで」同じであることを確認したい場合に便利です。許容誤差を指定する必要はありません。
2つの数の最小精度を見つける
両方とも最小精度に丸めて比較する
def isclose(a,b):
astr=str(a)
aprec=len(astr.split('.')[1]) if '.' in astr else 0
bstr=str(b)
bprec=len(bstr.split('.')[1]) if '.' in bstr else 0
prec=min(aprec,bprec)
return round(a,prec)==round(b,prec)
書かれているように、文字列表現に 'e'が付いていない数字に対してのみ機能します(0.9999999999995e-4 <number <= 0.9999999999995e11を意味します)。
例:
>>> isclose(10.0,10.049)
True
>>> isclose(10.0,10.05)
False
私は@Sesquipedalの提案が好きでしたが修正しました(両方の値が0のときの特別な使用例はFalseを返します)。私の場合は、Python 2.7を使い、単純な関数を使いました。
if f1 ==0 and f2 == 0:
return True
else:
return abs(f1-f2) < tol*max(abs(f1),abs(f2))
atol/rtol
なしで与えられた10進数と比較するには:
def almost_equal(a, b, decimal=6):
return '{0:.{1}f}'.format(a, decimal) == '{0:.{1}f}'.format(b, decimal)
print(almost_equal(0.0, 0.0001, decimal=5)) # False
print(almost_equal(0.0, 0.0001, decimal=4)) # True
これはちょっと醜いハックかもしれませんが、デフォルトのfloat精度(約11小数桁)以上を必要としない場合はかなりうまくいきます。 Python 2.7でうまく動作します。
round_to関数は、組み込みstrから フォーマット方法 を使用します。 floatを必要な小数点以下の桁数で表す文字列にfloatを切り上げ、次に丸めたfloat文字列に eval 組み込み関数を適用してfloat数値型に戻すクラス.
is_close関数は、切り上げられたfloatに単純な条件を適用するだけです。
def round_to(float_num, decimal_precision):
return eval("'{:." + str(int(decimal_precision)) + "f}'.format(" + str(float_num) + ")")
def is_close(float_a, float_b, decimal_precision):
if round_to(float_a, decimal_precision) == round_to(float_b, decimal_precision):
return True
return False
a = 10.0 / 3
# Result: 3.3333333333333335
b = 10.0001 / 3
# Result: 3.3333666666666666
print is_close(a, b, decimal_precision=4)
# Result: False
print is_close(a, b, decimal_precision=3)
# Result: True