Конечно, проблема точности PHP с плавающей запятой была темой для SO довольно часто, но я не могу понять, почему этот вопрос допускает 18 отрицательных ответов, поскольку вопросник очень точно задан для конкретной проблемы с конкретным числом с плавающей запятой. Я решил это так, что все должны четко понимать, в чем проблема (а не только эти неточные рассуждения).
Здесь мы видим - видим следующие результаты:
$example = 90000000000009132 - 1;
var_dump(PHP_INT_MAX);
var_dump($example);
var_dump(PHP_FLOAT_MAX);
var_dump(sprintf('%.0g', $example));
var_dump(sprintf('%.0f', $example));
Результаты:
int(9223372036854775807)
int(90000000000009131)
float(1.7976931348623E+308)
string(7) "9.0e+16"
string(17) "90000000000009136"
Это значит:
- Вы не выше максимального целочисленного размера, который
сбой арифметических операций
- Результат вашего вычисления верен с целыми числами
- Вы не выше PHP_FLOAT_MAX (требуется PHP 7.2), хотя размер зависит от вашей среды
- Представление вашего вычисления с плавающей запятой неожиданно неверно (поскольку ваш оператор printf преобразуется в число с плавающей точкой, а затем в строку)
Так почему у нас этот странный результат? Давайте сделаем несколько тестов:
var_dump(sprintf('%.0f', 90000000000009051));
var_dump(sprintf('%.0f', 90000000000009101));
var_dump(sprintf('%.0f', 90000000000009105));
var_dump(sprintf('%.0f', 90000000000009114));
var_dump(sprintf('%.0f', 90000000000009121));
var_dump(sprintf('%.0f', 90000000000009124));
var_dump(sprintf('%.0f', 90000000000009128));
var_dump(sprintf('%.0f', 90000000000009130));
var_dump(sprintf('%.0f', 90000000000009131));
var_dump(sprintf('%.0f', 90000000000009138));
var_dump(sprintf('%.0f', 90000000000009142));
var_dump(sprintf('%.0f', 90000000000009177));
Результат этого:
string(17) "90000000000009056"
string(17) "90000000000009104"
string(17) "90000000000009104"
string(17) "90000000000009120"
string(17) "90000000000009120"
string(17) "90000000000009120"
string(17) "90000000000009120"
string(17) "90000000000009136"
string(17) "90000000000009136"
string(17) "90000000000009136"
string(17) "90000000000009136"
string(17) "90000000000009184"
Посмотрите на эти цифры. Цифры в конце всегда делятся на 16. А что означает 16 в информационных технологиях? Байт Шестнадцатеричный. Что это говорит нам?
Возможно, вы превысили предел, в котором числа с плавающей точкой в основном точны (необязательно в арифметических операциях).
Что если мы уменьшим эти числа на один ноль до 16 цифр?
var_dump(sprintf('%.0f', 9000000000009051));
var_dump(sprintf('%.0f', 9000000000009101));
var_dump(sprintf('%.0f', 9000000000009124));
var_dump(sprintf('%.0f', 9000000000009138));
var_dump(sprintf('%.0f', 9000000000009142));
var_dump(sprintf('%.0f', 9000000000009177));
...
string(16) "9000000000009051"
string(16) "9000000000009101"
string(16) "9000000000009124"
string(16) "9000000000009138"
string(16) "9000000000009142"
string(16) "9000000000009177"
Вывод всегда правильный. Что ж. А что если мы улучшим его на одну нулевую цифру?
var_dump(sprintf('%.0f', 900000000000009051));
var_dump(sprintf('%.0f', 900000000000009101));
var_dump(sprintf('%.0f', 900000000000009124));
var_dump(sprintf('%.0f', 900000000000009138));
var_dump(sprintf('%.0f', 900000000000009142));
var_dump(sprintf('%.0f', 900000000000009177));
...
string(18) "900000000000009088"
string(18) "900000000000009088"
string(18) "900000000000009088"
string(18) "900000000000009088"
string(18) "900000000000009088"
string(18) "900000000000009216"
Интересно, что это опять факторы 16 - еще более неточные.
А теперь давайте разгадать эту загадку:
var_dump(strlen(sprintf('%.0f', 9000000000009051)));
Угадай, каков результат:
INT (16)
Wow. Это означает, что числа с плавающей точкой в PHP работают точно до 16 цифр (, если вы превысите это магическое число, оно станет неточным ).
Так что же произошло с вашим расчетом?
Он был округлен до следующих 16 в конце, когда вы превысили число 16 цифр на одну.
Более подробную информацию о точности с плавающей точкой в PHP можно найти в десятках вопросов о Stackoverflow и, конечно же, в руководстве по PHP.
Чтобы ответить, как заставить ваш код работать правильно:
Если вы хотите, чтобы ваши числа были точными, не конвертируйте в float. Например, для целых чисел возможны более высокие вычисления.
Если вы превысите число из 16 цифр, простая арифметика, такая как -1 или +1, потерпит неудачу (по крайней мере, в текущей версии PHP, как кажется).