2

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

在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 这样的数字,sum 的预期值是 sum 的最后一位数字有差异,结果是 0.999999999998 或其他东西,但我使用 3 时的输出是1.0.

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

为什么?

4

4 回答 4

3

这是因为除法是整数。

1/n对于 n > 1,总是给出 0。

因此,你总是以 sum = 0 + 1/1 + 0 + 0 ...

尝试1.0 / n

于 2013-03-22T16:47:48.200 回答
2

如果你加 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 没有告诉你 double 的确切值,它有点捏造。如果您添加如下行:

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

然后您将准确了解双重包含的内容。

于 2013-03-22T17:33:25.270 回答
1

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

这是一个使用 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
于 2013-03-23T04:05:31.110 回答
0

一个 int 除以一个 int 总是会产生另一个 int。现在 int 没有地方存储数字的小数部分,所以它被丢弃了。请记住,它被丢弃而不是四舍五入。

因此 1 / 3 = 0.3333333,小数部分被丢弃,这意味着它变为 0。

如果您将数字指定为双精度数(通过包括小数点,例如 1. 或 1.0),那么结果将是双精度数(因为 java 自动将 int 转换为双精度数)并且小数部分将被保留。

在您更新的问题中,您将 i 设置为 1.0,但 i 仍然是一个 int。所以 1.0 被截断为 1,为了进一步计算,它仍然是一个 int。您还需要将 i 的类型更改为 double ,否则代码将没有区别。

或者,您可以使用 sum += 1.0/n

这将具有在执行计算之前将 n 转换为 double 的效果

于 2013-03-22T16:51:21.193 回答