printf — Почему PHP sprintf не округляет 5с надежно?

Я полагался на sprintf('%0.1f', 2.25) === '2.3' но оказывается, что приходит в 2.2!

На самом деле это кажется случайным:

php > for ($j=0;$j<=10;$j++) { printf( "%s -> %0.1f\n",$j+ 0.05, $j+0.05); }
0.05 -> 0.1 // Up, as expected
1.05 -> 1.1 // Up, as expected
2.05 -> 2.0 // Down!
3.05 -> 3.0 // Down!
4.05 -> 4.0 // Down!
5.05 -> 5.0 // Down!
6.05 -> 6.0 // Down!
7.05 -> 7.0 // Down!
8.05 -> 8.1 // Up, as expected
9.05 -> 9.1 // Up, as expected

Я полностью упустил суть? Я чувствую, что из-под меня тянут коврик и что все, чему я научился в школе, неправильно …! Конечно, функция для округления чисел должна делать это последовательно? (Отмечу, что round($n, 1) работает как положено.)

0

Решение

Как объяснение почему round может предложить лучшие результаты, чем функция, специально не предназначенная для округления, мы должны рассмотреть пределы представления с плавающей запятой двойной точности. Двойные числа могут представлять от 15 до 17 десятичных знаков и цифр. От Статья в Википедии о двойной точности:

Если десятичная строка, содержащая не более 15 значащих цифр, преобразуется в представление двойной точности IEEE 754, а затем преобразуется обратно в строку с тем же количеством значащих цифр, то окончательная строка должна соответствовать оригиналу. Если двойная точность IEEE 754 преобразуется в десятичную строку, содержащую не менее 17 значащих цифр, а затем обратно в двойную, то окончательное число должно соответствовать оригиналу

Реализация round может и должен использовать это, чтобы «делать правильные вещи» в большинстве случаев.

Пример 1:

<?=number_format(2.05,14); //give me 15 significant digits. Guaranteed to produce the orginal number

выходы:

2.05000000000000

Пример 2:

<?=number_format(2.05,16); //give me 17 significant digits. Not guaranteed to produce the orginal number

выходы:

2.0499999999999998

Это просто демонстрация поведения IEEE 754.

Я собираюсь догадаться (потому что я не читал его реализацию), что sprintf на самом деле не пытается делать что-то особенно умное в отношении округления, тогда как round вероятно, пытается округлить «правильно» (согласно IEEE 754) относительно количества значащих цифр, которые вы запрашивали.

2

Другие решения

Вы должны использовать round

http://php.net/manual/en/function.round.php

round(2.25, 2) === floatval('2.3')
4

Подводя итог тому, что было сказано в комментариях в ответе:

Так как printf не является функцией округления, но функция низкого уровня, чтобы дать строковое представление числа. В этом случае номер внутри не совпадает с установленным номером, например, это может быть 2.249999991231231123 из-за ограничений внутреннего представления с плавающей запятой.

Так printf округляет другое число к тому, которое вы ввели / рассчитали, что правильно 2.2 в этом примере.

Поэтому, как указывает другой ответ (и мой оригинальный вопрос), лучшим решением будет использование round() (и, возможно, sprintf на результат).

1
По вопросам рекламы [email protected]