为什么这个断言在Java中失败:
double eps = 0.00000000000001;
double ten = 10.0;
double result = (ten - (ten - eps));
Assert.assertTrue(result <= eps);
如果我在eps
中的数字1之前删除一个零,则断言通过。我假设这与浮点实现有关,但我不确定如何。
此外,如果我将数字1替换为2(如0.00000000000002),则断言也会通过。在这种情况下,我甚至可以在数字2之前添加更多的零,测试仍然会通过。我尝试了Double.MIN_VALUE
(4.9E-324),断言也通过了。
请有人详细解释一下:
Double.MIN_VALUE
(4.9E-324)而不是eps = 1.0E-14 编辑:当我将eps
增加到1.0E-8时,断言也失败了:double eps = 0.00000001;
答案 0 :(得分:1)
这是因为组织了代表double
类型的字节。
如下图所示,它是64位结构。位[b0 .. b51]是连接的&#39;并且由指数提升,[b52 .. b62]。
确定每个比特组合在实际值中代表什么的等式是:
使用此公式,您可以使用
表示最小值3ff0 0000 0000 000116 => 1.0000000000000002
有关更好的说明,请参阅此Wiki页面Double-precision floating-point format
答案 1 :(得分:1)
在最后一个断言中,你比较结果(1.0658141036401503E-14)和eps(1.0E-14),从主观上说,如断言所认为的那样是错误的,这种情况下的结果大于eps。如果从eps rps中删除一个0,则在这种情况下变为大于1.0658141036401503E-14的1.0E-13
答案 2 :(得分:1)
问题是断言代码在某种意义上是错误的,它没有考虑第二个减法ten - (ten - eps)
。
让我们一步一步解释。设eps = 0.00000001
(1.0E-8)。在这种情况下,10.0 - eps
为9.99999999
。到现在为止还挺好。但是,10.0 - 9.99999999
是0.00000001000000082740371
,它大约是0.00000001
的预期结果,但只是略大一点,因为浮点运算(通常)给出了足够好的近似值。因此,对于某些eps
值,最终结果非常接近,但恰好低于实际结果,对于某些值,它又非常接近,但仅高于实际结果。
需要修复代码,以便考虑到第二个减法的结果也只是一个近似值。
一种方法是将断言更改为:
Assert.assertTrue(Math.abs(result - eps) <= eps);
为了更多地了解浮点算术,我发现这篇文章写得很好:http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
这句话总结了浮点算术错误发生的原因:
有两个原因导致实数可能不完全正确 可表示为浮点数。最常见的情况是 以十进制数0.1表示。虽然它有限 十进制表示,二进制表示无限重复 表示。因此,当β= 2时,数字0.1严格地位于 两个浮点数,并且两者都不能完全表示 它们。
答案 3 :(得分:-1)
请尝试以下代码:
BigDecimal eps1 = new BigDecimal(eps);
BigDecimal ten1 = new BigDecimal(ten);
BigDecimal result1 = ten1.subtract( ten1.subtract(eps1) );
无论eps
都应该稳定