web-dev-qa-db-ja.com

System.DoubleでAssert.AreEqual()が非常に混乱する

説明

これは実際の例ではありません! decimalなどの使用を提案しないでください。

なぜこれが起こるのか本当に知りたいので、私はこれを尋ねているだけです。

私は最近、素晴らしいTekpub Webキャストを見ましたJon SkeetでC#4.0をマスターする再び。

エピソードで7 --Decimals and Floating Pointsそれは本当に奇妙になり、私たちのChuck Norris of Programming(別名Jon Skeet)は私の質問に対する本当の答えを持っていません。 のみがである可能性があります。

質問:MyTestMethod()が失敗してMyTestMethod2()が成功したのはなぜですか?

例1

_[Test]
public void MyTestMethod()
{
    double d = 0.1d;
    d += 0.1d;
    d += 0.1d;
    d += 0.1d;
    d += 0.1d;
    d += 0.1d;
    d += 0.1d;
    d += 0.1d;
    d += 0.1d;
    d += 0.1d;

    Console.WriteLine("d = " + d);
    Assert.AreEqual(d, 1.0d);
}
_

d = 1

予想:0.999999999999999989dしかし、だった:1.0d

例2

_[Test]
public void MyTestMethod2()
{
    double d = 0.1d;
    d += 0.1d;
    d += 0.1d;
    d += 0.1d;
    d += 0.1d;

    Console.WriteLine("d = " + d);
    Assert.AreEqual(d, 0.5d);
}
_

d = 0,5

しかし、なぜ ?

更新

なぜAssert.AreEqual()はそれをカバーしないのですか?

20
dknaack

さて、私はAssert.AreEqualが何をするかをチェックしていません...しかし、デフォルトでは not は許容範囲を適用していないと思います。私はそれを私の後ろに期待しませんしません。それでは、別の説明を探しましょう...

あなたは基本的に偶然を見ています-4つの追加 happens の後の答えは正確な値です、おそらく大きさが変わるときにどこかで最下位ビットが失われるためです-私は見ていません関係するビットパターンですが、DoubleConverter.ToExactString(私自身のコード)を使用すると、正確にいつでも値が何であるかを確認できます。

using System;

public class Test
{    
    public static void Main()
    {
        double d = 0.1d;
        Console.WriteLine("d = " + DoubleConverter.ToExactString(d));
        d += 0.1d;
        Console.WriteLine("d = " + DoubleConverter.ToExactString(d));
        d += 0.1d;
        Console.WriteLine("d = " + DoubleConverter.ToExactString(d));
        d += 0.1d;
        Console.WriteLine("d = " + DoubleConverter.ToExactString(d));
        d += 0.1d;        
        Console.WriteLine("d = " + DoubleConverter.ToExactString(d));
    }
}

結果(私の箱に):

d = 0.1000000000000000055511151231257827021181583404541015625
d = 0.200000000000000011102230246251565404236316680908203125
d = 0.3000000000000000444089209850062616169452667236328125
d = 0.40000000000000002220446049250313080847263336181640625
d = 0.5

これで、別の番号から始めると、同じように機能しません。

(d = 10.1から開始)

d = 10.0999999999999996447286321199499070644378662109375
d = 10.199999999999999289457264239899814128875732421875
d = 10.2999999999999989341858963598497211933135986328125
d = 10.39999999999999857891452847979962825775146484375
d = 10.4999999999999982236431605997495353221893310546875

つまり、基本的に、テストで運が良かったり不運になったりしました。エラーはキャンセルされました。

9
Jon Skeet

Assert.AreEqual()doesそれをカバーします; 3番目のdelta引数でオーバーロードを使用する必要があります。

Assert.AreEqual(0.1 + 0.1 + 0.1, 0.3, 0.00000001);
61
phoog

Doublesは、すべての浮動小数点数と同様に、 近似値、絶対値ではありません バイナリ(基数2)表現。基数10の分数を完全に表すことができない場合があります(基数10が1/3を完全に表すことができないのと同じ方法)。したがって、等価比較を実行したときに2番目の値がたまたま正しい値に丸められるという事実(および最初の値が丸められないという事実)は運だけであり、フレームワークなどのバグではありません。

また、これを読んでください: float変更結果を返すメソッドでfloatに結果をキャストする

Assert.Equalsは、このケースをカバーしていません。 驚き最小の原則 は、.NETの他のすべての組み込み数値型が.Equals()を定義して==と同等の演算を実行するため、 Doubleもそうします。実際、テストで生成している2つの数値(リテラル0.5dと.1dの5x合計)は==等しくないため(プロセッサのレジスタの実際の値は異なります)Equals()はfalse。

あなたの生活を便利にするために、一般的に受け入れられているコンピューティングのルールを破ることはフレームワークの意図ではありません。

最後に、NUnitが実際にこの問題を認識していることを提案します。 http://www.nunit.org/index.php?p=equalConstraint&r=2.5 によると、浮動小数点をテストするための次の方法が提供されます。許容範囲内の平等:

Assert.That( 5.0, Is.EqualTo( 5 );
Assert.That( 5.5, Is.EqualTo( 5 ).Within(0.075);
Assert.That( 5.5, Is.EqualTo( 5 ).Within(1.5).Percent;
11
Chris Shain

これは、コンピューターの浮動小数点演算の機能です( http://www.eskimo.com/~scs/cclass/progintro/sx5.html

通常、浮動小数点数の精度は制限されており、これは驚くべき結果につながる可能性があることを覚えておくことが重要です。 1/3のような除算の結果は正確に表すことができないため(無限に繰り返される分数、0.333333 ...)、計算(1/3)x 3は、1.0ではなく0.999999 ...のような結果を生成する傾向があります。さらに、基数2では、分数1/10、つまり10進数で0.1も無限に繰り返される分数であり、正確に表すこともできないため、(1/10)x10でも0.999999 ...になります。理由など、浮動小数点の計算が正確になることはめったにありません。コンピューターの浮動小数点を使用する場合、正確に等しいかどうか2つの数値を比較しないように注意する必要があります。また、計算結果が大幅に低下するまで「丸め誤差」が累積しないようにする必要があります。

アサートの精度を明示的に設定する必要があります

例えば:

double precision = 1e-6;
Assert.AreEqual(d, 1.0, precision);

それはあなたのサンプルのための仕事です。私はコードでこの方法をよく使用しますが、状況に応じた精度

3
Alexander

これは、浮動小数点数の精度が低下するためです。等しいものを比較する最良の方法は、数値を減算し、その差が.001などの特定の数値よりも小さいことを確認することです(または必要な精度で)。 http://msdn.Microsoft.com/en-us/library/system.double%28v=VS.95%29.aspx 特に浮動小数点値と精度の低下のセクションをご覧ください。

1
Brad Semrad

0.1は内部形式であるため、doubleで正確に表すことはできません。

基数10の数値を表す場合は、10進数を使用します。

ダブルスを比較したい場合は、それらが互いに非常に少量であるかどうかを確認してください。

0
George Duckett