比较Java中的小双值

时间:2017-01-17 11:35:48

标签: java floating-point precision

为什么这个断言在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),断言也通过了。

请有人详细解释一下:

  1. 为什么断言通过eps = 1.0E-13而不是eps = 1.0E-14
  2. 为什么断言通过eps = Double.MIN_VALUE(4.9E-324)而不是eps = 1.0E-14
  3. 编辑:当我将eps增加到1.0E-8时,断言也失败了:double eps = 0.00000001;

4 个答案:

答案 0 :(得分:1)

这是因为组织了代表double类型的字节。

如下图所示,它是64位结构。位[b0 .. b51]是连接的&#39;并且由指数提升,[b52 .. b62]。

Representation of the double type

确定每个比特组合在实际值中代表什么的等式是:

Double formula

使用此公式,您可以使用

表示最小值
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 - eps9.99999999。到现在为止还挺好。但是,10.0 - 9.999999990.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

都应该稳定