使用System.Double的Assert.AreEqual()变得非常混乱

dkn*_*ack 19 .net c# floating-point clr c#-4.0

描述

这不是一个现实世界的例子!请不要建议使用decimal或其他东西.

我只是问这个,因为我真的想知道为什么会这样.

我最近再次与Jon Skeet一起看到了令人敬畏的Tekpub网络广播C#4.0.

在第7- 小数和浮点数它真的很奇怪,甚至我们的 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);
}
Run Code Online (Sandbox Code Playgroud) 这导致了

d = 1

预期:0.99999999999999989d但是: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);
}
Run Code Online (Sandbox Code Playgroud) 这导致成功

d = 0.5

但为什么 ?

更新

为什么不Assert.AreEqual()涵盖那个?

pho*_*oog 64

Assert.AreEqual() 确实涵盖了那个; 你必须使用带有第三个delta参数的重载:

Assert.AreEqual(0.1 + 0.1 + 0.1, 0.3, 0.00000001);
Run Code Online (Sandbox Code Playgroud)

  • @cikatomo 我明白了。在这种情况下,您可以[提出新问题](/sf/)。 (2认同)

Chr*_*ain 11

因为像所有浮点数一样,双打是近似值,而不是绝对值二进制(基数-2)表示,它可能无法完美地表示基数为10的分数(与基数10不能完全代表1/3的方式相同) .因此,当您执行相等比较(以及第一个没有的事实)时,第二个恰好舍入到正确值的事实只是运气,而不是框架中的错误或其他任何东西.

另外,请阅读:将结果转换为float方法返回float更改结果

Assert.Equals不包括这种情况,因为最小惊讶的原则指出,因为.NET中的每个其他内置数值类型都定义.Equals()来执行==的等效操作,所以Double也这样做.实际上,您在测试中生成的两个数字(文字0.5d和.xd的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;
Run Code Online (Sandbox Code Playgroud)


Jon*_*eet 9

好吧,我没有检查是什么Assert.AreEqual...但我怀疑默认情况下它没有应用任何容差.我不希望它落后于我.那么让我们寻找另一种解释......

你基本上看到了一个巧合 - 四次添加之后的答案恰好是确切的值,可能是因为当幅度发生变化时,最低位会丢失 - 我没有看过所涉及的位模式,但如果你使用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));
    }
}
Run Code Online (Sandbox Code Playgroud)

结果(在我的方框中):

d = 0.1000000000000000055511151231257827021181583404541015625
d = 0.200000000000000011102230246251565404236316680908203125
d = 0.3000000000000000444089209850062616169452667236328125
d = 0.40000000000000002220446049250313080847263336181640625
d = 0.5
Run Code Online (Sandbox Code Playgroud)

现在,如果你从一个不同的数字开始,它不会以同样的方式自行运行:

(从d = 10.1开始)

d = 10.0999999999999996447286321199499070644378662109375
d = 10.199999999999999289457264239899814128875732421875
d = 10.2999999999999989341858963598497211933135986328125
d = 10.39999999999999857891452847979962825775146484375
d = 10.4999999999999982236431605997495353221893310546875
Run Code Online (Sandbox Code Playgroud)

所以基本上你碰巧幸运或不幸的是你的测试 - 错误取消了自己.

  • 您应该使用Assert.AreEqual(double,double,double)重载,而不是转换为字符串,其中最后一个double是指示比较准确性的delta值,如下所述,以及此处: http://msdn.microsoft.com/en-us/library/ms243458.aspx (3认同)

Ode*_*ded 6

Assert.AreEqual 确实考虑到了这一点。

但是,为了这样做,您需要提供误差范围- 对于您的应用程序,两个浮点值之间的差之内的增量被视为相等。

有两个Assert.AreEqual仅包含两个参数的重载-通用一个(T, T)和非通用一个- (object, object)。这些只能进行默认比较。

使用要承受的重载之一,该重载double也应具有增量的参数。