提问者:小点点

在java中加1/3得到1.0,而它不应该


注意:问题仍未得到彻底解答!这个问题不涉及浮点部分的截断问题!!!

在Java中,我有这个简单的代码:

double sum = 0.0;
for(int i = 1; i <= n; i++){
    sum += 1.0/n
}
System.out.println("Sum should be: 1");
System.out.println("The result is: " + sum);

其中n可以是任何整数。对于像7,9这样的数字,和的期望值是和的最后一位数字有差,结果是0。999999999998什么的,但是当我使用3时,输出是1.0

如果你把1/3加3次,你会得到一个接近1的数字,但是我得到的正好是1.0。

为什么?


共3个答案

匿名用户

这是因为除法是以整数进行的。

1/n总是为n给出0

因此,你总是得到sum = 0 1/1 0 0...

尝试使用 1.0 / n

匿名用户

如果你把1/3加3次,你会得到一个接近1的数字,但是我得到的正好是1.0。

实际上,一个没有编程经验的正常人会期望n*1/n等于1,但我们在这里并不正常。

我不能完全重现你的问题,我明白

groovy:000> def foo(n) {
groovy:001>   sum = 0.0
groovy:002>   for (int i = 0; i < n; i++) {
groovy:003>     sum += 1.0 / n
groovy:004>   }
groovy:005>   sum
groovy:006> }
===> true
groovy:000> foo(3)
===> 0.9999999999

这里可能有两个问题,至少你会想知道它们。

一个是双精度不精确,它们不能准确地表示某些值,你只需要期望东西偏离一点点。你的目标不是100%的准确性,而是将误差保持在可接受的范围内。(彼得·劳里(Peter Lawrey)有一篇关于双打的有趣文章,你可能想看看。如果这对你来说不行,你会想避免双打。对于很多用途,BigDecimal 已经足够好了。如果你想要一个库中的问题,你的问题中的除法问题给出了准确的答案,你可以查看这个问题的答案。

另一个问题是System.out.println没有告诉你双精度的确切值,它有点捏造。如果添加如下行:

System.out.println(new java.math.BigDecimal(sum));

然后你就会准确地看到这个替身包含了什么。

匿名用户

我不确定这是否有助于澄清事情,因为我不确定你认为是什么问题。

这是一个使用BigDecimal的测试程序,如前所述,显示中间答案的值。在最后一步,将1.0/3的第三个副本添加到两个副本的总和中,确切的答案在1.0和比它低一倍的下一个副本之间。在这种情况下,四舍五入规则选择1.0。

鉴于此,我认为它应该四舍五入到1.0,与问题标题相矛盾。

测试程序:

import java.math.BigDecimal;

public class Test {
  public static void main(String[] args) {
    final double oneThirdD = 1.0/3;
    final BigDecimal oneThirdBD = new BigDecimal(oneThirdD);
    final double twoThirdsD = oneThirdD + oneThirdD;
    final BigDecimal twoThirdsBD = new BigDecimal(twoThirdsD);
    final BigDecimal exact = twoThirdsBD.add(oneThirdBD);
    final double nextLowerD = Math.nextAfter(1.0, 0);
    final BigDecimal nextLowerBD = new BigDecimal(nextLowerD);
    System.out.println("1.0/3: "+oneThirdBD);
    System.out.println("1.0/3+1.0/3: "+twoThirdsBD);
    System.out.println("Exact sum: "+exact);
    System.out.println("Rounding error rounding up to 1.0: "+BigDecimal.ONE.subtract(exact));
    System.out.println("Largest double that is less than 1.0: "+nextLowerBD);
    System.out.println("Rounding error rounding down to next lower double: "+exact.subtract(nextLowerBD));
  }
}

输出:

1.0/3: 0.333333333333333314829616256247390992939472198486328125
1.0/3+1.0/3: 0.66666666666666662965923251249478198587894439697265625
Exact sum: 0.999999999999999944488848768742172978818416595458984375
Rounding error rounding up to 1.0: 5.5511151231257827021181583404541015625E-17
Largest double that is less than 1.0: 0.99999999999999988897769753748434595763683319091796875
Rounding error rounding down to next lower double: 5.5511151231257827021181583404541015625E-17