web-dev-qa-db-ja.com

フロートを正しく標準的に比較する方法は?

新しいプロジェクトを開始するたびに、float変数またはdouble変数を比較する必要があるときは、次のようなコードを記述します。

if (fabs(prev.min[i] - cur->min[i]) < 0.000001 &&
    fabs(prev.max[i] - cur->max[i]) < 0.000001) {
        continue;
}

次に、これらのマジック変数0.000001(およびdoubleの場合は0.00000000001)とファブを削除したいので、インライン関数といくつかの定義を記述します。

#define FLOAT_TOL 0.000001

これを行う標準的な方法はあるのでしょうか。いくつかの標準的なヘッダーファイルの可能性がありますか?フロートとダブルの制限(最小値と最大値)があるといいでしょう

29
Dmitriy

あなたの答えをありがとう、彼らは私をたくさん助けてくれました。私はこれらの資料を読みました: 最初 および 2番目

答えは、相対比較に自分の関数を使用することです:

bool areEqualRel(float a, float b, float epsilon) {
    return (fabs(a - b) <= epsilon * std::max(fabs(a), fabs(b)));
}

これは私のニーズに最適なソリューションです。しかし、私はいくつかのテストと他の比較方法を書きました。これが誰かに役立つことを願っています。 areEqualRelはこれらのテストに合格しますが、そうでないものもあります。

#include <iostream>
#include <limits>
#include <algorithm>

using std::cout;
using std::max;

bool areEqualAbs(float a, float b, float epsilon) {
    return (fabs(a - b) <= epsilon);
}

bool areEqual(float a, float b, float epsilon) {
    return (fabs(a - b) <= epsilon * std::max(1.0f, std::max(a, b)));
}

bool areEqualRel(float a, float b, float epsilon) {
    return (fabs(a - b) <= epsilon * std::max(fabs(a), fabs(b)));
}

int main(int argc, char *argv[])
{
    cout << "minimum: " << FLT_MIN      << "\n";
    cout << "maximum: " << FLT_MAX      << "\n";
    cout << "epsilon: " << FLT_EPSILON  << "\n";

    float a = 0.0000001f;
    float b = 0.0000002f;
    if (areEqualRel(a, b, FLT_EPSILON)) {
        cout << "are equal a: " << a << " b: " << b << "\n";
    }
    a = 1000001.f;
    b = 1000002.f;
    if (areEqualRel(a, b, FLT_EPSILON)) {
        cout << "are equal a: " << a << " b: " << b << "\n";
    }
}
5
Dmitriy

から 浮動小数点ガイド

比較される数値が非常に小さい場合、「小さく見える」ために選択された固定イプシロンは実際には非常に大きくなる可能性があるため、これは悪い方法です。比較は、まったく異なる数値に対して「true」を返します。また、数値が非常に大きい場合、イプシロンは最小の丸め誤差よりも小さくなるため、比較は常に「false」を返します。

ここの「マジックナンバー」の問題は、ハードコードされているのではなく、「マジック」であることです。0.000005または0.0000000000001よりも0.000001を選択する理由は本当にありませんでしたか。 floatは、後者とさらに小さい値をほぼ表すことができることに注意してください。これは、最初のゼロ以外の数字の後に7桁の精度(= /// =)です

固定イプシロンを使用する場合は、使用する特定のコードの要件に応じて実際に選択する必要があります。別の方法としては、相対的なエラーマージン(詳細は上部のリンクを参照)を使用するか、さらには 浮動小数点数を整数として比較する を使用します。

17

標準はイプシロン値を提供します。 <limits>にあり、std::numeric_limits<float>::epsilonおよびstd::numeric_limits<double>::epsilonを使用して値にアクセスできます。そこには他の値もありますが、正確には何をチェックしていません。

12
Puppy

std::nextafterを使用して、値の最小のイプシロン(または最小のイプシロンの因数)を持つ2つのdoubleをテストできます。

bool nearly_equal(double a, double b)
{
  return std::nextafter(a, std::numeric_limits<double>::lowest()) <= b
    && std::nextafter(a, std::numeric_limits<double>::max()) >= b;
}

bool nearly_equal(double a, double b, int factor /* a 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;
}
7
Daniel Laügt

2つのfloatを比較して同等である場合、本質的に間違った処理を行っていることに注意してください。比較に傾斜係数を追加するだけでは十分ではありません。

7
ddyer

Float.hの標準定義を使用する必要があります。

#define DBL_EPSILON     2.2204460492503131e-016 /* smallest float value such that 1.0+DBL_EPSILON != 1.0 */

またはnumeric_limitsクラス:

// excerpt
template<>
class numeric_limits<float> : public _Num_float_base
{
public:
    typedef float T;

    // return minimum value
    static T (min)() throw();

    // return smallest effective increment from 1.0
    static T epsilon() throw();

    // return largest rounding error
    static T round_error() throw();

    // return minimum denormalized value
     static T denorm_min() throw();
};

[編集:少しだけ読みやすくしました。]

しかし、さらに、それはあなたが何をしているのかに依存します。

4
0xbadf00d

@geotavrosのソリューションのc ++ 11実装は次のとおりです。新しいstd::numeric_limits<T>::epsilon()関数と、std::fabs()およびstd::fmax()floatdoubleのオーバーロードを持つようになったという事実を利用します。およびlong float

template<typename T>
static bool AreEqual(T f1, T f2) { 
  return (std::fabs(f1 - f2) <= std::numeric_limits<T>::epsilon() * std::fmax(std::fabs(f1), std::fabs(f2)));
}
3

この投稿には、浮動小数点数を比較する方法の包括的な説明があります: http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/

抜粋:

  • ゼロと比較する場合、通常、相対的なイプシロンとULPベースの比較は意味がありません。絶対イプシロンを使用する必要があります。その値は、FLT_EPSILONと計算への入力の小数倍になる可能性があります。多分。
  • ゼロ以外の数値と比較する場合は、相対的なイプシロンまたはULPベースの比較がおそらく必要です。おそらく、相対的なイプシロンにはFLT_EPSILONの小さな倍数、またはいくつかのULPが必要になるでしょう。比較対象の数値が正確にわかっている場合は、絶対イプシロンを使用できます。
1
blufiro