Why is the last number (1) printed?

2020-02-24 07:08发布

The code:

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

The output:

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

Created a fiddle

One more: if you set $start = 1 and $stop = 2 it works fine.

Using: php 5.3.27

Why is the 1 printed?

3条回答
戒情不戒烟
2楼-- · 2020-02-24 07:21

If your step will always be a factor of 10, you can accomplish this quickly with the following:

<?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;
}
?>
查看更多
Summer. ? 凉城
3楼-- · 2020-02-24 07:23

Because not only float math is flawed, sometimes its representation is flawed too - and that's the case here.

You don't actually get 0.1, 0.2, ... - and that's quite easy to check:

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

The only difference here, as you see, is that echo replaced with number_format call. But the results are drastically different:

0.10000000000000000555111512312578
0.20000000000000001110223024625157
0.30000000000000004440892098500626
0.40000000000000002220446049250313
0.50000000000000000000000000000000
0.59999999999999997779553950749687
0.69999999999999995559107901499374
0.79999999999999993338661852249061
0.89999999999999991118215802998748
0.99999999999999988897769753748435

See? Only one time it was 0.5 actually - because that number can be stored in a float container. All the others were only approximations.

How to solve this? Well, one radical approach is using not floats, but integers in similar situations. It's easy to notice that have you done it this way...

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

... it would work ok:

Alternatively, you can use number_format to convert the float into some string, then compare this string with preformatted float. Like this:

$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;
}
查看更多
Ridiculous、
4楼-- · 2020-02-24 07:25

The problem is that the number in the variable $i is not 1 (when printed). Its actual just less than 1. So in the test ($i < $stop) is true, the number is converted to decimal (causing rounding to 1), and displayed.

Now why is $i not 1 exactly? It is because you got there by saying 10 * 0.1, and 0.1 cannot be represented perfectly in binary. Only numbers which can be expressed as a sum of a finite number of powers of 2 can be perfectly represented.

Why then is $stop exactly 1? Because it isn't in floating point format. In other words, it is exact from the start -- it isn't calculated within the system used floating point 10 * 0.1.

Mathematically, we can write this as follows: enter image description here

A 64 bit binary float can only hold the first 27 non-zero terms of the sum which approximates 0.1. The other 26 bits of the significand remain zero to indicate zero terms. The reason 0.1 isn't physically representable is that the required sequence of terms is infinite. On the other hand, numbers like 1 require only a small finite number of terms and are representable. We'd like that to be the case for all numbers. This is why decimal floating point is such an important innovation (not widely available yet). It can represent any number that we can write down, and do so perfectly. Of course, the number of available digits remains finite.

Returning to the given problem, since 0.1 is the increment for the loop variable and isn't actually representable, the value 1.0 (although representable) is never precisely reached in the loop.

查看更多
登录 后发表回答