Правильно ли выполнять арифметические операции над числами FLOAT в PHP - PullRequest
0 голосов
/ 02 июля 2018

Я хочу вычислить простейшую вещь:

$example = 90000000000009132 - 1;
echo sprintf('%.0f',  $example);

Удивительно, но я получаю:

           90000000000009136

мой вопрос - Как я могу получить правильный ответ в php для такой операции? Можете ли вы дать мне правильный код ?

p.s. После поиска в сети я нашел объяснения, говорящие, что «это не ошибка» и некоторые мифы о float и т. Д. Что ж, я не стану доказывать, почему я думаю, что это действительно является очевидной ошибкой, и нам просто все равно , что происходит на фоне ПК, но очевидным является то, что мы не получаем правильное число при вычитании X из Y так что это определенно ошибка! (Но, пожалуйста, не спорьте со мной об этом, потому что у меня другой вопрос - ясно написано выше).

p.s.После этого вопроса аномалия не проголосовала , я не смог найти реальное решение в указанных ссылках. Никто из них не работал, по крайней мере, на моем хостинге. Таким образом, все эти «ответы», на которые претендует решение, кажутся бесполезными на некоторых хостингах.

1 Ответ

0 голосов
/ 12 июля 2018

Конечно, проблема точности 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, как кажется).

...