web-dev-qa-db-ja.com

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

私は現在、次の行に沿って何かを持っているいくつかのコードを書いています:

double a = SomeCalculation1();
double b = SomeCalculation2();

if (a < b)
    DoSomething2();
else if (a > b)
    DoSomething3();

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

double a = SomeCalculation3();
double b = SomeCalculation4();

if (a == 0.0)
   DoSomethingUseful(1 / a);
if (b == 0.0)
   return 0; // or something else here

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

浮動小数点の比較は信頼性の低いものになる可能性があることを以前読んだことがあります。

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

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

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

使用している言語に関係なくこれを達成する方法に興味があるため、これを言語に依存しないとタグ付けしました。

71
Mike Bailey

Float/double precisionの境界の端で作業しているのでなければ、より大きい/小さいかどうかを比較することは実際には問題ではありません。

「ファジーイコール」比較の場合、これ(Javaコード、簡単に適応できるはずです)は、多くの作業と多くの批判を考慮して、 The Floating-Point Guide で思いついたものです。 :

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つの非常に小さい値、または無限大などのEdgeのケースでは事実上失敗することが保証されているため、そうでないソリューションはすぐに破棄する必要があります。

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

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

63

TL; DR

  • 現在受け入れられているソリューションの代わりに以下の関数を使用して、特定の制限の場合に望ましくない結果を回避し、潜在的にはより効率的にします。
  • 数字に予想される不正確さを把握し、それに応じて比較関数に入力します。
_bool nearly_equal(
  float a, float b,
  float epsilon = 128 * FLT_EPSILON, float relth = FLT_MIN)
  // those defaults are arbitrary and could be removed
{
  assert(std::numeric_limits<float>::epsilon() <= epsilon);
  assert(epsilon < 1.f);

  if (a == b) return true;

  auto diff = std::abs(a-b);
  auto norm = std::min((std::abs(a) + std::abs(b)), std::numeric_limits<float>::max());
  return diff < std::max(relth, epsilon * norm);
}
_

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

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

1つ目はrelativeモードで、xyの違いは振幅_|x| + |y|_。 2Dでプロットすると、次のプロファイルが得られます。緑はxyが等しいことを意味します。 (説明のためにepsilonを0.5にしました)。

enter image description here

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

2つ目は、absoluteモードであり、単純にそれらの差を固定数と比較します。次のプロファイルを示します(例として、0.5のepsilonと1のrelthを使用)。

enter image description here

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

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

Michael Borgwardtの答えでは、スイッチはdiffの値に基づいています。これはrelth(彼の答えでは_Float.MIN_NORMAL_)未満でなければなりません。このスイッチゾーンは、以下のグラフにハッチングで示されています。

enter image description here

_relth * epsilon_はrelthよりも小さいため、緑色のパッチは互いにくっつかず、その結果、ソリューションに悪い特性が与えられます:_x < y_1 < y_2_でありながら、_x == y2_だが_x != y1_。

enter image description here

この印象的な例を見てみましょう:

_x  = 4.9303807e-32
y1 = 4.930381e-32
y2 = 4.9309825e-32
_

_x < y1 < y2_があり、実際には_y2 - x_は_y1 - x_の2000倍以上です。それでも、現在のソリューションでは、

_nearlyEqual(x, y1, 1e-4) == False
nearlyEqual(x, y2, 1e-4) == True
_

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

enter image description here

また、上記のコードには分岐がないため、より効率的です。 aprioriが分岐を必要とするmaxabsなどの操作には、多くの場合、専用のアセンブリ命令があります。このため、このアプローチは、スイッチを_diff < relth_から_diff < eps * relth_に変更することでMichaelのnearlyEqualを修正し、本質的に同じ応答を生成する別のソリューションよりも優れていると思いますパターン。

相対比較と絶対比較を切り替える場所

これらのモード間の切り替えは、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 =~ b_はなく、_b =~ c_は_a =~ c_を意味し、_=~_は等価関係ではありません)。

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

  • _a < b < c_の場合、_a =~ c_は_a =~ b_を意味します(より近い値も等しくなければなりません)
  • if _a, b, m >= 0_ then _a =~ b_は_a + m =~ b + m_を意味します(同じ差の大きい値も等しくなければなりません)
  • if _0 <= λ < 1_ then _a =~ b_は、_λa =~ λb_を意味します(おそらく、引数についてはそれほど明白ではありません)。

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

_=~_を、_=~[Ɛ,t]_およびrelthでパラメーター化された等式関係_Ɛ_のファミリーと考える場合、追加することもできます。

  • if _Ɛ1 < Ɛ2_ then _a =~[Ɛ1,t] b_は_a =~[Ɛ2,t] b_を意味します(与えられた許容差の平等はより高い許容差での平等を意味します)
  • if _t1 < t2_ then _a =~[Ɛ,t1] b_は_a =~[Ɛ,t2] b_を意味します(与えられた不正確さの平等はより高い不正確さでの平等を意味します)

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

12
P-Gn

浮動小数点数を比較する問題がありましたA < BおよびA > Bこれがうまくいくようです:

if(A - B < Epsilon) && (fabs(A-B) > Epsilon)
{
    printf("A is less than B");
}

if (A - B > Epsilon) && (fabs(A-B) > Epsilon)
{
    printf("A is greater than B");
}

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

12
tech_loafer

浮動小数点数を比較するには、許容レベルを選択する必要があります。例えば、

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

ワンノート。あなたの例はかなり面白いです。

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!")
10
nni6

Swiftでの浮動小数点比較のアイデア

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)
}
3
Andy Poes

PHP Michael Borgwardt&bosonix's answer:

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
Dennis

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

例として、画面上にグラフを描くことが目標である場合、浮動小数点値が画面上の同じピクセルにマッピングされている場合、それらを等しく比較したいでしょう。画面のサイズが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
fishinear

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

#define EPSILON 0.00000001

if ((a - b) < EPSILON && (b - a) < EPSILON) {
  printf("a and b are about equal\n");
}

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

0
nelhage

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

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

static bool fpEqual(double a, double b)
{
    double diff = Math.Abs(a - b);
    double epsilon = Math.Max(Math.Abs(a), Math.Abs(b)) * Double.Epsilon;
    return (diff < epsilon);
}

考え?私はまだ、より大きく、より小さくも解決する必要があります。

0
Mike Bailey