¿Cómo manejar los valores monetarios en PHP y MySql?

16

Heredé una gran cantidad de código heredado escrito en PHP sobre una base de datos MySQL. Lo que noté es que la aplicación utiliza doublespara el almacenamiento y la manipulación de datos.

Ahora me encontré con numerosas publicaciones que mencionan cómo doubleno son adecuadas para las operaciones monetarias debido a los errores de redondeo. Sin embargo, todavía tengo que encontrar una solución completa sobre cómo se deben manejar los valores monetarios en el código PHP y almacenarlos en una base de datos MySQL.

¿Existe una mejor práctica cuando se trata de manejar dinero específicamente en PHP?

Las cosas que estoy buscando son:

  1. ¿Cómo deben almacenarse los datos en la base de datos? tipo de columna? ¿Talla?
  2. ¿Cómo deben manejarse los datos en suma, resta normales? multiplicación o división?
  3. ¿Cuándo debo redondear los valores? ¿Cuánto redondeo es aceptable si hay alguno?
  4. ¿Hay alguna diferencia entre manejar valores monetarios grandes y valores bajos?

Nota: Un código de muestra MUY simplificado de cómo podría encontrar valores monetarios en la vida cotidiana (varias preocupaciones de seguridad fueron ignoradas para simplificar. Por supuesto, en la vida real nunca usaría mi código así):

$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?

Espero que use este código de muestra como un medio para sacar más casos de uso y no tomarlo literalmente, por supuesto.

Pregunta adicional Si voy a usar un ORM como Doctrine o PROPEL, ¿qué tan diferente será usar dinero en mi código?

Songo
fuente
1
No conozco PHP, pero conocer la terminología es invaluable en estos escenarios para google-fu, el término que está buscando es "precisión arbitraria" a la que google responde con php.net/manual/en/book.bc.php
Jimmy Hoffa
1
@Jimmy Hoffa: la precisión arbitraria generalmente no es lo que necesita, es algo que puede representar con precisión y trabajar con fracciones decimales. Por supuesto, con frecuencia encuentra ambos combinados, pero, por ejemplo, el decimaltipo en C # tiene una precisión limitada pero se adapta perfectamente a los valores monetarios.
Michael Borgwardt
1
@MichaelBorgwardt bien, me olvido de los tipos racionales porque muchos idiomas no los tienen. Buena llamada.
Jimmy Hoffa
Después de haber trabajado mucho con las monedas y el código heredado, diría que debe almacenarlo en enteros y usar centavos en lugar de dólares. Sin embargo, tenga cuidado, 32 enteros pueden contener una cantidad sorprendentemente pequeña. 4) un entero de 32 bits solo puede contener una cantidad de 21 millones si no está firmado y usa centavos.
Pieter B
Además, su código de muestra que muestra los precios y descuentos que se PUBLICAN me aterroriza. Esperemos que esté tan simplificado que no refleje el uso real, pero al pie de la letra, parece que está confiando en el navegador para que le diga cuál es el precio de un artículo PUBLICANDO un campo oculto o algo así.
Carson63000

Respuestas:

6

Puede ser bastante complicado manejar números con PHP / MySQL. Si usa decimal (10,2) y su número es más largo o tiene mayor precisión, se truncará sin error (a menos que configure el modo adecuado para su servidor de base de datos).

Para manejar valores grandes o valores de alta precisión, puede usar una biblioteca como BCMath, que le permitirá realizar operaciones básicas en los números grandes y mantener la precisión requerida.

No estoy seguro de qué cálculos exactamente hará, pero también debe tener en cuenta que (0.22 * 0.4576) + (0.78 * 0.4576) no será igual a 0.4576 si no utiliza la precisión adecuada durante el proceso.

El tamaño máximo de DECIMAL en MySQL es 65, por lo que debería ser más que suficiente para cualquier propósito. Si usa el tipo de campo DECIMAL, se devolverá como una cadena, independientemente del uso de un ORM o simplemente PDO / mysql (i).

¿Cómo deben almacenarse los datos en la base de datos? tipo de columna? ¿Talla?

DECIMAL con la precisión que necesitas. Si está utilizando tipos de cambio, necesitará al menos cuatro decimales

¿Cómo deben manejarse los datos en suma, resta normales? multiplicación o división?

Use BCMath para estar del lado de guardar y por qué usar flotador puede no ser una buena idea

¿Cuándo debo redondear los valores? ¿Cuánto redondeo es aceptable si hay alguno?

Para valores monetarios normales, dos decimales son aceptables, pero es posible que necesite más si, por ejemplo, está utilizando tipos de cambio.

¿Hay alguna diferencia entre manejar valores monetarios grandes y valores bajos?

Depende de lo que quieras decir con grande. Definitivamente hay una diferencia entre manejar números con alta precisión.

onlineapplab.com
fuente
9

Una solución simple es almacenarlos como enteros. 99.99 almacenado como 9999. Si esto no funciona (y hay muchas razones por las que esto podría ser una mala elección), puede usar el tipo Decimal. http://dev.mysql.com/doc/refman/5.0/en/precision-math-decimal-changes.html en el lado de mysql. En el lado de php encontré este /programming/3244094/decimal-type-in-php que podría ser lo que buscas.

Pregunta extra: difícil de decir. El orm funcionará en función de los tipos de datos elegidos. Diría que podría hacer algunas cosas con abstracción para ayudar, pero este problema específico no se aborda simplemente moviéndose a un ORM.

Ominus
fuente
1
FWIW, Drupal Commerce utiliza el truco 9999 para almacenar sus precios.
Florian Margaine
1
"y hay muchas razones por las cuales esta podría ser una mala elección" ¿Podría compartir algunos de los problemas de esta práctica?
Songo
2
@Songo: ver floating-point-gui.de/formats/integer
Michael Borgwardt
@MichaelBorgwardt Gracias por la información, Songo, perdón por la demora, el huracán nos sacó :)
Ominus
3

Trataré de poner mi experiencia en esto:

Las cosas que estoy buscando son:

¿Cómo deben almacenarse los datos en la base de datos? tipo de columna? ¿Talla?

He estado usando DECIMAL(10,2)mysql sin problemas (8 completos y 2 decimales == 99.999.999,99 == gran cantidad), pero esto depende del rango de dinero que deberá cubrir. Se deben tomar grandes cantidades con mucho cuidado (por ejemplo, valores flotantes máximos del sistema operativo). En la parte decimal utilizo 2 valores para evitar truncar ni redondear valores. En cuanto al dinero, hay pocos casos en los que necesita más decimales (en cuyo caso debe asegurarse de que el usuario trabaje con todos ellos; de lo contrario, son datos inútiles)

¿Cómo deben manejarse los datos en suma, resta normales? multiplicación o división?

Trabaje con una moneda y una tabla de cambio (con fechas). De esta manera, se asegura de que siempre tendrá la cantidad correcta guardada. Un extra: guardar valores completos y crear una vista con resultados de cálculo. Esto lo ayudará a fijar valores sobre la marcha.

¿Cuándo debo redondear los valores? ¿Cuánto redondeo es aceptable si hay alguno?

Nuevamente, depende del rango de dinero de su sistema. Siempre piense en términos de KISS, a menos que necesite caer en el desorden del cambio de divisas.

¿Hay alguna diferencia entre manejar valores monetarios grandes y valores bajos?

Dependiendo de su sistema operativo y lenguajes de programación, siempre necesita verificar sus valores máximos y mínimos

Alwin Kesler
fuente
Gracias por la respuesta, pero si tengo que manejar algo como $valueToBeStored= $a * $b;si$a y $bambos se leen como decimales de la base de datos, creo que se enviarán a doublePHP ¿verdad? ¿afectará eso a los números?
Songo
$ay $bse toman de db? por lo tanto, en mi ejemplo, no necesita almacenar nunca $valueToBeStoredporque siempre tendrá la fuente $ay los $bdatos. Por lo tanto, puede trabajar programáticamente el valor en una función o crear una vista mysql con el resultado de la columna. De esta manera, si se debe cambiar algún valor, no tiene que preocuparse por modificar varios lugares (propenso a errores)
Alwin Kesler