Как обрабатывать денежные значения в PHP и MySql?

16

Я унаследовал огромную кучу унаследованного кода, написанного на PHP, поверх базы данных MySQL. Я заметил, что приложение используетdoubles для хранения и манипулирования данными.

Теперь я наткнулся на многочисленные посты с упоминанием того, как double они не подходят для денежных операций из-за ошибок округления. Тем не менее, я еще не нашел полного решения о том, как денежные значения должны обрабатываться в коде PHP и храниться в базе данных MySQL.

Есть ли лучшая практика, когда дело доходит до обращения с деньгами именно в PHP?

Вещи, которые я ищу:

  1. Как данные должны храниться в базе данных? тип столбца? размер?
  2. Как данные должны обрабатываться в обычном сложении, вычитании. умножение или деление?
  3. Когда я должен округлить значения? Сколько округления приемлемо, если таковые имеются?
  4. Есть ли разница между обработкой больших денежных сумм и низкими?

Примечание: очень упрощенный пример кода , как я мог бы столкнуться ценности денег в повседневной жизни (различные вопросы безопасности были проигнорированы для упрощения Конечно , в реальной жизни я никогда бы не использовать мой код , как это.):

$a= $_POST['price_in_dollars']; //-->(ex: 25.06) will be read as a string should it be cast to double?
$b= $_POST['discount_rate'];//-->(ex: 0.35) value will always be less than 1
$valueToBeStored= $a * $b; //--> any hint here is welcomed 

$valueFromDatabase= $row['price']; //--> price column in database could be double, decimal,...etc.

$priceToPrint=$valueFromDatabase * 0.25; //again cast needed or not?

Я надеюсь, что вы используете этот пример кода в качестве средства, чтобы выявить больше вариантов использования, а не воспринимать его буквально, конечно.

Бонусный вопрос Если я буду использовать ORM, такой как Doctrine или PROPEL, насколько отличается использование денег в моем коде.

Songo
источник
1
Я не знаю PHP, но знание терминологии неоценимо в этих сценариях для google-fu, искомый
Джимми Хоффа
1
@ Джимми Хоффа: произвольная точность, как правило, не то, что вам нужно, это то, что может точно представлять и работать с десятичными дробями. Конечно, вы часто находите оба вместе, но, например, decimalтип в C # имеет ограниченную точность, но идеально подходит для денежных значений.
Майкл Боргвардт
1
@MichaelBorgwardt верно, я забываю о рациональных типах, потому что у многих языков их нет. Хороший звонок.
Джимми Хоффа
Много поработав с валютами и устаревшим кодом, я бы сказал, чтобы хранить его в целых числах и использовать центы вместо долларов. Остерегайтесь, однако, 32 целых числа могут содержать на удивление небольшую сумму.4) 32-разрядное целое число может содержать только 21 миллион, если не подписано и использует центы.
Питер Б
Кроме того, ваш пример кода, который показывает цены и скидки, размещаемые POSTed, пугает меня. Надеюсь, он настолько упрощен, что не отражает реального использования, но на первый взгляд кажется, что вы доверяете браузеру, чтобы сказать вам, какова цена предмета, отправив обратно скрытое поле или что-то подобное.
Carson63000

Ответы:

6

Может быть довольно сложно обрабатывать числа с помощью PHP / MySQL. Если вы используете десятичное число (10,2), а ваше число длиннее или имеет более высокую точность, оно будет усечено без ошибок (если вы не установили правильный режим для своего сервера БД).

Для обработки больших значений или значений высокой точности вы можете использовать библиотеку типа BCMath это позволит вам выполнять основные операции с большими числами и сохранять требуемую точность.

Я не уверен, какие именно вычисления вы будете делать, но вы также должны иметь в виду, что (0,22 * 0,4576) + (0,78 * 0,4576) не будет равно 0,4576, если вы не будете использовать надлежащую точность в процессе.

Максимальный размер DECIMAL в MySQL составляет 65, поэтому его должно быть более чем достаточно для любых целей. Если вы используете тип поля DECIMAL, он будет возвращен в виде строки независимо от использования ORM или просто PDO / mysql (i).

Как данные должны храниться в базе данных? тип столбца? размер?

DECIMAL с точностью, которая вам нужна. Если вы используете обменный курс, вам понадобится как минимум четыре знака после запятой

Как данные должны обрабатываться в обычном сложении, вычитании. умножение или деление?

Используйте BCMath, чтобы быть на стороне сохранения и почему использование float не может быть хорошей идеей

Когда я должен округлить значения? Сколько округления приемлемо, если таковые имеются?

Для денежных значений обычно допустимы два десятичных знака, но вам может потребоваться больше, если, например, вы используете обменные курсы.

Есть ли разница между обработкой больших денежных сумм и низкими?

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

onlineapplab.com
источник
9

Простой обходной путь - хранить их как целые числа. 99.99 хранится как 9999. Если это не сработает (и есть много причин, по которым это может быть неправильным выбором), вы можете использовать тип Decimal. http://dev.mysql.com/doc/refman/5.0/en/precision-math-decimal-changes.html на стороне mysql. На стороне PHP я нашел этот /programming/3244094/decimal-type-in-php который может быть тем, что вы ищете.

Бонусный вопрос: сложно сказать. Орм будет работать на основе выбранных типов данных. Я бы сказал, что вы можете сделать некоторые вещи с помощью абстракции, чтобы помочь, но эта конкретная проблема не решается просто путем перехода на ORM.

Ominus
источник
1
FWIW, Drupal Commerce использует трюк 9999 для хранения своих цен.
Флориан Маргейн
1
«и есть много причин, по которым это может быть плохим выбором». Не могли бы вы поделиться некоторыми проблемами с этой практикой?
Сонго
2
@Songo: см. Плавающая
Майкл Боргвардт,
@MichaelBorgwardt Спасибо за информацию, Сонго, извините за задержку, ураган
унес
3

Я постараюсь изложить свой опыт в этом:

Вещи, которые я ищу:

Как данные должны храниться в базе данных? тип столбца? размер?

Я использую DECIMAL(10,2)для mysql без проблем (8 завершений и 2 десятичных знака == 99.999.999,99 == огромное количество), но это зависит от диапазона денег, который вы должны покрыть. Огромные суммы следует принимать с особой осторожностью (например, максимальные значения ОС). В десятичной части я использую 2 значения, чтобы избежать усечения или округления значений. Что касается денег, то мало случаев, когда вам нужно больше десятичных знаков (в этом случае вам нужно убедиться, что пользователь будет работать со всеми из них, в противном случае это бесполезные данные)

Как данные должны обрабатываться в обычном сложении, вычитании. умножение или деление?

Работа с одной валютой и обменным столом (с датами). Таким образом вы гарантируете, что у вас всегда будет правильная сохраненная сумма. Дополнительно: сохранить полные значения и создать представление с результатами расчета. Это поможет вам зафиксировать значения на лету

Когда я должен округлить значения? Сколько округления приемлемо, если таковые имеются?

Опять же, зависит от вашей системы диапазон денег. Всегда думайте в терминах KISS, если вам не нужно попадать в беспорядок обмена валюты

Есть ли разница между обработкой больших денежных сумм и низкими?

В зависимости от вашей ОС и языков программирования вам всегда нужно проверять максимальные и минимальные значения

Элвин Кеслер
источник
Спасибо за ответ, но если мне придется обработать что-то вроде $valueToBeStored= $a * $b;if $aи $bоба будут считаны из базы данных как десятичные числа, я думаю, что они будут преобразованы doubleв PHP, верно? это повлияет на цифры?
Сонго
$aи $bвзяты из дб? поэтому в моем примере вам не нужно хранить данные, $valueToBeStoredпотому что вы всегда будете иметь источник $aи $bданные. Таким образом, вы можете программно работать со значением в функции или около того или создать представление MySQL с результатом столбца. Таким образом, если нужно изменить какое-либо значение, вам не нужно беспокоиться об изменении нескольких мест (подвержено ошибкам)
Alwin Kesler