Recientemente implementamos un sistema que necesita manejar valores en múltiples monedas y convertir entre ellos, y descubrimos algunas cosas de la manera difícil.
NUNCA USE NÚMEROS DE PUNTO FLOTANTE PARA DINERO
La aritmética de punto flotante introduce imprecisiones que pueden no notarse hasta que hayan arruinado algo. Todos los valores deben almacenarse como enteros o tipos de decimales fijos, y si elige usar un tipo de decimales fijos, asegúrese de comprender exactamente qué hace ese tipo debajo del capó (es decir, usa internamente un número entero o coma flotante). tipo).
Cuando necesite hacer cálculos o conversiones:
- Convertir valores a coma flotante
- Calcular nuevo valor
- Redondea el número y vuelve a convertirlo en un número entero.
Cuando convierta un número de coma flotante a un número entero en el paso 3, no solo lo eche: use una función matemática para redondearlo primero. Esto generalmente será round
, aunque en casos especiales podría ser floor
o ceil
. Conozca la diferencia y elija con cuidado.
Almacene el tipo de un número junto al valor
Esto puede no ser tan importante para usted si solo maneja una moneda, pero fue importante para nosotros en el manejo de múltiples monedas. Utilizamos el código de 3 caracteres para una moneda, como USD, GBP, JPY, EUR, etc.
Dependiendo de la situación, también puede ser útil almacenar:
- Si el número es antes o después de impuestos (y cuál fue la tasa de impuestos)
- Si el número es el resultado de una conversión (y de qué se convirtió)
Conozca los límites de precisión de los números con los que está tratando
Para valores reales, desea ser tan preciso como la unidad más pequeña de la moneda. Esto significa que no tiene valores menores que un centavo, un centavo, un yen, un fen, etc. No almacene valores con mayor precisión que eso sin ninguna razón.
Internamente, puede optar por tratar con valores más pequeños, en cuyo caso ese es un tipo diferente de valor de moneda . Asegúrese de que su código sepa cuál es cuál y no los mezcla. Evite usar valores de coma flotante incluso aquí.
Sumando todas esas reglas juntas, decidimos las siguientes reglas. Al ejecutar el código, las monedas se almacenan utilizando un número entero para la unidad más pequeña.
class Currency {
String code; // eg "USD"
int value; // eg 2500
boolean converted;
}
class Price {
Currency grossValue;
Currency netValue;
Tax taxRate;
}
En la base de datos, los valores se almacenan como una cadena en el siguiente formato:
USD:2500
Eso almacena el valor de $ 25.00. Pudimos hacer eso solo porque el código que se ocupa de las monedas no necesita estar dentro de la capa de la base de datos, por lo que todos los valores se pueden convertir en memoria primero. Sin duda, otras situaciones se prestarán a otras soluciones.
Y en caso de que no lo deje claro antes, ¡no use flotador!