这不是一个现实世界的例子!请不要建议使用decimal
或其他内容。
我只是问这个,因为我真的想知道为什么会这样。
我最近再次看到了令人敬畏的Tekpub网络广播使用Jon Skeet 掌握C#4.0。
在剧集 7 - 小数点和浮点数中,这真的很奇怪,甚至我们的 编程的Chuck Norris(又名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);
}
这导致了
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);
}
这导致成功
d = 0,5
但为什么?
为什么Assert.AreEqual()
没有涵盖?
答案 0 :(得分:57)
Assert.AreEqual()
确实涵盖了这一点;你必须使用带有第三个delta
参数的重载:
Assert.AreEqual(0.1 + 0.1 + 0.1, 0.3, 0.00000001);
答案 1 :(得分:11)
因为双打与所有浮点数一样,是 近似值,而不是绝对值二进制(base-2)表示,它们可能无法完美地表示基数 - 10个分数(与基数10完全不能代表1/3的方式相同)。因此,当你执行相等比较时(第一个没有)事实上第二个碰巧会转到正确的值,这只是运气,而不是框架中的错误或其他任何错误。
另外,请阅读:Casting a result to float in method returning float changes result
Assert.Equals不包括这种情况,因为the principle of least astonishment表明由于.NET中的每个其他内置数值类型都定义.Equals()来执行==的等效操作,所以Double这样做好。实际上,您在测试中生成的两个数字(文字0.5d和.xd的5x总和)不是==
相等(处理器寄存器中的实际值不同)Equals()返回假的。
为了让您的生活更方便,打破普遍接受的计算规则并不是框架的意图。
最后,我提出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;
答案 2 :(得分: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));
}
}
结果(在我的方框中):
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
所以基本上你碰巧遇到了你的考试很幸运或不幸 - 错误自行取消了。
答案 3 :(得分:5)
Assert.AreEqual
确实考虑到了这一点。
但是为了做到这一点,你需要提供误差幅度 - 两个浮点值之间差值的增量被视为等于您应用的 。
Assert.AreEqual
有两个重载只有两个参数 - 一个通用的(T, T)
和一个非通用的 - (object, object)
。这些只能进行默认比较。
使用其中一个带double
的重载,并且还有一个delta参数。
答案 4 :(得分:3)
这是计算机浮点算术的特征 (http://www.eskimo.com/~scs/cclass/progintro/sx5.html)
重要的是要记住浮点的精度 数字通常是有限的,这可能会导致令人惊讶的结果。 像1/3这样的除法结果无法准确表示(它是 一个无限重复的分数,0.333333 ...),所以计算(1 / 3)x 3倾向于产生像0.999999 ...而不是1.0的结果。 此外,在基数2中,小数的1/10或0.1也是 无限重复的部分,无法准确表示, 或者,所以(1/10)x 10也可能产生0.999999 ....由于这些原因 和其他人一样,浮点计算很少是准确的。工作时 有了计算机浮点,你必须要小心不要比较 两个数字是完全相等的,你必须确保``round 关闭错误''不会累积,直到它严重降低结果 你的计算。
您应该明确设置Assert的精度
例如:
double precision = 1e-6;
Assert.AreEqual(d, 1.0, precision);
这适合你的样本。我经常在我的代码中使用这种方式,但精确度取决于具体情况
答案 5 :(得分:1)
这是因为浮点数会失去精度。比较equals的最佳方法是减去数字,并验证差异是否小于某个数字,如.001(或您需要的精度)。请特别注意http://msdn.microsoft.com/en-us/library/system.double%28v=VS.95%29.aspx浮点值和精度损失部分。
答案 6 :(得分:0)
0.1
因为它的内部格式而无法在双精度中完全表示。
如果要表示基数为10的数字,请使用小数。
如果你想比较双打,请检查它们是否相互之间的数量非常小。