Pythonでフロートをほぼ同等に比較するための最良の方法は何ですか?


332

丸めと精度の問題のために、浮動小数点数が等しいかどうかを比較するのは少し手間がかかることはよく知られています。

例えば: https //randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/

Pythonでこれに対処するための推奨される方法は何ですか?

確かにどこかにこれのための標準ライブラリ関数がありますか?


@tolomea:アプリケーションとデータ、および問題のドメインに依存しているため-そして、それはコードの1行だけです-なぜ「標準ライブラリ関数」があるのでしょうか?
S.Lott、2011

9
S.Lott @: 、all、、any それぞれ基本的にワンライナーであり、それらは単にライブラリで提供されていない、彼らは機能組み込みます。したがって、BDFLの理由はそれではありません。ほとんどの人が書く1行のコードは、かなり洗練されておらず、機能しないことがよくあります。これが、より良いものを提供する大きな理由です。もちろん、他の戦略を提供するモジュールは、それらがいつ適切であるかを説明する警告を提供する必要があります。数値分析は難しいです。言語設計者が通常、それを支援するツールを試さないことは大きな不名誉ではありません。maxmin
Steve Jessop、2011

@スティーブジェソップ。これらのコレクション指向の関数には、浮動小数点のようにアプリケーション、データ、および問題ドメインの依存関係がありません。したがって、「ワンライナー」は本当の理由ほど重要ではないことは明らかです。数値解析は困難であり、汎用言語ライブラリのファーストクラスの一部になることはできません。
S.Lott、2011

6
@ S.Lott:標準のPythonディストリビューションにXMLインターフェース用の複数のモジュールが付属していない場合は、おそらく同意するでしょう。明らかに、異なるアプリケーションが別のことをする必要があるという事実は、モジュールを基本セットに入れて何らかの方法でそれを行うことにはまったく障害がありません。確かに、たくさん再利用されるフロートを比較するトリックがあります。最も基本的なものは、指定された数のulpです。だから私は部分的にのみ同意します-問題は数値解析が難しいということです。原則として、Python それをいくらか簡単にするツールを提供することができます。誰も志願していないと思います。
Steve Jessop、2011

4
また、「結局のところ、コードを設計するのが難しい1行に要約できます」-それを適切に実行しても、まだ1行の場合は、モニターの幅が私のものよりも広いと思います;-)。とにかく、ほとんどのプログラマー(私を含む)がめったにそれを使用しないという意味で、領域全体がかなり特殊化されていると思います。難しいことと組み合わせると、ほとんどの言語のコアライブラリの「最も望まれる」リストのトップに到達することはありません。
Steve Jessop

回答:


325

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すべての場合にそのまま適用される絶対許容誤差です。差がこれらの許容誤差のいずれかより小さい場合、値は等しいと見なされます。


26
注記ときaまたはbnumpy arraynumpy.isclose作品。
dbliss、2015年

6
@marsh rel_tol相対許容誤差であり、2つの引数の大きさの大きい方が乗算されます。値が大きくなると、それらが等しいと見なしながら、それらの間の許容差が大きくなります。abs_tolある絶対許容値として、すべての場合に適用されます。差がこれらの許容誤差のいずれかより小さい場合、値は等しいと見なされます。
Mark Ransom

5
この答えの価値を減らさないように(それは良いものだと思います)、ドキュメントには「モジュロエラーチェックなど、関数は次の結果を返す...」とあります。つまり、isclose関数(上記)は完全な実装ではありません。
rkersh

5
古いスレッドを復活させたことをお詫びしますが、それはisclose常に保守的ではない基準に準拠していることを指摘する価値があるように思われました。その振る舞いは私には直観に反するので、私はそれだけに言及します。私が2つの基準を指定した場合、許容差が小さいほど許容差が大きくなることを常に期待します。
Mackieメッサー

3
もちろん、あなたはあなたの意見を受け取る権利がありますが、この振る舞いは私には完全に理にかなっています。定義では、相対許容誤差にゼロを掛けた値は常にゼロであるため、ゼロに「近い」ものはありません。
Mark Ransom 2017年

72

次のような単純なものは十分ではありませんか?

return abs(f1 - f2) <= allowed_error

8
私が提供したリンクが指摘しているように、減算は、事前に数値のおおよその大きさがわかっている場合にのみ機能します。
Gordon Wrigley

8
私の経験では、フロートを比較する最良の方法は次のとおりabs(f1-f2) < tol*max(abs(f1),abs(f2))です。このような相対的な許容誤差は、一般的に浮動小数点数を比較するための唯一の意味のある方法です。浮動小数点数は通常、小数第2位の丸め誤差の影響を受けます。
Sesquipedal 2015

2
それがうまくいかないかもしれない単純な例を追加するだけです:>>> abs(0.04 - 0.03) <= 0.01、それはをもたらしFalseます。私が使用するPython 2.7.10 [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
schatten 2015

3
@schattenは公平であるため、この例は特定の比較アルゴリズムよりもマシンのバイナリ精度/フォーマットに関係しています。システムに0.03を入力した場合、それは実際にはCPUに到達した数ではありません。
Andrew White

2
@AndrewWhiteの例は、abs(f1 - f2) <= allowed_error期待どおりに機能しないことを示しています。
2015

45

Garethの答えはおそらく軽量な関数/ソリューションとして最も適切であることに同意します。

しかし、NumPyを使用している場合、またはNumPyを検討している場合は、パッケージ化された関数があることに注意してください。

numpy.isclose(a, b, rtol=1e-05, atol=1e-08, equal_nan=False)

ただし、免責事項が少しあります。NumPyのインストールは、プラットフォームによっては重要な経験になる場合があります。


1
「プラットフォームによっては、numpyをインストールするのは簡単なことではありません。」...ええと?numpyをインストールするのは「簡単ではない」どのプラットフォームですか?どうしてそれを簡単なものにしたのですか?
John

10
@ジョン:Windows用の64ビットバイナリを入手するのは難しい。pipWindowsでnumpyを取得するのは難しい。
Ben Bolker、2015年

@Ternak:そうですが、一部の生徒はWindowsを使用しているため、この問題に対処する必要があります。
Ben Bolker、2015年

4
@BenBolker Pythonを使用したオープンデータサイエンスプラットフォームをインストールする必要がある場合、最善の方法はAnaconda continuum.io/downloads(pandas、numpyなど)をそのまま使用することです
jrovegno

Anacondaのインストールは簡単です
エンドリス

13

クラスdecimalを提供するPythonのモジュールを使用しますDecimal

コメントから:

数学を多用する作業を行っており、10進数の精度が絶対に必要ない場合は、これにより実際にパフォーマンスが低下する可能性があることに注意してください。フロートは処理速度が速いですが、不正確です。小数は非常に正確ですが遅いです。


11

私は、ドーソンの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。これは正確ではありません。ドーソンと同じですが、精神的には似ています。


私はあなたがここでやっていることを完全には理解していませんが、興味深いです。eps、eps1、eps2、EPSの違いは何ですか?
Gordon Wrigley

eps1そしてeps2、相対および絶対許容誤差を定義する:あなたはできるように準備しているab約によって異なるためにeps1、彼らがどのように大きな回プラスeps2eps単一の公差です。サイズの大きさは、サイズがそれよりも小さいと想定されているという条件の下で、それらの大きさを許容しa、それらの大きbさの数eps倍程度の違いを覚悟しEPSておく必要がありますEPS。あなたが取る場合はEPS、あなたの浮動小数点型の最小の非デノーマル値になるように、これは(ドーソンはのULPで許容値を測定しているため、2 ^#ビットの要因を除く)ドーソンのコンパレータと非常によく似ています。
Gareth McCaughan、2011

2
ちなみに、正しいことは常に実際のアプリケーションに依存するというS. Lottの意見に同意します。そのため、浮動小数点比較のニーズすべてに対応する単一の標準ライブラリー関数はありません。
Gareth McCaughan、2011

@ gareth-mccaughan Pythonの「浮動小数点型の非正規でない最小値」をどのように決定しますか?
Gordon Wrigley

このページdocs.python.org/tutorial/floatingpoint.htmlは、ほとんどすべてのpython実装がIEEE-754倍精度浮動小数点数を使用し、このページen.wikipedia.org/wiki/IEEE_754-1985は、ゼロに最も近い正規化された数値が±2 *であると述べています* −1022。
Gordon Wrigley

11

浮動小数点数が等しいかどうか比較できないという一般的な知識は不正確です。浮動小数点数は整数と同じです。 "a == b"を評価すると、それらが同じ数の場合はtrueになり、それ以外の場合はfalseになります(2つのNaNはもちろん同じ数ではないことを理解しています)。

実際の問題は次のとおりです。いくつかの計算を実行し、比較する必要がある2つの数値が正確に正しいかどうかわからない場合は、どうすればよいですか。この問題は、整数の場合と同様に、浮動小数点の場合にも同じです。整数式「7/3 * 3」を評価すると、「7 * 3/3」と比較されません。

それで、「整数を等しいかどうか比較するにはどうすればよいですか?」そのような状況で。単一の答えはありません。何をすべきかは、特定の状況、特にどのようなエラーがあり、何を達成したいかによって異なります。

可能な選択肢は次のとおりです。

数学的に正確な数値が等しい場合に「真」の結果を取得するには、実行する計算のプロパティを使用して、2つの数値で同じエラーが発生することを証明することができます。それが可能であり、正確に計算された場合に等しい数値を与える式から生じる2つの数値を比較すると、比較から「true」が得られます。別のアプローチは、計算のプロパティを分析し、エラーが特定の量、絶対量または入力または出力の1つに対する相対量を超えないことを証明することです。その場合、2つの計算された数値が最大でその量だけ異なるかどうかを確認し、それらが間隔内であれば「true」を返すことができます。エラー限界を証明できない場合は、あなたは推測して最高のものを期待するかもしれません。推測する1つの方法は、多数のランダムサンプルを評価し、結果にどのような分布が表示されるかを確認することです。

もちろん、数学的に正確な結果が等しい場合にのみ「true」を取得するという要件を設定しているため、それらが等しくなくても「true」を取得する可能性はあります。(実際には、常に「true」を返すことで要件を満たすことができます。これにより、計算は簡単になりますが、一般的には望ましくないため、以下の状況の改善について説明します。

数学的に正確な数値が等しくない場合に「偽」の結果を取得したい場合、数学的に正確な数値が等しくない場合に、数値の評価が異なる数値をもたらすことを証明する必要があります。これは、多くの一般的な状況での実用的な目的には不可能かもしれません。そこで、代替案を検討しましょう。

有用な要件は、数学的に正確な数値が一定量以上異なる場合に「偽」の結果が得られることです。たとえば、コンピューターゲームで投げられたボールがどこを移動したかを計算し、それがバットを打ったかどうかを知りたいとします。この場合、ボールがバットに当たった場合は必ず「true」を取得し、ボールがバットから遠い場合は「false」を取得します。ボールが入っている場合、誤った「true」の回答を受け入れることができます。数学的に正確なシミュレーションはコウモリを逃しましたが、コウモリを打ってから1ミリ以内です。その場合、ボールの位置とバットの位置の計算に、最大で1ミリの誤差(すべての対象位置)があることを証明(または推測/推定)する必要があります。これにより、常に戻ることができます」

したがって、浮動小数点数を比較するときに何を返すかを決定する方法は、特定の状況に大きく依存します。

計算の誤差範囲をどのように証明するかについては、複雑な問題になる可能性があります。最も近い丸めモードでIEEE 754標準を使用する浮動小数点の実装は、基本的な演算(特に、乗算、除算、加算、減算、平方根)の正確な結果に最も近い浮動小数点数を返します。(同数の場合、丸めるので下位ビットが偶数になります。)(平方根と除算には特に注意してください。言語の実装では、IEEE 754に準拠していないメソッドを使用する可能性があります。)この要件により、 1つの結果のエラーは、最下位ビットの値の最大で1/2です。(それ以上の場合、丸めは値の1/2以内の別の数値になります。)

そこから先に進むと、かなり複雑になります。次のステップは、入力の1つにすでにエラーがある操作を実行することです。単純な式の場合、これらのエラーは、最終的なエラーの限界に到達するための計算をたどることができます。実際には、これは、高品質の数学ライブラリで作業するなど、いくつかの状況でのみ行われます。そしてもちろん、実行する操作を正確に制御する必要があります。高水準言語は、コンパイラに多くの余裕を与えることが多いため、操作が実行される順序がわからない場合があります。

このトピックについて書かれている可能性がある(そして書かれている)ものはまだたくさんありますが、私はそこで止めなければなりません。要約すると、答えは次のとおりです。ライブラリルーチンに入れる価値のあるほとんどのニーズに適合する単一のソリューションがないため、この比較のためのライブラリルーチンはありません。(相対または絶対エラー間隔との比較で十分であれば、ライブラリルーチンなしで簡単に実行できます。)


3
上記のGareth McCaughanとの議論から、相対誤差と正しく比較すると、本質的には「abs(ab)<= eps max(2 * -1022、abs(a)、abs(b))」になりますが、これは私が説明するものではありません単純で、確かに私が自分で考え出したものではありません。また、Steve Jessopが指摘しているように、max、min、any、all(すべて組み込み)と同様の複雑さです。したがって、標準の数学モジュールで相対エラー比較を提供することは良い考えのようです。
Gordon Wrigley、

(7/3 * 3 == 7 * 3/3)は、PythonでTrueを評価します。
xApple 2013

@xApple:OS X 10.8.3でPython 2.7.2を実行して、と入力しました(7/3*3 == 7*3/3)。印刷したFalse
Eric Postpischil 2013

3
おそらくタイプするのを忘れましたfrom __future__ import division。そうしないと、浮動小数点数がなく、2つの整数が比較されます。
xApple 2013

3
これは重要な議論ですが、信じられないほど役に立ちません。
Dan Hulme、2015

6

それをテスト/ TDDコンテキストで使用したい場合、これは標準的な方法だと思います。

from nose.tools import assert_almost_equals

assert_almost_equals(x, y, places=7) #default is 7

5

math.isclose()がPython 3.5に追加されました(ソースコード)。これがPython 2への移植です。MarkRansomの1ライナーとの違いは、「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

2

次の比較が役立つことがわかりました。

str(f1) == str(f2)

それは興味深いですが、str(.1 + .2)== .3のため、あまり実用的ではありません
Gordon Wrigley

str(.1 + .2)== str(.3)はTrueを返します
Henrikh Kantuni 2016年

これはf1 == f2とどのように異なりますか?両方が近いが、精度のために依然として異なる場合、文字列表現も等しくありません。
MrMas 2017年

2
.1 + .2 == .3はFalseを返し、str(.1 + .2)== str(.3)はTrueを返します
Kresimir

4
Python 3.7.2では、str(.1 + .2) == str(.3)Falseを返します。唯一のPython 2.のために作品を上記の方法
Danibix

1

ソース番号の表現に影響を与える可能性のあるいくつかのケースでは、整数の分子と分母を使用して、浮動小数点数ではなく分数として表現できます。これにより、正確な比較を行うことができます。

参照分数を詳細については、分画モジュールから。


1

@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))

1

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 <数値<= 0.9999999999995e11)

例:

>>> isclose(10.0,10.049)
True
>>> isclose(10.0,10.05)
False

無限のクローズという概念はうまく機能しません。isclose(1.0, 1.1)を生成Falseしてisclose(0.1, 0.000000000001)返しますTrue
kfsone

1

なしで与えられた小数まで比較するにはatol/rtol

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 

1

これは少々見苦しいハックかもしれませんが、デフォルトの浮動小数点精度(約11桁)を必要としない場合はかなりうまくいきます。

round_to機能は使用フォーマット方法を必要と小数点以下の桁数を有するフロートを表す文字列にフロートを切り上げするSTRクラスに組み込み、次いで適用の評価を組み込みの機能を取り戻すために丸みを帯びたフロート文字列にfloat数値型に変換します。

is_close機能は、単に丸いアップfloatに簡単な条件を適用します。

def round_to(float_num, prec):
    return eval("'{:." + str(int(prec)) + "f}'.format(" + str(float_num) + ")")

def is_close(float_a, float_b, prec):
    if round_to(float_a, prec) == round_to(float_b, prec):
        return True
    return False

>>>a = 10.0
10.0
>>>b = 10.0001
10.0001
>>>print is_close(a, b, prec=3)
True
>>>print is_close(a, b, prec=4)
False

更新:

@stepehjfoxで提案されているように、「eval」を回避するrount_to関数を作成するより明確な方法は、ネストされたフォーマットを使用することです。

def round_to(float_num, prec):
    return '{:.{precision}f}'.format(float_num, precision=prec)

同じ考え方に従って、すばらしい新しいf文字列(Python 3.6+)を使用すると、コードはさらにシンプルになります。

def round_to(float_num, prec):
    return f'{float_num:.{prec}f}'

そのため、すべてを1つのシンプルでクリーンな「is_close」関数にまとめることもできます。

def is_close(a, b, prec):
    return f'{a:.{prec}f}' == f'{b:.{prec}f}'

1
eval()パラメータ化されたフォーマットを取得するために使用する必要はありません。何か return '{:.{precision}f'.format(float_num, precision=decimal_precision) がそれを行う必要があります
stephenjfox

1
私のコメントのソースとその他の例: pyformat.info/#param_align
stephenjfox

1
@stephenjfoxに感謝します。入れ子になった書式設定については知りませんでした。ところで、サンプルコードには終了中括弧がありません。return '{:.{precision}}f'.format(float_num, precision=decimal_precision)
Albert Alomar

1
良いキャッチ、そして特にf-stringsでよく行われた拡張。Python 2の死が近づいているので、おそらくこれが標準になるでしょう
間もなく死ぬので

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.