Поскольку тип данных float в 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. Но почему этаж (115) возвращается 114 ??
Попробуйте это быстро:
$price_int = intval(floor($price_corrected + 0.5));
Проблема, с которой вы столкнулись, – это не ошибка PHP, все языки программирования с использованием реальных чисел с арифметикой с плавающей запятой имеют схожие проблемы.
Общее правило для денежных расчетов заключается в том, чтобы никогда не использовать float (ни в базе данных, ни в вашем скрипте). Вы можете избежать всех проблем, всегда сохраняя центы вместо долларов. Центами являются целые числа, и вы можете свободно добавлять их вместе и умножать на другие целые числа. Всякий раз, когда вы показываете номер, убедитесь, что вы вставили точку перед двумя последними цифрами.
Причина, по которой вы получаете 114 вместо 115, состоит в том, что floor
округляется вниз, к ближайшему целому числу, поэтому пол (114.999999999) становится 114. Более интересным вопросом является то, почему 1,15 * 100 составляет 114.999999999 вместо 115. Причина этого в том, что 1.15 не точно 115/100, но это немного меньше, поэтому, если вы умножаетесь на 100, вы получаете число, меньшее, чем 115.
Вот более подробное объяснение, что echo 1.15 * 100;
делает:
Причина, по которой 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 вам, вероятно, следует взглянуть на тип DECIMAL , который позволяет хранить точные значения с десятичными знаками.
PHP делает округление на основе значительных цифр. Это скрывает неточность (в строке 2). Конечно, когда приходит пол, он не знает ничего лучшего и полностью останавливает его.
Возможно, это еще одно возможное решение для этой «проблемы»:
intval(number_format($problematic_float, 0, '', ''));
Как уже говорилось, это не проблема с PHP как таковой, это скорее проблема обработки дробей, которые не могут быть выражены как конечные значения с плавающей запятой, что приводит к потере характера при округлении.
Решение состоит в том, чтобы убедиться, что когда вы работаете с значениями с плавающей запятой, и вам нужно поддерживать точность – используйте функции gmp или функции математики BC – bcpow, bcmul et al. и проблема будет решена легко.
Например, вместо $ price_corrected = $ price * 100;
используйте $ price_corrected = bcmul ($ price, 100);