41

编码:

<?php
$start = 0;
$stop  = 1;
$step = ($stop - $start)/10;
$i = $start + $step;
while ($i < $stop) {
    echo($i . "<br/>");
    $i += $step;
}
?>

输出:

0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
1 <-- notice the 1 printed when it shouldn't

创造了一个小提琴

还有一个:如果你设置$start = 1并且$stop = 2它工作正常。

使用:php 5.3.27

为什么是1印刷品?

4

3 回答 3

53

因为不仅浮点数学有缺陷,有时它的表示也有缺陷——这里就是这种情况。

你实际上并没有得到 0.1, 0.2, ... - 这很容易检查:

$start = 0;
$stop  = 1;
$step = ($stop - $start)/10;
$i = $start + $step;
while ($i < $stop) {
   print(number_format($i, 32) . "<br />");
   $i += $step;
}

如您所见,这里唯一的区别是echo替换为number_format调用。但结果却截然不同:

0.10000000000000000555111512312578
0.20000000000000001110223024625157
0.30000000000000004440892098500626
0.40000000000000002220446049250313
0.50000000000000000000000000000000
0.59999999999999997779553950749687
0.69999999999999995559107901499374
0.79999999999999993338661852249061
0.89999999999999991118215802998748
0.99999999999999988897769753748435

看?实际上只有一次0.5——因为这个数字可以存储在一个浮动容器中。所有其他的只是近似值。

如何解决这个问题?好吧,一种激进的方法是在类似情况下不使用浮点数,而是使用整数。很容易注意到你是否这样做过......

$start = 0;
$stop  = 10;
$step = (int)(($stop - $start) / 10);
$i = $start + $step;
while ($i < $stop) {
   print(number_format($i, 32) . "<br />");
   $i += $step;
}

...它会工作正常:

或者,您可以使用number_format将浮点数转换为某个字符串,然后将此字符串与预格式化的浮点数进行比较。像这样:

$start = 0;
$stop  = 1;
$step = ($stop - $start) / 10;
$i = $start + $step;
while (number_format($i, 1) !== number_format($stop, 1)) {
   print(number_format($i, 32) . "\n");
   $i += $step;
}
于 2013-09-05T21:15:36.263 回答
13

问题是变量 $i 中的数字不是 1(打印时)。它实际只是小于1。所以在测试中($i < $stop) 为真,数字被转换为十进制(导致四舍五入为1),并显示出来。

现在为什么 $i 不完全是 1 ?这是因为您通过说 10 * 0.1 到达那里,而 0.1 不能完美地用二进制表示。只有可以表示为 2 的有限次幂之和的数字才能被完美地表示。

那么为什么 $stop 正好是 1?因为它不是浮点格式。换句话说,它从一开始就是精确的——它不是在系统使用的浮点 10 * 0.1 内计算的。

在数学上,我们可以这样写: 在此处输入图像描述

一个 64 位二进制浮点数只能保存总和的前 27 个非零项,近似于 0.1。有效数的其他 26 位保持为零以指示零项。0.1 在物理上不可表示的原因是所需的项序列是无限的。另一方面,像 1 这样的数字只需要少量的有限项并且是可表示的。我们希望所有数字都是如此。这就是为什么十进制浮点是一项如此重要的创新(尚未广泛使用)。它可以表示我们可以写下的任何数字,并且可以完美地做到这一点。当然,可用位数仍然是有限的。

回到给定的问题,因为 0.1 是循环变量的增量并且实际上不可表示,所以值 1.0(尽管可表示)在循环中永远不会精确地达到。

于 2013-09-05T21:18:46.803 回答
2

如果您的步长始终是 10 倍,您可以使用以下方法快速完成此操作:

<?php
$start = 0;
$stop  = 1;
$step = ($stop - $start)/10;
$i = $start + $step;
while (round($i, 1) < $stop) { //Added round() to the while statement
    echo($i . "<br/>");
    $i += $step;
}
?>
于 2013-09-05T21:51:15.463 回答