Мозг поломал со своим скриптом, а походу это косяк в PHP. Что не так с var_export? Откуда он берёт эти 84.030000000000001? PHP: $n = 84.03; // способ округления 1 (возвращает float) $n = round($n, 2); echo $n; // отладка echo '<pre>'; var_export($n); echo '</pre>'; // отладка echo '<pre>'; var_dump($n); echo '</pre>'; // отладка echo '<br>'; // способ округления 2 (возвращает строку) $n = sprintf("%.2f", $n); echo $n; // отладка echo '<pre>'; var_export($n); echo '</pre>'; // отладка echo '<pre>'; var_dump($n); echo '</pre>'; // отладка echo '<br>'; // конвертируем строку в double $n = (double)$n; echo $n; // отладка echo '<pre>'; var_export($n); echo '</pre>'; // отладка echo '<pre>'; var_dump($n); echo '</pre>'; // отладка echo '<br>'; Вывод:
PHP: $n = 84.03; $n = round($n, 2); echo $n; // отладка echo '<pre>'; var_export($n,true); echo '</pre>'; // отладка echo '<pre>'; var_dump($n); echo '</pre>'; // отладка echo '<br>'; // способ округления 2 (возвращает строку) $n = sprintf("%.2f", $n); echo $n; // отладка echo '<pre>'; var_export($n,true); echo '</pre>'; // отладка echo '<pre>'; var_dump($n); echo '</pre>'; // отладка echo '<br>'; // конвертируем строку в double $n = (double)$n; echo $n; // отладка echo '<pre>'; var_export($n,true); echo '</pre>'; // отладка echo '<pre>'; var_dump($n); echo '</pre>'; // отладка echo '<br>';
Для начала я не морожу фигню а привел твой пример в нормальный вид. Второе это : Спойлер: php Точность чисел с плавающей точкой Числа с плавающей точкой имеют ограниченную точность. Хотя это зависит от операционной системы, в PHP обычно используется формат двойной точности IEEE 754, дающий максимальную относительную ошибку округления порядка 1.11e-16. Неэлементарные арифметические операции могут давать большие ошибки, и, разумеется, необходимо принимать во внимание распространение ошибок при совместном использовании нескольких операций. Кроме того, рациональные числа, которые могут быть точно представлены в виде чисел с плавающей точкой с основанием 10, например, 0.1 или 0.7, не имеют точного внутреннего представления в качестве чисел с плавающей точкой с основанием 2, вне зависимости от размера мантиссы. Поэтому они и не могут быть преобразованы в их внутреннюю двоичную форму без небольшой потери точности. Это может привести к неожиданным результатам: например, floor((0.1+0.7)*10) скорее всего вернет 7 вместо ожидаемого 8, так как результат внутреннего представления будет чем-то вроде 7.9999999999999991118.... Так что никогда не доверяйте точности чисел с плавающей точкой до последней цифры, и не проверяйте напрямую их равенство. Если вам действительно необходима высокая точность, используйте математические функции произвольной точности и gmp-функции. "Простое" объяснение можно найти в » руководстве по числам с плавающей точкой, которое также называется "Why don’t my numbers add up?" ("Почему мои числа не складываются?")
В данном случае результат не совсем связан с проблемой представления чисел с плавающей точкой(я бы сказал косвенно связано) в вычислительных системах. var_export производит дополнительные обработки используя один из параметров ini конфигурации https://php.ru/manual/ini.core.html#ini.serialize-precision, который и сводит на нет ваше округление числа.
Я подзабыл про этот параметр,спасибо что дополнил,касательно чисел с плавающей точкой я специально привел цитаты из документации,дабы потом ТС при виде такого PHP: echo floor((0.1+0.7)*10); Не удивлялся почему результат будет 7
В твоём примере var_export не работает. В моём работает. Но спасибо за копипасту. Только там ссылки битые, вот рабочие: математические функции произвольной точности gmp-функции Что из этих двух вариантов мне лучше использовать для базовых операций с флоутами - сложение, вычитание, умножение, деление? Я не математик и даже не программист, а выскочка-самоучка. И это конечно полная жесть. Я работаю с деньгами и недавно мне пришлось отказаться от округления копеек. Если PHP так криво считает флоуты, это значит что скорее всего моя система рассчётов считает неправильно, и в итоге я наёживаю себя, клиентов и налоговую.
Тэээк, поехали: 1) Никогда, НИКОГДА не храни деньги во флоатах. 2) Это не косяк PHP, это проблема представления чисел с плавающей точкой в самой машине. Это нормально. Так оно себя ведет независимо от платформы и языка. Как правильно хранить деньги? Целым числом. В копейках. А при выводе делить на 100. Либо просто внедрять запятую после крайнего правого регистра. Либо как-то еще форматировать print-fом, что угодно, в общем. Главное, что бы в хранилище и в вычислениях участвовали только копейки. При делении всегда помни об остатке. Тогда у тебя не будет никаких проблем. Это как хранение даты-времени. Упоротейший тип данных с кучей вариантов представления. А хранится на деле - в секундах или миллисекундах. А все эти красивые даты - не более чем форматирование перед выводом.
Почему нельзя хранить деньги в флоутах? Я в double храню, если что (double(10,2)). Просто перед записью в БД округляю до десятых. А при выводе юзеру, если длина копеек меньше 2, то добавляю ноль в конец. Я согласен, что лучше хранить в копейках, но систему я начал писать давно и затачивал изначально под рубли, а потом бюрократы ввели новые правила и заставили считать копейки. Жизнь не идеальная штука короче, сейчас уже слишком много придётся переделывать, чтобы перевести всё на копейки. Я тут ещё внезапно понял, что неверно прочитал копипасту от товарища alexblack выше. Там было написано: PHP: floor((0.1+0.7)*10); А я прочитал: PHP: (float)((0.1+0.7)*10); Вопрос - меня как-то могут коснутся эти косяки в PHP (которые не только в PHP, и вообще это нормально), если я использую с флоутами только базовые манипуляции, типа сложения-вычитания, деления-умножения? Я не использую функцию floor нигде. И даже round не использую, потому что с ней был похожий косяк давненько, уже не помню что там было, но я написал свою функцию round и использую только её: PHP: function round($double, $after_dot=0) { $double = (double)$double; return (double)sprintf("%.".$after_dot."f", $double); } Всё-таки, (float)((0.1+0.7)*10) == 8, так что хочется надеяться, что мне не придётся ничего переписывать и проблем с налоговой у меня нет.
Сам на свой вопрос ответил. Потому что костыли. У нас в универе, в свое время, если кто-то на курсяке или дипломе выкатывал софт, который баблом ворочать должен на предприятии, наличие флоата/дабла в качестве хранилища денег автоматом означало разъеб и косые взгляды в сторону научника, который прошляпил это. Твое округление не работает, оно прокатывает. Однажды, когда регистр встанет не туда, у тебя где-то сожрется или прирастет копеечка. А через полгода это выльется в адские проблемы с налоговой и поставит раком бухгалтерию. Пока не поздно, перепиливай на Integer.
Абсолютно в жопу правильно. Деньги хранить с плавающей точкой нельзя. Добавлю только, что стоит учитывать количество символов после точки. Для RUB вполне хватает 2 цифры после запятой, для KZT (тенге) - пяти, а лучше семи, для BTC (биткоин) - так же, от пяти до семи. Все зависит от точности исходной валюты и точности на сайте. А как выводить - number_format, например, в помощь.
decimal для денег в финансовых системах накапливающейся погрешности также не даёт. Но тут уже зависит от задач что оптимальнее. Точно не float разумеется.