为什么使用double的for循环无法终止

Pat*_*ati 27 java for-loop floating-point-precision

我正在查看旧的考试问题(目前是大学的第一年).我想知道是否有人能够更彻底地解释为什么以下for循环在它应该结束时不会结束.为什么会这样?我知道它因为四舍五入错误而跳过100.0,但为什么呢?

for(double i = 0.0; i != 100; i = i +0.1){
    System.out.println(i);
}
Run Code Online (Sandbox Code Playgroud)

Ric*_*gle 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
Run Code Online (Sandbox Code Playgroud)

这是因为二进制:

0.1=(binary)0.00011001100110011001100110011001....... forever
Run Code Online (Sandbox Code Playgroud)

然而,double不能包含无限精度,因此,正如我们接近1/3到0.3333333所以二进制表示必须接近0.1.


扩展的十进制类比

在十进制中你可能会发现

1/3+1/3+1/3
=0.333+0.333+0.333
=0.999
Run Code Online (Sandbox Code Playgroud)

这是完全相同的问题.它不应被视为浮点数的弱点,因为我们自己的十进制系统具有相同的困难(但对于不同的数字,具有base-3系统的人会觉得奇怪,我们努力代表1/3).然而,这是一个需要注意的问题.

演示

一个现场演示由安德烈Ligios提供显示这些错误建立.


hev*_*evi 11

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

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

如果我们查看java中的double的最大值(Double.MAX_VALUE)是1.7976931348623157E308(> 10 ^ 307).只有64位,可以表示大量数字,但问题是精度.

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

作为结论,为了在几位中适应巨大的浮点数,聪明的工程师决定牺牲精度.如果你正在处理浮点数,你不应该使用'=='或'!=',除非你确定你在做什么.


Mar*_*nik 10

作为一般规则,永远不要double因为舍入错误而使用迭代(0.1在写入基数10时可能看起来不错,但尝试将其写入基数2 - 这是double使用的).你应该做的是使用一个普通的int变量来迭代和计算double它.

for (int i = 0; i < 1000; i++)
  System.out.println(i/10.0);
Run Code Online (Sandbox Code Playgroud)

  • 对于迭代器变量来说,使用浮点数并不是一个特别的问题;问题是使用 `!=` 作为终止条件!如果循环结构为`for(double i = 0.0; i &lt; 100; i = i +0.1)`,它会很好地终止。我建议_更好_的一般规则是始终对浮点数使用不等式。许多语言都有一个名为“Epsilon”或类似的内置常量(检查您选择的语言的文档),您可以像这样使用它:`if (myDouble - double.Epsilon &lt; target &amp;&amp; target &lt; myDouble + double.Epsilon ) {...}` (2认同)
  • @BrianS 如果您有 i=(10*i+1)/10 ,您将不会有累积错误(或任何其他不累积的方式,而只是分配)。并不是说这是一个特别好的主意,只是提到它。 (2认同)

ζ--*_*ζ-- 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 ......),当你到达那里时,你将有足够的错误错过100.

1/2可以用十进制表示,也可以用1/5表示.这是因为分母的主要因素是基数因子的子集.对于基数为10的三分之一或基数为二的十分之一,情况并非如此.