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()通过?[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
[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)
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)
好吧,我没有检查是什么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 确实考虑到了这一点。
但是,为了这样做,您需要提供误差范围- 对于您的应用程序,两个浮点值之间的差之内的增量被视为相等。
有两个Assert.AreEqual仅包含两个参数的重载-通用一个(T, T)和非通用一个- (object, object)。这些只能进行默认比较。
使用要承受的重载之一,该重载double也应具有增量的参数。