浮動小数点の比較はどのようにすればよいですか?


85

私は現在、次のようなコードを書いています。

そして他の場所では私は平等をする必要があるかもしれません:

つまり、浮動小数点演算がたくさん行われているので、条件についてさまざまな比較を行う必要があります。この文脈ではそのようなことは無意味なので、私はそれを整数数学に変換することはできません。

このようなことが起こる可能性があるため、浮動小数点の比較は信頼できない可能性があることを前に読みました。

要するに、私は知りたいのです:どうすれば浮動小数点数(より小さい、より大きい、等しい)を確実に比較できますか?

私が使用している番号の範囲はおおよそ10E-14から10E6なので、大きい数だけでなく小さい数でも作業する必要があります。

使用している言語に関係なく、これをどのように達成できるかに興味があるため、これを言語に依存しないものとしてタグ付けしました。


浮動小数点数を使用する場合、これを確実に行う方法はありません。実際には等しくないが、コンピューターの場合は常に等しい数があり(たとえば、1E + 100、1E + 100 + 1)、通常、実際には等しいのにコンピューターの場合は等しくないという計算結果もあります(を参照)。 nelhageの回答へのコメントの1つ)。2つのうちどちらを少なくしたいかを選択する必要があります。
toochin 2011

一方、たとえば有理数のみを扱う場合は、整数に基づいて有理数演算を実装すると、2つの数値の一方をもう一方の数値にキャンセルできる場合、2つの数値は等しいと見なされます。
toochin 2011

さて、現在私はシミュレーションを行っています。私が通常これらの比較を行っている場所は、可変の時間ステップに関連しています(いくつかの頌歌を解決するため)。あるオブジェクトの指定されたタイムステップが別のオブジェクトのタイムステップと等しいか、小さいか、大きいかを確認する必要がある場合がいくつかあります。
マイクベイリー


なぜ配列を使わないのですか?stackoverflow.com/questions/28318610/…–
Adrian P.

回答:


69

フロート/倍精度の制限の端で作業している場合を除いて、大きい/小さいの比較は実際には問題ではありません。

「あいまいな等しい」比較のために、これ(Javaコード、適応しやすいはずです)は 、多くの作業と多くの批判を考慮した後、浮動小数点ガイドのために私が思いついたものです。

public static boolean nearlyEqual(float a, float b, float epsilon) {
    final float absA = Math.abs(a);
    final float absB = Math.abs(b);
    final float diff = Math.abs(a - b);

    if (a == b) { // shortcut, handles infinities
        return true;
    } else if (a == 0 || b == 0 || diff < Float.MIN_NORMAL) {
        // a or b is zero or both are extremely close to it
        // relative error is less meaningful here
        return diff < (epsilon * Float.MIN_NORMAL);
    } else { // use relative error
        return diff / (absA + absB) < epsilon;
    }
}

テストスイートが付属しています。1つの値が0、2つの非常に小さい値がゼロの反対、または無限大であるなど、一部のエッジケースでは失敗することが事実上保証されているため、そうでないソリューションはすぐに却下する必要があります。

別の方法(詳細については上記のリンクを参照)は、floatのビットパターンを整数に変換し、固定整数距離内のすべてを受け入れることです。

いずれにせよ、すべてのアプリケーションに最適なソリューションはおそらくありません。理想的には、実際のユースケースをカバーするテストスイートを使用して独自に開発/適応します。


1
@toochin:許容する誤差の大きさによって異なりますが、ゼロに最も近い非正規化数、正と負を考慮すると、明らかに問題になります。ゼロは別として、これらは他の2つよりも接近しています。値ですが、相対誤差に基づく多くの単純な実装では、それらが離れすぎていると見なされます。
Michael Borgwardt 2011

2
うーん。テストelse if (a * b == 0)がありますが、同じ行へのコメントはa or b or both are zeroです。しかし、これら2つの異なるものではありませんか?例えば、場合a == 1e-162b == 2e-162、条件がa * b == 0trueになります。
マークディキンソン

1
@toochin:主な理由は、コードがその機能を持たない可能性のある他の言語に簡単に移植できるようになっているためです(Javaにも1.5でのみ追加されました)。
Michael Borgwardt 2011

1
その関数が非常に使用されている場合(たとえば、ビデオゲームのすべてのフレーム)、壮大な最適化を使用してアセンブリで書き直します。

1
特にabs(a-b)<epsここでの答えを考えると、素晴らしいガイドと素晴らしい答え。2つの質問:(1)すべて<のsを<=sに変更して、正確な比較と同等の「ゼロeps」比較を可能にする方がよいのではないでしょうか。(2)(最後の行)のdiff < epsilon * (absA + absB);代わりに使用したほうがいいのではないでしょうdiff / (absA + absB) < epsilon;か?
フランツD.

41

TL; DR

  • 現在受け入れられているソリューションの代わりに次の関数を使用して、特定の制限の場合に望ましくない結果を回避し、潜在的により効率的にします。
  • 数値に予想される不正確さを把握し、それに応じて比較関数に入力します。

グラフィックをお願いします?

浮動小数点数を比較する場合、2つの「モード」があります。

最初のものは、相対差モードxとは、yそれらの振幅に比較的考えられます|x| + |y|。2Dでプロットすると、次のプロファイルが得られます。ここで、緑はとの等しいことを意味xyます。(epsilon説明のために0.5を取りました)。

ここに画像の説明を入力してください

相対モードは、「通常の」または「十分に大きい」浮動小数点値に使用されるものです。(これについては後で詳しく説明します)。

2つ目は、それらの差を固定数と単純に比較する絶対モードです。これにより、次のプロファイルが得られます(ここでも、説明のためepsilonに0.5とrelth1を使用しています)。

ここに画像の説明を入力してください

この絶対比較モードは、「小さな」浮動小数点値に使用されるものです。

ここで問題となるのは、これら2つの応答パターンをどのようにつなぎ合わせるかです。

Michael Borgwardtの回答では、切り替えはの値に基づいておりdiff、これはrelthFloat.MIN_NORMAL彼の回答では)以下である必要があります。このスイッチゾーンは、下のグラフにハッチングで示されています。

ここに画像の説明を入力してください

のでrelth * epsilonその小さいrelth、緑のパッチが順番に解決に悪い性質を与える、一緒に固執しないでください:私たちは、このような数字のトリプレット見つけることができますx < y_1 < y_2し、まだx == y2けどをx != y1

ここに画像の説明を入力してください

この印象的な例を見てください:

x < y1 < y2あり、実際にy2 - xはの2000倍以上ですy1 - x。それでも現在のソリューションでは、

対照的に、上記で提案されたソリューションでは、スイッチゾーンはの値に基づいています|x| + |y|。これは下のハッチングされた正方形で表されます。これにより、両方のゾーンが正常に接続されます。

ここに画像の説明を入力してください

また、上記のコードには分岐がないため、より効率的です。操作のようなことを考えてみましょうmaxabs先験的ニーズは分岐が、多くの場合、専用の組立説明書を持っています。このため、このアプローチはnearlyEqual、スイッチをからdiff < relthに変更してMichaelを修正するという別のソリューションよりも優れていると思います。これにより、diff < eps * relth基本的に同じ応答パターンが生成されます。

相対比較と絶対比較をどこで切り替えるか?

これらのモード間の切り替えは、受け入れられた回答とrelth同様FLT_MINに行われる。この選択は、の表現がfloat32浮動小数点数の精度を制限するものであることを意味します。

これは必ずしも意味がありません。たとえば、比較する数値が減算の結果である場合、おそらく範囲内の何かFLT_EPSILONがより理にかなっています。それらが減算された数の平方根である場合、数値の不正確さはさらに高くなる可能性があります。

浮動小数点をと比較することを検討すると、かなり明白です0。ここでは、相対的な比較は失敗し|x - 0| / (|x| + 0) = 1ます。したがって、がx計算の不正確さのオーダーである場合、比較は絶対モードに切り替える必要があります-そしてそれがFLT_MIN。ほど低くなることはめったにありません。

これが、relth上記のパラメータを導入した理由です。

また、乗算しないことによってrelthepsilon、このパラメータの解釈は、我々はこれらの数字に期待している数値精度のレベルにシンプルで対応しています。

数学的ゴロゴロ

(主に私自身の喜びのためにここに保管されました)

より一般的には、行儀の良い浮動小数点比較演算子に=~はいくつかの基本的なプロパティがあるはずだと思います。

以下はかなり明白です:

  • 自己平等: a =~ a
  • 対称性:a =~ b意味するb =~ a
  • 野党による不変性:a =~ b暗示-a =~ -b

(私たちは持っておらずa =~ bb =~ c暗黙のa =~ cうちに=~、同値関係ではありません)。

浮動小数点比較に固有の次のプロパティを追加します

  • の場合a < b < c、それはa =~ c意味しますa =~ b(より近い値も等しくなければなりません)
  • a, b, m >= 0それがa =~ b意味する場合a + m =~ b + m(同じ差を持つより大きな値も等しくなければなりません)
  • 0 <= λ < 1それがa =~ b意味する場合λa =~ λb(おそらく議論するのはあまり明白ではありません)。

これらのプロパティは、可能なほぼ等式の関数にすでに強い制約を与えています。上で提案された機能はそれらを検証します。おそらく、1つまたはいくつかの明らかなプロパティが欠落しています。

とによってパラメータ化された=~平等関係のファミリーと考えると、次のように追加することもできます。=~[Ɛ,t]Ɛrelth

  • Ɛ1 < Ɛ2それがa =~[Ɛ1,t] b意味する場合a =~[Ɛ2,t] b(与えられた許容範囲の平等は、より高い許容範囲での平等を意味します)
  • if t1 < t2thenがa =~[Ɛ,t1] b意味するa =~[Ɛ,t2] b(与えられた不正確さの平等は、より高い不正確さでの平等を意味する)

提案されたソリューションは、これらも検証します。


1
それは素晴らしい答えです!
davidhigh

1
C ++実装の質問:(std::abs(a) + std::abs(b))これまでに大きくなる可能性がありstd::numeric_limits<float>::max()ますか?
anneb

1
@annebはい、+ INFにすることができます。
PaulGroke20年

16

浮動小数点数の比較に問題がA < Bありました。これA > B が機能しているようです。

ファブ(絶対値)は、それらが本質的に等しいかどうかを処理します。


1
使用する必要はありませんfabsあなたが最初のテストにする場合、すべてでif (A - B < -Epsilon)
fishinear

11

フロート数を比較するには、許容レベルを選択する必要があります。例えば、

final float TOLERANCE = 0.00001;
if (Math.abs(f1 - f2) < TOLERANCE)
    Console.WriteLine("Oh yes!");

1つのメモ。あなたの例はかなり面白いです。

double a = 1.0 / 3.0;
double b = a + a + a;
if (a != b)
    Console.WriteLine("Oh no!");

ここにいくつかの数学

a = 1/3
b = 1/3 + 1/3 + 1/3 = 1.

1/3 != 1

ああ、そうだ。

どういう意味ですか

if (b != 1)
    Console.WriteLine("Oh no!")

3

浮動小数点をすばやく比較するために私が持っていたアイデア

infix operator ~= {}

func ~= (a: Float, b: Float) -> Bool {
    return fabsf(a - b) < Float(FLT_EPSILON)
}

func ~= (a: CGFloat, b: CGFloat) -> Bool {
    return fabs(a - b) < CGFloat(FLT_EPSILON)
}

func ~= (a: Double, b: Double) -> Bool {
    return fabs(a - b) < Double(FLT_EPSILON)
}

1

Michael Borgwardtとbosonixの回答によるPHPへの適応:

class Comparison
{
    const MIN_NORMAL = 1.17549435E-38;  //from Java Specs

    // from http://floating-point-gui.de/errors/comparison/
    public function nearlyEqual($a, $b, $epsilon = 0.000001)
    {
        $absA = abs($a);
        $absB = abs($b);
        $diff = abs($a - $b);

        if ($a == $b) {
            return true;
        } else {
            if ($a == 0 || $b == 0 || $diff < self::MIN_NORMAL) {
                return $diff < ($epsilon * self::MIN_NORMAL);
            } else {
                return $diff / ($absA + $absB) < $epsilon;
            }
        }
    }
}

1

なぜ数字を比較しているのか自問する必要があります。比較の目的がわかっている場合は、必要な数値の精度も知っておく必要があります。これは、状況やアプリケーションのコンテキストごとに異なります。しかし、ほとんどすべての実際のケースでは、絶対的な精度が必要です。相対的な精度が適用できることはめったにありません。

例を挙げると、目標が画面上にグラフを描画することである場合、浮動小数点値が画面上の同じピクセルにマップされていれば、浮動小数点値を等しく比較する必要があります。画面のサイズが1000ピクセルで、数値が1e6の範囲にある場合、100を200と比較することをお勧めします。

必要な絶対精度が与えられると、アルゴリズムは次のようになります。

public static ComparisonResult compare(float a, float b, float accuracy) 
{
    if (isnan(a) || isnan(b))   // if NaN needs to be supported
        return UNORDERED;    
    if (a == b)                 // short-cut and takes care of infinities
        return EQUAL;           
    if (abs(a-b) < accuracy)    // comparison wrt. the accuracy
        return EQUAL;
    if (a < b)                  // larger / smaller
        return SMALLER;
    else
        return LARGER;
}

0

標準的なアドバイスは、いくつかの小さな「イプシロン」値(おそらくアプリケーションに応じて選択)を使用し、互いにイプシロン内にあるフロートが等しいと見なすことです。例:

浮動小数点エラーは非常に微妙であり、推論が混乱するため、より完全な答えは複雑です。正確な意味での平等を本当に気にしているのであれば、おそらく浮動小数点を含まないソリューションを探しているでしょう。


彼が2.3E-15のような非常に小さな浮動小数点数で作業している場合はどうなりますか?
toochin 2011

1
私はおおよそ[​​10E-14、10E6]の範囲で作業していますが、マシンイプシロンではありませんが、それに非常に近いものです。
マイクベイリー

2
相対的なエラーを処理する必要があることを念頭に置いておくと、小さい数を処理することは問題になりません。比較的大きな許容誤差を気にしない場合は、条件を次のようなものに置き換えれば、上記は問題ありませんif ((a - b) < EPSILON/a && (b - a) < EPSILON/a)
toochin 2011

2
上記のコードは、非常に大きな数を処理する場合にも問題がありますc。これは、数が十分に大きくなると、EPSILONがのマシン精度よりも小さくなるためですc。たとえば、と仮定しますc = 1E+22; d=c/3; e=d+d+d;。次いで、e-cウェル1よりもかなり大きくてもよい
toochin

1
たとえば、double a = pow(8,20); double b = a/7; double c = b+b+b+b+b+b+b; std::cout<<std::scientific<<a-c;(aとcがpntとnelhageに従って等しくない)、またはdouble a = pow(10,-14); double b = a/2; std::cout<<std::scientific<<a-b;(aとbがpntとnelhageに従って等しくない)
toochin 2011

0

上記のコメントを念頭に置いて、等式関数を書いてみました。これが私が思いついたものです:

編集:Math.Max(a、b)からMath.Max(Math.Abs​​(a)、Math.Abs​​(b))に変更

考え?私はまだ大なり記号と小なり記号を計算する必要があります。


epsilonである必要があります。そうMath.abs(Math.Max(a, b)) * Double.Epsilon;でない場合は、常にdiff負の数aとよりも小さくなりbます。そして、私はあなたepsilonが小さすぎると思います、関数は==演算子と異なるものを返さないかもしれません。より大きいa < b && !fpEqual(a,b)
toochin 2011

1
両方の値が正確にゼロの場合は失敗し、Double.Epsilonと-Double.Epsilonの場合は失敗し、無限大の場合は失敗します。
Michael Borgwardt 2011

1
無限大の場合は、私の特定のアプリケーションでは問題ではありませんが、十分に注意されています。
マイクベイリー

-1

切り捨てエラーは相対的なものであることを考慮する必要があります。2つの数値は、それらの差がulp(最後の単位)とほぼ同じである場合、ほぼ等しくなります。

ただし、浮動小数点計算を行う場合は、操作ごとにエラーの可能性が高くなるため(特に、減算に注意してください)、それに応じてエラー許容度を上げる必要があります。


-1

ダブルスの等式/不等式を比較する最良の方法は、それらの差の絶対値を取得し、それを十分に小さい(コンテキストに応じて)値と比較することです。

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