現在、次のようなコードを書いています:
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 つの「モード」があります。
一つ目は相対的モードでは、 と の差はx
、y
それらの振幅 に対して相対的であると考えられます|x| + |y|
。 2D でプロットすると、次のプロファイルが得られます。ここで、緑はx
と が等しいことを意味します。 (説明のために を 0.5y
としました)。epsilon
相対モードは、「通常の」または「十分に大きい」浮動小数点値に使用されます。(詳細は後述します)。
2つ目は絶対モードでは、単純にその差を固定数と比較します。次のプロファイルが得られます (ここでも、説明のためにepsilon
を 0.5、 をabs_th
1 としています)。
この絶対的な比較モードは、「小さな」浮動小数点値に使用されます。
ここで問題となるのは、これら 2 つの応答パターンをどのように組み合わせるかということです。
Michael Borgwardt 氏の回答では、スイッチは の値に基づいており、これは(彼の回答では )diff
より下になるはずです。このスイッチ ゾーンは、下のグラフでハッチングで示されています。abs_th
Float.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 - x
2000倍以上の大きさですy1 - x
。しかし、現在の解では、
nearlyEqual(x, y1, 1e-4) == False
nearlyEqual(x, y2, 1e-4) == True
対照的に、上で提案したソリューションでは、スイッチ ゾーンは の値に基づいており|x| + |y|
、これは以下の斜線付きの四角で表されます。これにより、両方のゾーンが正常に接続されることが保証されます。
また、上記のコードには分岐がありませんが、これはより効率的です。max
やabs
などの操作を考えてみましょう。アプリオリnearlyEqual
分岐が必要な場合は、専用のアセンブリ命令を持つことが多いです。このため、このアプローチは、スイッチを から に変更してdiff < abs_th
Michaelを修正するという別の解決策よりも優れていると思いますdiff < eps * abs_th
。この方法では、基本的に同じ応答パターンが生成されます。
相対比較と絶対比較を切り替える場所はどこですか?
これらのモード間の切り替えは の周りで行われabs_th
、これはFLT_MIN
受け入れられた回答では と解釈されます。この選択は、 の表現がfloat32
浮動小数点数の精度を制限することを意味します。
これは必ずしも意味をなさない場合があります。たとえば、比較する数値が減算の結果である場合、 の範囲内の値の方がFLT_EPSILON
意味をなす可能性があります。減算された数値の平方根である場合、数値の不正確さはさらに大きくなる可能性があります。
浮動小数点を と比較する場合、これはむしろ明白です0
。ここでは、 のため、相対比較は失敗します|x - 0| / (|x| + 0) = 1
。したがって、 が計算の不正確さのオーダーにある場合、比較は絶対モードに切り替える必要があります。x
そして、 ほど低くなることはめったにありませんFLT_MIN
。
これが上記のパラメータを導入する理由ですabs_th
。
また、 を乗算しないことでabs_th
、epsilon
このパラメータの解釈は簡単になり、それらの数値に期待される数値精度のレベルに対応します。
数学的なゴロゴロ
(主に私自身の楽しみのためにここに保存しています)
より一般的には、適切に動作する浮動小数点比較演算子には、=~
いくつかの基本的なプロパティがあるはずだと私は考えています。
以下はむしろ明白です:
- 自己平等:
a =~ a
- 対称性:
a =~ b
意味するb =~ a
- 反対による不変性:
a =~ b
意味する-a =~ -b
( は存在せずa =~ b
、は同値関係ではないことb =~ 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]
Ɛ
abs_th
- ならば
Ɛ1 < Ɛ2
、(与えられた許容値での等価性は、より高い許容値での等価性を意味するa =~[Ɛ1,t] b
)a =~[Ɛ2,t] b
- ならば
t1 < t2
、次のa =~[Ɛ,t1] b
式が成り立ちますa =~[Ɛ,t2] b
(与えられた不正確さに対する等価性は、より高い不正確さに対する等価性を意味します)
提案されたソリューションはこれらも検証します。