web-dev-qa-db-ja.com

浮動小数点数をゼロと比較する

C++ FAQ lite "[29.17]浮動小数点の比較が機能しないのはなぜですか?" この等価性テストを推奨しています:

#include <cmath>  /* for std::abs(double) */

inline bool isEqual(double x, double y)
{
  const double epsilon = /* some small number such as 1e-5 */;
  return std::abs(x - y) <= epsilon * std::abs(x);
  // see Knuth section 4.2.2 pages 217-218
}
  1. 正しいですか、これは、ゼロに等しい数字が+0-0だけであることを意味するということですか?
  2. ゼロをテストするとき、または|x| < epsilonのようなテストを行うときにも、この関数を使用する必要がありますか?

更新

Daniel Daranasが指摘したように、関数はおそらくisNearlyEqualと呼ばれるべきです(これは私が気にするケースです)。

誰かが このリンク を指摘しました。これをもっとはっきりと共有したいと思います。

52

あなたの観察は正しいです。

x == 0.0の場合、abs(x) * epsilonはゼロであり、abs(y) <= 0.0かどうかをテストしています。

y == 0.0の場合、abs(x) <= abs(x) * epsilonをテストしています。つまり、epsilon >= 1(そうではない)またはx == 0.0のいずれかを意味します。

したがって、is_equal(val, 0.0)またはis_equal(0.0, val)は無意味であり、単にval == 0.0と言うことができます。 exactlyのみを受け入れたい場合+0.0および-0.0

この場合のFAQの推奨事項は、限定的な有用性です。 「すべてに適合する」浮動小数点比較はありません。変数のセマンティクス、許容される値の範囲、および計算によって導入されたエラーの大きさを考慮する必要があります。 FAQでさえ、この関数は「xとyの大きさがイプシロンよりも大幅に大きいが、走行距離が異なる場合がある」という問題ではない、と述べています。

40
Ben Voigt

番号。

平等は平等です。

あなたが書いた関数は、その名前が約束するように、2つのdoubleが等しいかどうかをテストしません。 2つのdoubleが互いに「十分に近い」かどうかのみをテストします。

really2つのdoubleの等価性をテストする場合は、この1つを使用します。

inline bool isEqual(double x, double y)
{
   return x == y;
}

コーディング標準では、通常、2つのdoubleを正確に同等にするために比較することを推奨しています。しかし、それは別の主題です。 実際に2つのdoubleの正確な等価性を比較する場合、x == yが目的のコードです。

10.00000000000000001は、10.0とは異なります。

正確な等価性を使用することは、doubleの特定の値が、「保留中の計算」や「使用可能なデータなし」などの特別な状態の同義語として使用される場合です。これは、その保留中の計算の後の実際の数値が、doubleの可能な値のサブセットのみである場合にのみ可能です。最も典型的な特定のケースは、その値が非負であり、「保留中の計算」または「利用可能なデータなし」の(正確な)表現として-1.0を使用する場合です。あなたは定数でそれを表すことができます:

const double NO_DATA = -1.0;

double myData = getSomeDataWhichIsAlwaysNonNegative(someParameters);

if (myData != NO_DATA)
{
    ...
}
18
Daniel Daranas

次のような値のfactorの固定epsilonとともにstd::nextafterを使用できます。

bool isNearlyEqual(double a, double b)
{
  int factor = /* a fixed factor of epsilon */;

  double min_a = a - (a - std::nextafter(a, std::numeric_limits<double>::lowest())) * factor;
  double max_a = a + (std::nextafter(a, std::numeric_limits<double>::max()) - a) * factor;

  return min_a <= b && max_a >= b;
}
6
Daniel Laügt

+0.0-0.0のみに関心がある場合は、<cmath>から fpclassify を使用できます。例えば:

if( FP_ZERO == fpclassify(x) ) do_something;

5
DaBler

2 + 2 = 5(*)

2の一部の浮動精度値の場合)

この問題は、「浮動小数点」を精度を高める方法と考えると頻繁に発生します。次に、「浮動」部分に反して実行します。つまり、どの数値を表現できるかは保証されません。

そのため、大きな数値になると「1.0、-1.0、0.1、-0.1」を簡単に表すことができるかもしれませんが、近似値が表示されるようになります。

その結果、コンピューターは「0.003」を保存していると思われるかもしれませんが、代わりに「0.0033333333334」を保存している可能性があります。

「0.0003-0.0002」を実行するとどうなりますか? .0001を期待していますが、実際に保存される値は、「0.00033」〜「0.00029」のようになり、「0.000004」、、または最も近い表現可能な値になります。または、「0.000006」になる場合があります。

現在の浮動小数点演算では、- (a/b)* b == a とは保証されません。

#include <stdio.h>

// defeat inline optimizations of 'a / b * b' to 'a'
extern double bodge(int base, int divisor) {
    return static_cast<double>(base) / static_cast<double>(divisor);
}

int main() {
    int errors = 0;
    for (int b = 1; b < 100; ++b) {
        for (int d = 1; d < 100; ++d) {
            // b / d * d ... should == b
            double res = bodge(b, d) * static_cast<double>(d);
            // but it doesn't always
            if (res != static_cast<double>(b))
                ++errors;
        }
    }
    printf("errors: %d\n", errors);
}

ideoneは、1 <= b <= 100と1 <= d <= 100の10,000の組み合わせのみを使用して、(b * d)/ d!= bの599インスタンスを報告します。

FAQで説明されている解決策は、本質的に粒度制約を適用することです-if (a == b +/- epsilon)をテストすることです。

別の方法は、固定小数点の精度を使用するか、ストレージの基本単位として希望の粒度を使用することにより、問題を完全に回避することです。例えば。時間をナノ秒の精度で保存する場合は、保存単位としてナノ秒を使用します。

C++ 11では、異なる時間単位間の固定小数点変換の基礎として std :: ratio が導入されました。

4
kfsone

@Exceptyonが指摘したように、この関数は比較する値に対して「相対」です。 Epsilon * abs(x)メジャーはxの値に基づいてスケーリングされるため、xまたはyの値の範囲に関係なく、epsilonと同じくらい正確に比較結果を取得できます。

Zero(y)を別の非常に小さな値(x)、たとえば1e-8と比較している場合、abs(x-y) = 1e-8epsilon *abs(x) = 1e-13よりもずっと大きくなります。したがって、double型で表現できない非常に小さな数を処理する場合を除き、この関数はジョブを実行し、+0および-0に対してのみゼロと一致します。

この関数はゼロ比較に完全に有効なようです。使用する予定がある場合は、フロートが関係するすべての場所で使用することをお勧めします。ゼロなどの特別なケースは使用せず、コードの均一性を確保します。

ps:これはきちんとした機能です。それを指してくれてありがとう。

1
Arun R

FP番号の単純な比較は固有のものであり、重要なのはFP形式の理解です( https://en.wikipedia.org/wiki/IEEE_floating_pointを参照

FP数値が異なる方法で計算された場合、1つはsin()で、もう1つはexp()ですが、数学的には数値が等しくても、厳密な等価は機能しません。同じ方法では、定数と同等に機能しません。実際、多くの場合、FP数値は厳密な等価性(==)を使用して比較しないでください。

そのような場合、DBL_EPSIPON定数を使用する必要があります。これは最小値であり、変更しないでください1.0が1.0を超える数に追加されます。 2.0を超える浮動小数点数の場合、DBL_EPSIPONはまったく存在しません。一方、DBL_EPSILONの指数は-16です。つまり、指数-34のすべての数値は、DBL_EPSILONと比較して完全に等しいことを意味します。

また、 、なぜ10.0 == 10.0000000000000001を参照してください

2つの浮動小数点数の比較は、これらの数値の性質に依存するため、比較に意味があるDBL_EPSILONを計算する必要があります。単純に、DBL_EPSILONをこれらの数値の1つに乗算する必要があります。どれですか?もちろん最大

bool close_enough(double a, double b){
    if (fabs(a - b) <= DBL_EPSILON * std::fmax(fabs(a), fabs(b)))
    {
        return true;
    }
    return false;
}

他のすべての方法では、キャッチするのが非常に難しい不平等のバグが発生します

1

そのコードは次のとおりです。

std::abs((x - y)/x) <= epsilon

varの「相対誤差」が<=イプシロンであることを要求しています。

0
Exceptyon