-1
public class yyy
{
    public static void main(String[] args)
    {
        double sum = 0;
        float d = 0;
        while (d != 10.0) {
            d += 0.1;
            sum += sum + d;
        }
        System.out.print("The sum is: "+sum);
   }
}
  • Where is the error which causes the infinite loop?
  • Why?
  • I thought that the type is the problem but its not; I changed d to double but nothing changed.
4

5 回答 5

5

这是一个无限循环,因为d永远不等于10.0. 为什么不?因为浮点数不是完全精确的,所以当你添加0.1d. 您可以在调试器中观看,逐步执行代码。Java 中使用的浮点类型是 IEEE-754,更多信息在这里

以下是d接近的值,然后跳过,10.0

d = 9.4
d = 9.5
d = 9.6
d = 9.700001
d = 9.800001
d = 9.900002
d = 10.000002
于 2013-10-26T12:16:20.480 回答
3

使循环无限的原因是float' 表示的不精确性0.1:因为它不是 2 的负幂的精确总和,所以将其与自身相加 100 次并不完全是10.0,即使理论上它确实如此。这个数字非常接近,但由于您使用!=,循环永远不会停止。

用。。。来代替

while (d < 10.05)

解决无限循环问题。然而,这不是最易读的方法:使用intfor 你的循环计数器会更干净。

请注意,如果您使用了不同的步骤,例如,0.125您的循环将起作用。这是因为0.125is 2 ^ -3,它具有作为 a 的精确表示float

我改为ddouble没有任何改变。

double类型具有与 相同的逻辑表示float,但具有更多位以扩展其范围和精度。但是,使用BigDecimal可以解决问题,因为此数据类型使用不同的表示,可以0.1精确表示。

您应该从本练习中学到的要点是,在比较浮点值的相等性和不等性时需要非常小心。

于 2013-10-26T12:16:16.647 回答
2

无限循环是由于计算机表示浮点数的方式不准确。

计算机只能为每个变量分配一定数量的位。您可以将其视为在小数点后存储有限数量的数字。在以十为底的情况下,您可以将 1/10 精确地表示为 0.1,因此您可以d在 0.0、0.1、0.2、...、10.0 的过程中以完美的精度书写。但想象一下以 1/7 递增。这是一个重复的小数 (0.142857142857...),因此无法以有限的位数以 10 为基数精确表示。如果计算机只能为每个变量存储 2 位小数,则d看起来像这样:

分数   实际值   储值
   1/7 0.142857... 0.14
   2/7 0.285714... 0.28
   3/7 0.428571... 0.42
   4/7 0.571428... 0.56 ←不匹配
   5/7 0.714285... 0.70
   6/7 0.857142... 0.84
   7/7 1.0 0.99
请注意从 4/7 开始的每个值的舍入误差。(实际上,由于截断,每个值都有一个舍入误差;当数字不匹配时会更加明显。)要注意的重要一点是,我们存储多少位并不重要;除非它是无限的,否则总会有舍入误差。

因此,在基数为 10 的情况下,将变量增加 0.1 既简单又“干净”,因为该数字可以用有限的位数精确表示。但对于由重复小数表示的数字,例如 1/6、1/7、1/13 等,情况并非如此。

计算机以二进制(以 2 为底)存储数字,但概念完全相同。分数 1/10 没有以 2 为底的精确表示。计算机必须通过将 2 的不同幂加在一起来表示每个数字:8、4、2、1、½ ¼、⅛ 等数字。例如:

15 = 8 + 4 + 2 + 1 = 1111 b 
10 = 8 + 0 + 2 + 0 = 1010 b 
2½ = 0 + 0 + 2 + 0 + ½ = 0010.1 b 
 ¾ = 0 + 0 + 0 + 0 + ½ + ¼ = 0010.11 b 

但我们不能仅使用 2 的幂来准确表示 1/10:

1/10 = 0/2 + 0/4 + 0/8 + 1/16    + 1/32    + 0/64 + 0/128 + 1/256 +
      1/512 + 0/1024 + 0/2048 + 1/ 4096 + 1/8192 + ...
1/10 = 0.0001100110011... b

同样,假设我们的小数位数有限。你会看到,无论我们使用多少,如果我们继续添加 1/10,最终都会产生舍入误差。这正是您的程序中发生的情况:重复添加 1/10 的二进制表示将在总和达到 10.0 之前产生舍入误差,因此条件d != 10.0始终为真。

正因为如此,在处理浮点数时,最好的做法是像其他几个人建议的那样:永远不要测试浮点变量是否相等;总是使用不等式。您可以使用 消除无限循环while (d < 10.0)

于 2013-10-26T13:07:59.343 回答
0

执行此循环的最佳方法是使用固定精度。即使用一个整数值,假设您在最后添加一位小数。如您所见,出错的余地非常小。

int sum = 0;
for(int i = 0; i <= 100; i++)
   sum += i;
// sum is 10x the value you need.
System.out.print("The sum is: " + sum/10.0); 

这有帮助的原因是,否则您将执行一系列不精确的计算并寻找 10.0 的精确结果,这是极不可能的。即累积误差可能为零,但您不想假设它是针对不同的值。

于 2013-10-27T06:49:01.737 回答
0

双精度和浮点数具有所谓的浮点精度,即除了您要添加的 0.1 之外,还有更多您看不到的小数位。正如 TJ Crowder 所说,您可以通过调试器逐步查看这些值。我的建议是在比较值时使用整数,否则请查看建议。

于 2013-10-26T12:17:13.273 回答