浮動小数点の比較はどのように行うのでしょうか? 質問する

浮動小数点の比較はどのように行うのでしょうか? 質問する

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

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 までなので、大きな数値だけでなく小さな数値も扱う必要があります。

私は、どの言語を使用していてもこれを実現する方法に興味があるため、これを言語非依存としてタグ付けしました。

ベストアンサー1

要約

  • 特定の制限ケースで望ましくない結果を回避し、潜在的に効率を高めるには、現在受け入れられているソリューションの代わりに次の関数を使用します。
  • 数値の予想される不正確さを把握し、それに応じて比較関数に入力します。
bool nearly_equal(
  float a, float b,
  float epsilon = 128 * FLT_EPSILON, float abs_th = 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());
  // or even faster: std::min(std::abs(a + b), std::numeric_limits<float>::max());
  // keeping this commented out until I update figures below
  return diff < std::max(abs_th, epsilon * norm);
}

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

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

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

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

相対モードは、「通常の」または「十分に大きい」浮動小数点値に使用されます。(詳細は後述します)。

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

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

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

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

Michael Borgwardt 氏の回答では、スイッチは の値に基づいており、これは(彼の回答では )diffより下になるはずです。このスイッチ ゾーンは、下のグラフでハッチングで示されています。abs_thFloat.MIN_NORMAL

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

abs_th * epsilonは よりも小さいため、abs_th緑の部分はくっつかず、その結果、解に悪い特性が与えられます。x < y_1 < y_2つまり、 でありながら であるような 3 つの数字を見つけることができますが、x == y2ですx != y1

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

次の印象的な例を見てみましょう。

x  = 4.9303807e-32
y1 = 4.930381e-32
y2 = 4.9309825e-32

がありx < y1 < y2、実際は のy2 - x2000倍以上の大きさですy1 - x。しかし、現在の解では、

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

対照的に、上で提案したソリューションでは、スイッチ ゾーンは の値に基づいており|x| + |y|、これは以下の斜線付きの四角で表されます。これにより、両方のゾーンが正常に接続されることが保証されます。

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

また、上記のコードには分岐がありませんが、これはより効率的です。maxabsなどの操作を考えてみましょう。アプリオリnearlyEqual分岐が必要な場合は、専用のアセンブリ命令を持つことが多いです。このため、このアプローチは、スイッチを から に変更してdiff < abs_thMichaelを修正するという別の解決策よりも優れていると思いますdiff < eps * abs_th。この方法では、基本的に同じ応答パターンが生成されます。

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

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

これは必ずしも意味をなさない場合があります。たとえば、比較する数値が減算の結果である場合、 の範囲内の値の方がFLT_EPSILON意味をなす可能性があります。減算された数値の平方根である場合、数値の不正確さはさらに大きくなる可能性があります。

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

これが上記のパラメータを導入する理由ですabs_th

また、 を乗算しないことでabs_thepsilonこのパラメータの解釈は簡単になり、それらの数値に期待される数値精度のレベルに対応します。

数学的なゴロゴロ

(主に私自身の楽しみのためにここに保存しています)

より一般的には、適切に動作する浮動小数点比較演算子には、=~いくつかの基本的なプロパティがあるはずだと私は考えています。

以下はむしろ明白です:

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

( は存在せずa =~ b、は同値関係ではないことb =~ cを意味します)。a =~ c=~

浮動小数点の比較に特有の以下のプロパティを追加します。

  • ならばa < b < c、(近い値も等しいはずa =~ ca =~ b
  • ならばa, b, m >= 0、 (同じ差a =~ ba + m =~ b + m持つより大きな値も等しいはず)
  • ならば0 <= λ < 1、(おそらく議論の余地は少ないでしょうが)をa =~ b意味します。λa =~ λb

これらの特性は、ほぼ等しい可能性のある関数にすでに強い制約を与えています。上で提案した関数は、それらを検証します。おそらく、1 つまたは複数の明らかな特性が欠けている可能性があります。

をとでパラメータ化された=~等式関係の族として考えると、次の式も加えられる。=~[Ɛ,t]Ɛabs_th

  • ならばƐ1 < Ɛ2、(与えられた許容値での等価性は、より高い許容値での等価性を意味するa =~[Ɛ1,t] ba =~[Ɛ2,t] b
  • ならばt1 < t2、次のa =~[Ɛ,t1] b式が成り立ちますa =~[Ɛ,t2] b(与えられた不正確さに対する等価性は、より高い不正確さに対する等価性を意味します)

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

おすすめ記事