27

我正在查看旧的考试问题(目前是大学的第一年),我想知道是否有人可以更彻底地解释为什么下面的for循环没有在它应该结束的时候结束。为什么会这样?我知道由于舍入错误或其他原因,它会跳过 100.0,但为什么呢?

for(double i = 0.0; i != 100; i = i +0.1){
    System.out.println(i);
}
4

5 回答 5

42

数字 0.1 不能用二进制精确表示,就像 1/3 不能用十进制精确表示一样,因此您不能保证:

0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1==1

这是因为在二进制中:

0.1=(binary)0.00011001100110011001100110011001....... forever

然而,双精度不能包含无限精度,因此,正如我们将 1/3 近似为 0.3333333 一样,二进制表示也必须近似于 0.1。


扩展十进制类比

在十进制中,您可能会发现

1/3+1/3+1/3
=0.333+0.333+0.333
=0.999

这是完全相同的问题。它不应该被视为浮点数的弱点,因为我们自己的十进制系统也有同样的困难(但对于不同的数字,使用 base-3 系统的人会发现我们很难表示 1/3)。然而,这是一个需要注意的问题。

演示

Andrea Ligios 提供的现场演示显示了这些错误的累积

于 2013-11-11T13:09:05.690 回答
12

计算机(至少是当前计算机)使用二进制数据。此外,计算机在其算术逻辑单元(即 32 位、64 位等)中进行处理存在长度限制。以二进制形式表示整数很简单,相反我们不能对浮点数说同样的话。64位浮点表示

如上所示,根据 IEEE-754 有一种表示浮点的特殊方式,这也被处理器生产商和软件人员接受为事实上的方式,这就是为什么每个人都知道它很重要的原因。

如果我们看一下 java (Double.MAX_VALUE) 中 double 的最大值是 1.7976931348623157E308 (>10^307)。只有 64 位,才能表示巨大的数字,但问题是精度。

由于 '==' 和 '!=' 运算符按位比较数字,在您的情况下 0.1+0.1+0.1 不等于 0.3 就它们所表示的位而言。

总而言之,为了在几位中拟合巨大的浮点数,聪明的工程师决定牺牲精度。如果您正在处理浮点数,则除非您确定自己在做什么,否则不应使用 '==' 或 '!='。

于 2013-11-11T15:07:43.933 回答
11

作为一般规则,double由于舍入错误,永远不要使用 to 迭代(0.1 在以 10 为基数编写时可能看起来不错,但尝试以 2 为基数编写它——这就是double使用的方法)。你应该做的是使用一个普通的int变量来迭代和计算double它。

for (int i = 0; i < 1000; i++)
  System.out.println(i/10.0);
于 2013-11-11T13:11:50.660 回答
8

首先,我要解释一些关于双打的事情。为了便于理解,这实际上将在十进制中进行。

取三分之一的值并尝试用十进制表示。你得到 0.3333333333333.... 假设我们需要将它四舍五入到 4 位。我们得到 0.3333。现在,让我们再添加一个 1/3。我们得到 0.6666333333333.... 四舍五入为 0.6666。让我们再添加一个 1/3。我们得到 0.9999,而不是1。

同样的事情发生在基数二和十分之一。由于您将通过 0.1 10和 0.1 10是一个重复的二进制值(例如 0.1666666... 以十为基数),所以当您到达那里时,您将有足够的错误来错过一百。

1/2 可以用十进制表示就好了,1/5 也可以。这是因为分母的质因数是基因数的子集。这不是以十为底的三分之一或以二为底的十分之一的情况。

于 2013-11-11T13:10:18.870 回答
0

它应该是 for(double a = 0.0; a < 100.0; a = a + 0.01)

试试看这是否可行

于 2013-11-15T15:43:53.923 回答