Почему напечатано последнее число (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 

Создал скрипку

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

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

Почему печатается 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 самом деле 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; } 

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

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

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

Математически мы можем записать это следующим образом: введите описание изображения здесь

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

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

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