Почему напечатано последнее число (1)?

Код:

<?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

Создано fiddle

Еще одно: если вы установили $start = 1 и $stop = 2, он работает нормально.

Использование: php 5.3.27

Почему печатается 1?

Ответ 1

Потому что не только флеш-математика ошибочна, иногда ее представление тоже ошибочно - и это дело здесь.

Фактически вы не получаете 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 на самом деле - потому что это число можно сохранить в контейнере с плавающей точкой. Все остальные были только приближениями.

Как это решить? Ну, один радикальный подход использует не float, а целые числа в подобных ситуациях. Легко заметить, что вы сделали это так...

$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 для преобразования float в некоторую строку, а затем сравнить эту строку с предварительно отформатированным поплавком. Вот так:

$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;
}

Ответ 2

Проблема в том, что число в переменной $i не равно 1 (при печати). Его фактическое значение меньше 1. Таким образом, в тесте ($ я < $stop) истинно, число преобразуется в десятичное (вызывает округление до 1) и отображается.

Теперь почему $i не 1 точно? Это потому, что вы добрались туда, указав 10 * 0,1, а 0,1 не могут быть представлены отлично в двоичном формате. Только числа, которые могут быть выражены как сумма конечного числа степеней 2, могут быть прекрасно представлены.

Почему тогда $stop точно 1? Потому что он не в формате с плавающей запятой. Другими словами, это точно с самого начала - оно не вычисляется в системе, используемой с плавающей запятой 10 * 0,1.

Математически мы можем записать это следующим образом:   enter image description here

64-битный двоичный float может удерживать только первые 27 ненулевых членов суммы, которая приблизительно равна 0,1. Остальные 26 бит значения остаются равными нулю, чтобы указать нулевые члены. Причина 0.1 не является физически представимой, так это то, что требуемая последовательность термов бесконечна. С другой стороны, числа типа 1 требуют лишь небольшого конечного числа членов и являются представимыми. Мы бы хотели, чтобы это имело место для всех чисел. Вот почему десятичная плавающая точка является такой важной новинкой (пока не широко доступной). Он может представлять любое число, которое мы можем записать, и делать это отлично. Конечно, количество доступных цифр остается конечным.

Возвращаясь к заданной задаче, поскольку 0,1 - это приращение для переменной цикла и на самом деле не представимо, значение 1.0 (хотя и представимо) никогда не достигается точно в цикле.

Ответ 3

Если ваш шаг всегда будет в 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;
}
?>