TL; DR
- 現在受け入れられているソリューションの代わりに次の関数を使用して、特定の制限の場合に望ましくない結果を回避し、潜在的により効率的にします。
- 数値に予想される不正確さを把握し、それに応じて比較関数に入力します。
bool nearly_equal(
float a, float b,
float epsilon = 128 * FLT_EPSILON, float relth = FLT_MIN)
{
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つの「モード」があります。
最初のものは、相対差モードx
とは、y
それらの振幅に比較的考えられます|x| + |y|
。2Dでプロットすると、次のプロファイルが得られます。ここで、緑はとの等しいことを意味x
しy
ます。(epsilon
説明のために0.5を取りました)。
相対モードは、「通常の」または「十分に大きい」浮動小数点値に使用されるものです。(これについては後で詳しく説明します)。
2つ目は、それらの差を固定数と単純に比較する絶対モードです。これにより、次のプロファイルが得られます(ここでも、説明のためepsilon
に0.5とrelth
1を使用しています)。
この絶対比較モードは、「小さな」浮動小数点値に使用されるものです。
ここで問題となるのは、これら2つの応答パターンをどのようにつなぎ合わせるかです。
Michael Borgwardtの回答では、切り替えはの値に基づいておりdiff
、これはrelth
(Float.MIN_NORMAL
彼の回答では)以下である必要があります。このスイッチゾーンは、下のグラフにハッチングで示されています。
のでrelth * epsilon
その小さいrelth
、緑のパッチが順番に解決に悪い性質を与える、一緒に固執しないでください:私たちは、このような数字のトリプレット見つけることができますx < y_1 < y_2
し、まだ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 < relth
に変更してMichaelを修正するという別のソリューションよりも優れていると思います。これにより、diff < eps * relth
基本的に同じ応答パターンが生成されます。
相対比較と絶対比較をどこで切り替えるか?
これらのモード間の切り替えは、受け入れられた回答とrelth
同様FLT_MIN
に行われる。この選択は、の表現がfloat32
浮動小数点数の精度を制限するものであることを意味します。
これは必ずしも意味がありません。たとえば、比較する数値が減算の結果である場合、おそらく範囲内の何かFLT_EPSILON
がより理にかなっています。それらが減算された数の平方根である場合、数値の不正確さはさらに高くなる可能性があります。
浮動小数点をと比較することを検討すると、かなり明白です0
。ここでは、相対的な比較は失敗し|x - 0| / (|x| + 0) = 1
ます。したがって、がx
計算の不正確さのオーダーである場合、比較は絶対モードに切り替える必要があります-そしてそれがFLT_MIN
。ほど低くなることはめったにありません。
これが、relth
上記のパラメータを導入した理由です。
また、乗算しないことによってrelth
で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]
Ɛ
relth
Ɛ1 < Ɛ2
それがa =~[Ɛ1,t] b
意味する場合a =~[Ɛ2,t] b
(与えられた許容範囲の平等は、より高い許容範囲での平等を意味します)
- if
t1 < t2
thenがa =~[Ɛ,t1] b
意味するa =~[Ɛ,t2] b
(与えられた不正確さの平等は、より高い不正確さでの平等を意味する)
提案されたソリューションは、これらも検証します。