Возвращаемое значение php intval() иfloor() слишком низкое?

StackOverflow https://stackoverflow.com/questions/812815

Вопрос

Поскольку тип данных с плавающей запятой в PHP неточен, а FLOAT в MySQL занимает больше места, чем INT (и является неточным), я всегда сохраняю цены как INT, умножая их на 100 перед сохранением, чтобы гарантировать, что у нас есть ровно 2 десятичных знака точности. .Однако я считаю, что PHP ведет себя неправильно.Пример кода:

echo "<pre>";

$price = "1.15";
echo "Price = ";
var_dump($price);

$price_corrected = $price*100;
echo "Corrected price = ";
var_dump($price_corrected);


$price_int = intval(floor($price_corrected));
echo "Integer price = ";
var_dump($price_int);

echo "</pre>";

Произведенный выпуск:

Price = string(4) "1.15"
Corrected price = float(115)
Integer price = int(114)

Я был удивлен.Когда окончательный результат оказался на 1 ниже ожидаемого, я ожидал, что результат моего теста будет выглядеть примерно так:

Price = string(4) "1.15"
Corrected price = float(114.999999999)
Integer price = int(114)

что продемонстрировало бы неточность типа float.Но почему Floor(115) возвращает 114??

Это было полезно?

Решение

Попробуйте это как быстрое решение:

$price_int = intval(floor($price_corrected + 0.5));

Проблема, с которой вы столкнулись, не является ошибкой PHP: все языки программирования, использующие действительные числа с арифметикой с плавающей запятой, имеют схожие проблемы.

Общее практическое правило денежных расчетов — никогда не использовать числа с плавающей запятой (ни в базе данных, ни в вашем скрипте).Вы можете избежать всевозможных проблем, всегда храня центы, а не доллары.Центы являются целыми числами, и вы можете свободно складывать их и умножать на другие целые числа.Всякий раз, когда вы отображаете номер, обязательно вставляйте точку перед двумя последними цифрами.

Причина, по которой вы получаете 114 вместо 115, заключается в том, что floor округляет в меньшую сторону до ближайшего целого числа, таким образом, Floor(114,999999999) становится 114.Более интересный вопрос: почему 1,15 * 100 равно 114,999999999, а не 115.Причина в том, что 1,15 — это не совсем 115/100, а немного меньше, поэтому, если вы умножите на 100, вы получите число чуть меньше 115.

Вот более подробное объяснение того, что echo 1.15 * 100; делает:

  • Он анализирует 1,15 в двоичное число с плавающей запятой.Это включает в себя округление, иногда округление немного в меньшую сторону, чтобы получить двоичное число с плавающей запятой, ближайшее к 1,15.Причина, по которой вы не можете получить точное число (без ошибки округления), заключается в том, что 1,15 имеет бесконечное количество цифр по основанию 2.
  • Он преобразует 100 в двоичное число с плавающей запятой.Это предполагает округление, но поскольку 100 — небольшое целое число, ошибка округления равна нулю.
  • Он вычисляет произведение двух предыдущих чисел.Это также включает в себя небольшое округление, чтобы найти ближайшее двоичное число с плавающей запятой.Ошибка округления в этой операции оказывается равной нулю.
  • Он преобразует двоичное число с плавающей запятой в десятичное число по основанию 10 с точкой и печатает это представление.Это также предполагает небольшое округление.

Причина, по которой PHP печатает удивительные результаты Corrected price = float(115) (вместо 114.999...) заключается в том, что var_dump не печатает точное число (!), но печатает число, округленное до n - 2 (или n - 1) цифр, где n цифр — точность расчета.Вы можете легко убедиться в этом:

echo 1.15 * 100;  # this prints 115
printf("%.30f", 1.15 * 100);  # you 114.999....
echo 1.15 * 100 == 115.0 ? "same" : "different";  # this prints `different'
echo 1.15 * 100 < 115.0 ? "less" : "not-less";    # this prints `less'

Если вы печатаете плавающие числа, помните: вы не всегда видите все цифры при печати числа с плавающей запятой.

См. также большое предупреждение в начале PHP с плавающей запятой документы.

Другие советы

Я считаю, что другие ответы охватывают причину и хорошее решение проблемы.

Чтобы решить проблему с другой стороны:

Для хранения значений цен в MySQL вам, вероятно, следует взглянуть на ДЕСЯТИЧНЫЙ тип, что позволяет хранить точные значения с десятичными знаками.

PHP выполняет округление по значащим цифрам.Он скрывает неточность (в строке 2).Конечно, когда появляется пол, он ничего не знает и опускает его до упора.

Возможно, это еще одно возможное решение этой «проблемы»:

intval(number_format($problematic_float, 0, '', ''));

Как уже говорилось, это не проблема PHP как такового. Это скорее проблема обработки дробей, которые не могут быть выражены как конечные значения с плавающей запятой, что приводит к потере символов при округлении.

Решение состоит в том, чтобы гарантировать, что когда вы работаете со значениями с плавающей запятой и вам необходимо поддерживать точность, используйте функции gmp или математические функции BC — bcpow, bcmul и др.и проблема легко решится.

Например, вместо $ pryal_corrected = $ price*100;

используйте $price_corrected = bcmul($price,100);

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top