За последние 24 часа нас посетили 26537 программистов и 1511 роботов. Сейчас ищут 843 программиста ...

Косяк с var_export?

Тема в разделе "PHP для профи", создана пользователем NerdRage, 29 янв 2017.

  1. NerdRage

    NerdRage Активный пользователь

    С нами с:
    6 июл 2016
    Сообщения:
    439
    Симпатии:
    42
    Мозг поломал со своим скриптом, а походу это косяк в PHP. Что не так с var_export? Откуда он берёт эти 84.030000000000001?
    PHP:
    1. $n = 84.03;
    2.  
    3. // способ округления 1 (возвращает float)
    4. $n = round($n, 2);
    5. echo $n; // отладка
    6. echo '<pre>'; var_export($n); echo '</pre>'; // отладка
    7. echo '<pre>'; var_dump($n); echo '</pre>'; // отладка
    8. echo '<br>';
    9.  
    10. // способ округления 2 (возвращает строку)
    11. $n = sprintf("%.2f", $n);
    12. echo $n; // отладка
    13. echo '<pre>'; var_export($n); echo '</pre>'; // отладка
    14. echo '<pre>'; var_dump($n); echo '</pre>'; // отладка
    15. echo '<br>';
    16.  
    17. // конвертируем строку в double
    18. $n = (double)$n;
    19. echo $n; // отладка
    20. echo '<pre>'; var_export($n); echo '</pre>'; // отладка
    21. echo '<pre>'; var_dump($n); echo '</pre>'; // отладка
    22. echo '<br>';
    Вывод:
     
  2. alexblack

    alexblack Старожил

    С нами с:
    20 янв 2016
    Сообщения:
    640
    Симпатии:
    381
    PHP:
    1. $n = 84.03;
    2. $n = round($n, 2);
    3. echo $n; // отладка
    4. echo '<pre>'; var_export($n,true); echo '</pre>'; // отладка
    5. echo '<pre>'; var_dump($n); echo '</pre>'; // отладка
    6. echo '<br>';
    7. // способ округления 2 (возвращает строку)
    8. $n = sprintf("%.2f", $n);
    9. echo $n; // отладка
    10. echo '<pre>'; var_export($n,true); echo '</pre>'; // отладка
    11. echo '<pre>'; var_dump($n); echo '</pre>'; // отладка
    12. echo '<br>';
    13. // конвертируем строку в double
    14. $n = (double)$n;
    15. echo $n; // отладка
    16. echo '<pre>'; var_export($n,true); echo '</pre>'; // отладка
    17. echo '<pre>'; var_dump($n); echo '</pre>'; // отладка
    18. echo '<br>';
     
  3. NerdRage

    NerdRage Активный пользователь

    С нами с:
    6 июл 2016
    Сообщения:
    439
    Симпатии:
    42
    Фигню морозишь.

    З.Ы. Тут и округление ни при чём.
    PHP:
    1. $n = 81.9;
    2. echo var_export($n, true);
    Выдаёт:
     
  4. alexblack

    alexblack Старожил

    С нами с:
    20 янв 2016
    Сообщения:
    640
    Симпатии:
    381
    Для начала я не морожу фигню а привел твой пример в нормальный вид. Второе это :

    Точность чисел с плавающей точкой
    Числа с плавающей точкой имеют ограниченную точность. Хотя это зависит от операционной системы, в 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?" ("Почему мои числа не складываются?")
     
  5. t1grok

    t1grok Новичок

    С нами с:
    29 янв 2017
    Сообщения:
    119
    Симпатии:
    32
    В данном случае результат не совсем связан с проблемой представления чисел с плавающей точкой(я бы сказал косвенно связано) в вычислительных системах.
    var_export производит дополнительные обработки используя один из параметров ini конфигурации https://php.ru/manual/ini.core.html#ini.serialize-precision, который и сводит на нет ваше округление числа.
     
  6. alexblack

    alexblack Старожил

    С нами с:
    20 янв 2016
    Сообщения:
    640
    Симпатии:
    381
    Я подзабыл про этот параметр,спасибо что дополнил,касательно чисел с плавающей точкой я специально привел цитаты из документации,дабы потом ТС при виде такого
    PHP:
    1. echo floor((0.1+0.7)*10);
    Не удивлялся почему результат будет 7
     
  7. NerdRage

    NerdRage Активный пользователь

    С нами с:
    6 июл 2016
    Сообщения:
    439
    Симпатии:
    42
    В твоём примере var_export не работает. В моём работает. Но спасибо за копипасту. Только там ссылки битые, вот рабочие:
    математические функции произвольной точности
    gmp-функции

    Что из этих двух вариантов мне лучше использовать для базовых операций с флоутами - сложение, вычитание, умножение, деление? Я не математик и даже не программист, а выскочка-самоучка. И это конечно полная жесть. Я работаю с деньгами и недавно мне пришлось отказаться от округления копеек. Если PHP так криво считает флоуты, это значит что скорее всего моя система рассчётов считает неправильно, и в итоге я наёживаю себя, клиентов и налоговую.
     
  8. Fell-x27

    Fell-x27 Суперстар
    Команда форума Модератор

    С нами с:
    25 июл 2013
    Сообщения:
    12.156
    Симпатии:
    1.771
    Адрес:
    :сердА
    Тэээк, поехали:
    1) Никогда, НИКОГДА не храни деньги во флоатах.
    2) Это не косяк PHP, это проблема представления чисел с плавающей точкой в самой машине. Это нормально. Так оно себя ведет независимо от платформы и языка.

    Как правильно хранить деньги? Целым числом. В копейках. А при выводе делить на 100. Либо просто внедрять запятую после крайнего правого регистра. Либо как-то еще форматировать print-fом, что угодно, в общем. Главное, что бы в хранилище и в вычислениях участвовали только копейки. При делении всегда помни об остатке. Тогда у тебя не будет никаких проблем.

    Это как хранение даты-времени. Упоротейший тип данных с кучей вариантов представления. А хранится на деле - в секундах или миллисекундах. А все эти красивые даты - не более чем форматирование перед выводом.
     
  9. NerdRage

    NerdRage Активный пользователь

    С нами с:
    6 июл 2016
    Сообщения:
    439
    Симпатии:
    42
    Почему нельзя хранить деньги в флоутах? Я в double храню, если что (double(10,2)). Просто перед записью в БД округляю до десятых. А при выводе юзеру, если длина копеек меньше 2, то добавляю ноль в конец. Я согласен, что лучше хранить в копейках, но систему я начал писать давно и затачивал изначально под рубли, а потом бюрократы ввели новые правила и заставили считать копейки. Жизнь не идеальная штука короче, сейчас уже слишком много придётся переделывать, чтобы перевести всё на копейки.

    Я тут ещё внезапно понял, что неверно прочитал копипасту от товарища alexblack выше. Там было написано:
    PHP:
    1. floor((0.1+0.7)*10);
    А я прочитал:
    PHP:
    1. (float)((0.1+0.7)*10);
    Вопрос - меня как-то могут коснутся эти косяки в PHP (которые не только в PHP, и вообще это нормально), если я использую с флоутами только базовые манипуляции, типа сложения-вычитания, деления-умножения? Я не использую функцию floor нигде. И даже round не использую, потому что с ней был похожий косяк давненько, уже не помню что там было, но я написал свою функцию round и использую только её:
    PHP:
    1. function round($double, $after_dot=0) {
    2.    $double = (double)$double;
    3.    return (double)sprintf("%.".$after_dot."f", $double);
    4. }
    Всё-таки, (float)((0.1+0.7)*10) == 8, так что хочется надеяться, что мне не придётся ничего переписывать и проблем с налоговой у меня нет.
     
  10. Fell-x27

    Fell-x27 Суперстар
    Команда форума Модератор

    С нами с:
    25 июл 2013
    Сообщения:
    12.156
    Симпатии:
    1.771
    Адрес:
    :сердА
    Сам на свой вопрос ответил.

    Потому что костыли. У нас в универе, в свое время, если кто-то на курсяке или дипломе выкатывал софт, который баблом ворочать должен на предприятии, наличие флоата/дабла в качестве хранилища денег автоматом означало разъеб и косые взгляды в сторону научника, который прошляпил это.

    Твое округление не работает, оно прокатывает. Однажды, когда регистр встанет не туда, у тебя где-то сожрется или прирастет копеечка. А через полгода это выльется в адские проблемы с налоговой и поставит раком бухгалтерию.

    Пока не поздно, перепиливай на Integer.
     
  11. admyx

    admyx Активный пользователь

    С нами с:
    14 мар 2008
    Сообщения:
    2.159
    Симпатии:
    1
    Абсолютно в жопу правильно.
    Деньги хранить с плавающей точкой нельзя.
    Добавлю только, что стоит учитывать количество символов после точки.
    Для RUB вполне хватает 2 цифры после запятой, для KZT (тенге) - пяти, а лучше семи, для BTC (биткоин) - так же, от пяти до семи.
    Все зависит от точности исходной валюты и точности на сайте.
    А как выводить - number_format, например, в помощь.
     
    Fell-x27 нравится это.
  12. Zuldek

    Zuldek Старожил

    С нами с:
    13 май 2014
    Сообщения:
    2.381
    Симпатии:
    344
    Адрес:
    Лондон, Тисовая улица, дом 4, чулан под лестницей
    decimal для денег в финансовых системах накапливающейся погрешности также не даёт. Но тут уже зависит от задач что оптимальнее. Точно не float разумеется.