web-dev-qa-db-ja.com

特定の固定許容範囲内ではなく、特定の精度に基づいて2つのdoubleが等しいかどうかを評価します

NUnitテストを実行して、いくつかの既知のテストデータと計算結果を評価しています。数値は浮動小数点の2倍であるため、正確に等しいとは思いませんが、特定の精度で等しいものとして扱う方法がわかりません。

NUnitでは、固定許容値と比較できます。

double expected = 0.389842845321551d;
double actual   = 0.38984284532155145d; // really comes from a data import
Expect(actual, EqualTo(expected).Within(0.000000000000001));

これはゼロ未満の数値では問題なく機能しますが、数値が大きくなるにつれて許容誤差を実際に変更する必要があるため、常に同じ桁数の精度を重視します。

具体的には、このテストは失敗します。

double expected = 1.95346834136148d;
double actual   = 1.9534683413614817d; // really comes from a data import
Expect(actual, EqualTo(expected).Within(0.000000000000001));

そしてもちろん、より大きな数は許容範囲で失敗します。

double expected = 1632.4587642911599d;
double actual   = 1632.4587642911633d; // really comes from a data import
Expect(actual, EqualTo(expected).Within(0.000000000000001));

与えられた精度で2つの浮動小数点数が等しいと評価する正しい方法は何ですか? NUnitでこれを行うための組み込みの方法はありますか?

31
Samuel Neff

Msdnから:

デフォルトでは、Double値には10進数の15桁の精度が含まれますが、内部では最大17桁が維持されます。

では、15としましょう。

したがって、許容範囲を同程度にしたいと言えます。

小数点以下の正確な数字はいくつありますか?小数点から最上位桁までの距離を知る必要がありますよね?マグニチュード。これはLog10で取得できます。

次に、1を10 ^の精度で除算して、必要な精度の値を取得する必要があります。

今、あなたは私が持っているよりも多くのテストケースを行う必要があるでしょう、しかしこれはうまくいくようです:

  double expected = 1632.4587642911599d;
  double actual = 1632.4587642911633d; // really comes from a data import

  // Log10(100) = 2, so to get the manitude we add 1.
  int magnitude = 1 + (expected == 0.0 ? -1 : Convert.ToInt32(Math.Floor(Math.Log10(expected))));
  int precision = 15 - magnitude ;

  double tolerance = 1.0 / Math.Pow(10, precision);

  Assert.That(actual, Is.EqualTo(expected).Within(tolerance));

遅いです-ここに落とし穴があるかもしれません。私はあなたの3セットのテストデータに対してそれをテストし、それぞれが合格しました。 pricision16 - magnitudeに変更すると、テストが失敗しました。 14 - magnitudeに設定すると、許容範囲が大きくなるため、明らかに合格しました。

17
Brett

これは私が思いついたものです 浮動小数点ガイド (Javaコードですが、簡単に翻訳できるはずであり、本当に必要なテストスイートが付属しています):

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 == 0) { // a or b or both are zero
        // relative error is not meaningful here
        return diff < (epsilon * epsilon);
    } else { // use relative error
        return diff / (absA + absB) < epsilon;
    }
}

本当に難しい質問は、比較する数値の1つがゼロの場合にどうするかです。最良の答えは、そのような比較では、普遍的であろうとするのではなく、比較される数値のドメインの意味を常に考慮する必要があるということかもしれません。

9

それぞれの項目を文字列に変換して比較してみませんか?

string test1 = String.Format("{0:0.0##}", expected);
string test2 = String.Format("{0:0.0##}", actual);
Assert.AreEqual(test1, test2);
6
John K.

Nunitでそれを行う組み込みの方法があるかどうかはわかりませんが、各フロートに求めている精度の10倍を掛け、結果をlongとして格納し、2つのlongを相互に比較することをお勧めします。
例えば:

double expected = 1632.4587642911599d;
double actual   = 1632.4587642911633d;
//for a precision of 4
long lActual = (long) 10000 * actual;
long lExpected = (long) 10000 * expected;

if(lActual == lExpected) {  // Do comparison
   // Perform desired actions
}
4
nybbler

これは簡単なアイデアですが、ゼロ未満になるまでシフトダウンしてはどうでしょうか。 num/(10^ceil(log10(num)))のようなものでなければなりません。 。 。それがどれだけうまくいくかはわかりませんが、それはアイデアです。

1632.4587642911599 / (10^ceil(log10(1632.4587642911599))) = 0.16324587642911599
1
troutinator

2つの値の差は、どちらかの値を精度で割った値よりも小さくする必要があります。

Assert.Less(Math.Abs(firstValue - secondValue), firstValue / Math.Pow(10, precision));
0
Robin Bennett

どうですか:

const double significantFigures = 10;
Assert.AreEqual(Actual / Expected, 1.0, 1.0 / Math.Pow(10, significantFigures));
0
Pughjl