私は現在、次の行に沿って何かを持っているいくつかのコードを書いています:
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であるため、小さい数字でも大きい数字でも作業する必要があります。
使用している言語に関係なくこれを達成する方法に興味があるため、これを言語に依存しないとタグ付けしました。
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のケースでは事実上失敗することが保証されているため、そうでないソリューションはすぐに破棄する必要があります。
別の方法(詳細については上記のリンクを参照)は、フロートのビットパターンを整数に変換し、固定整数距離内のすべてを受け入れることです。
いずれにせよ、すべてのアプリケーションに最適なソリューションはおそらくないでしょう。理想的には、実際のユースケースをカバーするテストスイートを使用して独自の開発/適応を行います。
_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モードで、x
とy
の違いは振幅_|x| + |y|
_。 2Dでプロットすると、次のプロファイルが得られます。緑はx
とy
が等しいことを意味します。 (説明のためにepsilon
を0.5にしました)。
相対モードは、「通常」または「十分に大きい」浮動小数点値に使用されるモードです。 (これについては後で説明します)。
2つ目は、absoluteモードであり、単純にそれらの差を固定数と比較します。次のプロファイルを示します(例として、0.5のepsilon
と1のrelth
を使用)。
この絶対比較モードは、「小さな」浮動小数点値に使用されます。
問題は、これら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
_は_y1 - x
_の2000倍以上です。それでも、現在のソリューションでは、
_nearlyEqual(x, y1, 1e-4) == False
nearlyEqual(x, y2, 1e-4) == True
_
対照的に、上記で提案されたソリューションでは、スイッチゾーンは_|x| + |y|
_の値に基づいており、これは下のハッチングされた正方形で表されます。これにより、両方のゾーンが適切に接続されます。
また、上記のコードには分岐がないため、より効率的です。 aprioriが分岐を必要とするmax
やabs
などの操作には、多くの場合、専用のアセンブリ命令があります。このため、このアプローチは、スイッチを_diff < relth
_から_diff < eps * relth
_に変更することでMichaelのnearlyEqual
を修正し、本質的に同じ応答を生成する別のソリューションよりも優れていると思いますパターン。
これらのモード間の切り替えは、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
_ then _a =~ b
_は_a + m =~ b + m
_を意味します(同じ差の大きい値も等しくなければなりません)0 <= λ < 1
_ then _a =~ b
_は、_λa =~ λb
_を意味します(おそらく、引数についてはそれほど明白ではありません)。これらのプロパティは、可能な限り同等に近い関数にすでに強い制約を与えています。上記で提案された機能はそれらを検証します。おそらく1つまたは複数の明らかなプロパティが欠落している可能性があります。
_=~
_を、_=~[Ɛ,t]
_およびrelth
でパラメーター化された等式関係_Ɛ
_のファミリーと考える場合、追加することもできます。
Ɛ1 < Ɛ2
_ then _a =~[Ɛ1,t] b
_は_a =~[Ɛ2,t] b
_を意味します(与えられた許容差の平等はより高い許容差での平等を意味します)t1 < t2
_ then _a =~[Ɛ,t1] b
_は_a =~[Ɛ,t2] b
_を意味します(与えられた不正確さの平等はより高い不正確さでの平等を意味します)提案されたソリューションはこれらも検証します。
浮動小数点数を比較する問題がありました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");
}
ファブ(絶対値)は、それらが本質的に等しい場合に処理します。
浮動小数点数を比較するには、許容レベルを選択する必要があります。例えば、
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!")
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)
}
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;
}
}
}
}
数値を比較する理由を自問する必要があります。比較の目的がわかっている場合は、必要な数値の精度も知っている必要があります。これは、状況やアプリケーションコンテキストごとに異なります。しかし、ほとんどすべての実際的なケースでは、必要な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;
}
標準的なアドバイスは、小さな「イプシロン」値(おそらくアプリケーションに応じて選択される)を使用し、互いにイプシロン内にあるフロートが等しいと見なすことです。例えば何かのようなもの
#define EPSILON 0.00000001
if ((a - b) < EPSILON && (b - a) < EPSILON) {
printf("a and b are about equal\n");
}
より完全な答えは複雑です。なぜなら、浮動小数点エラーは非常に微妙であり、推論するのが難しいからです。正確な意味で平等を本当に重視するのであれば、おそらく浮動小数点を使用しないソリューションを探しているでしょう。
上記のコメントを念頭に置いて、等式関数を作成してみました。ここに私が思いついたものがあります:
編集: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);
}
考え?私はまだ、より大きく、より小さくも解決する必要があります。