Puedo encontrar muchas preguntas sobre bibliotecas para representar cantidades en alguna moneda. Y sobre el viejo problema de por qué no debe almacenar moneda como un número de coma flotante IEEE 754. Pero parece que no puedo encontrar nada más. Seguramente hay mucho más que saber sobre la moneda en el uso del mundo real. Estoy particularmente interesado en lo que necesita saber para representarlo en el uso físico (por ejemplo, con el dólar, nunca tiene una precisión de menos de $ 0.01, lo que permite la representación como un número entero de centavos).
Pero es difícil hacer suposiciones sobre cuán versátil es su programa cuando las únicas monedas que conoce son las populares occidentales (como el dólar, el euro y la libra). ¿Qué más es conocimiento relevante en una perspectiva puramente programática? No me preocupa el tema de la conversión.
En particular, ¿qué necesitamos saber para poder almacenar valores en alguna moneda e imprimirlos?
Respuestas:
¿Oh enserio?
No dude en almacenar pulgadas en números de punto flotante IEEE 754 . Almacenan exactamente como cabría esperar.
No dude en almacenar cualquier cantidad de dinero en números de coma flotante IEEE 754 que pueda almacenar utilizando los ticks que dividen una regla en fracciones de pulgada.
¿Por qué? Porque cuando usa IEEE 754 , así es como lo almacena.
Lo que pasa con las pulgadas es que están divididas en mitades. Lo que pasa con la mayoría de los tipos de moneda es que están divididos en décimas (algunos tipos no lo son, pero centrémonos).
¡Esta diferencia no sería tan confusa, excepto que, para la mayoría de los lenguajes de programación, la entrada y salida de los números de coma flotante IEEE 754 se expresa en decimales! Lo cual es muy extraño porque no están almacenados en decimales.
Debido a esto, nunca puedes ver cómo los bits hacen cosas raras cuando le pides a la computadora que almacene
0.1
. Solo ves la rareza cuando haces cálculos matemáticos y tiene errores extraños.Del efectivo java de Josh Bloch :
Produce
0.6100000000000001
Lo más revelador de esto no es el
1
camino sentado a la derecha. Son los números extraños que tuvieron que usarse para obtenerlo. En lugar de usar el ejemplo más popular0.1
, tenemos que usar un ejemplo que muestre el problema y evite el redondeo que lo ocultaría.Por ejemplo, ¿por qué funciona esto?
Produce
-0.01
Porque tuvimos suerte.
Odio los problemas que son difíciles de diagnosticar porque a veces tengo "suerte".
IEEE 754 simplemente no puede almacenar 0.1 con precisión. Pero si le pide que almacene 0.1 y luego le pida que imprima, mostrará 0.1 y pensará que todo está bien. No está bien, pero no puedes ver eso porque se está redondeando para volver a 0.1.
Algunas personas confunden a otros llamando a estas discrepancias redondeando los errores. No, estos no son errores de redondeo. El redondeo es hacer lo que se supone que debe hacer y convertir lo que no es un decimal en un decimal para que se pueda imprimir en la pantalla.
Pero esto oculta la falta de coincidencia entre cómo se muestra el número y cómo se almacena. El error no ocurrió cuando ocurrió el redondeo. Sucedió cuando decidió poner un número en un sistema que no puede almacenarlo con precisión y asumió que se estaba almacenando precisamente cuando no lo estaba.
Nadie espera que π se almacene con precisión en una calculadora y logran trabajar con ella perfectamente. Entonces, el problema ni siquiera es acerca de la precisión. Se trata de la precisión esperada. Las computadoras muestran una décima como
0.1
lo hacen nuestras calculadoras, por lo que esperamos que almacenen una décima perfectamente como lo hacen nuestras calculadoras. Ellos no. Lo cual es sorprendente, ya que las computadoras son más caras.Déjame mostrarte el desajuste:
Observe que 1/2 y 0.5 se alinean perfectamente. Pero 0.1 simplemente no se alinea. Claro que puedes acercarte si sigues dividiendo por 2, pero nunca lo acertarás exactamente. Y necesitamos más y más bits cada vez que dividimos por 2. Por lo tanto, representar 0.1 con cualquier sistema que divide por 2 necesita un número infinito de bits. Mi disco duro no es tan grande.
Por lo tanto, IEEE 754 deja de intentarlo cuando se queda sin bits. Lo cual es bueno porque necesito espacio en mi disco duro para ... fotos familiares. No realmente. Fotos de familia. :PAG
De todos modos, lo que escribe y lo que ve son los decimales (a la derecha), pero lo que almacena son bicimales (a la izquierda). A veces esos son perfectamente iguales. A veces no lo son. A veces parece que son iguales cuando simplemente no lo son. Ese es el redondeo.
Por favor, si está manejando mi dinero basado en decimales, no use flotadores o dobles.
Si está seguro de que cosas como décimas de centavos no estarán involucradas, simplemente almacene centavos. Si no lo está, averigüe cuál será la unidad más pequeña de esta moneda y úsela. Si no puede, use algo como BigDecimal .
Mi patrimonio neto probablemente siempre encajará en un entero de 64 bits, pero cosas como BigInteger funcionan bien para proyectos más grandes que eso. Son solo más lentos que los tipos nativos.
Descubrir cómo almacenarlo es solo la mitad del problema. Recuerde que también debe poder mostrarlo. Un buen diseño separará estas dos cosas. El verdadero problema con el uso de flotadores aquí es que esas dos cosas se mezclan.
fuente
Las "bibliotecas" son innecesarias a menos que la biblioteca estándar de su idioma carezca de ciertos tipos de datos, como explicaré.
En pocas palabras, necesita coma decimal de punto fijo , no decimal de coma flotante . Por ejemplo, la clase BigDecimal de Java podría usarse para almacenar una cantidad de moneda. Otros lenguajes modernos tienen tipos similares incorporados, incluidos C # y Python . Las implementaciones varían, pero generalmente almacenan un número como un entero, con la ubicación decimal como un miembro de datos separado. Esto proporciona una precisión exacta, incluso cuando se realiza una aritmética que daría restos impares (por ejemplo, 0.0000001) con números de coma flotante IEEE.
Hay algunos puntos importantes.
Use un tipo decimal real en lugar de coma flotante.
Comprenda que el monto de una moneda tiene dos componentes: un valor (5.63) y un código o tipo de moneda (USD, CAD, GBP, EUR, etc.). A veces puede ignorar el código de moneda, otras veces es vital. ¿Qué sucede si está trabajando en un sistema financiero o minorista / de comercio electrónico que permite múltiples monedas? ¿Qué sucede si está tratando de tomar dinero de un cliente en CAD, pero quiere pagar con MXN? Necesita un tipo de "dinero" con un código de moneda y un monto de moneda para poder mezclar estos valores (también tipo de cambio, pero no quiero llegar demasiado lejos en una tangente). Al mismo tiempo, mi software de finanzas personales nunca necesita preocuparse por esto porque todo está en USD ( puede mezclar monedas, pero nunca lo necesito).
Si bien una moneda puede tener una unidad física más pequeña en la práctica (CAD y USD tienen centavos, JPY es solo ... Yen) es posible reducir su tamaño. La respuesta de CandiedOrange señala los precios del combustible en décimas de centavo. Mis impuestos a la propiedad se evalúan como molinos por dólar, o décimas de centavo (1/1000 de un dólar USD). No se limite a $ 0.01. Si bien puede mostrar esos valores la mayor parte del tiempo, sus tipos deberían permitir valores más pequeños (los tipos decimales mencionados anteriormente lo hacen).
Los cálculos intermedios ciertamente deben permitir más precisión que un solo centavo. He trabajado en sistemas minoristas / de comercio electrónico donde los valores internos se redondearon internamente a $ 0.00000001. La precisión infinita generalmente no es compatible con los tipos decimales (o SQL), por lo que debe haber algún límite. Por ejemplo, dividir 1/3 usando BigDecimal de Java arrojará una excepción sin un RoundingMode o MathContext especificado porque el valor no puede representarse exactamente.
De todos modos, esto es crítico en ciertos casos. Supongamos que tiene un usuario con seis artículos en su carrito de compras, y él va a pagar. Su sistema tiene que calcular los impuestos, y lo hace por artículo porque los artículos pueden estar sujetos a impuestos diferentes. Si redondea los impuestos en cada artículo, puede recibir errores de redondeo de centavo a nivel de transacción / carrito. Un enfoque para solucionar esto podría ser almacenar los impuestos en más decimales por artículo, obtener el total de la transacción completa y volver y redondear cada artículo para que el impuesto total sea correcto (tal vez un artículo se redondea hacia arriba y otro hacia abajo).
Lo importante de todo esto es darse cuenta de que algo tan importante como redondear centavos puede ser muy importante para las personas adecuadas (por ejemplo, algunos de mis clientes anteriores que tuvieron que pagar los impuestos sobre las ventas del gobierno en nombre de sus clientes). Sin embargo, todos estos son problemas resueltos. Tenga en cuenta los puntos anteriores y experimente por su cuenta, y aprenderá haciendo.
fuente
Un lugar donde muchos desarrolladores se enfrentan a una única representación de datos para cualquier moneda es para compras en la aplicación para aplicaciones iOS. Su cliente puede estar conectado a una tienda en casi cualquier país del mundo. Y en esa situación, se le dará un precio de compra que consiste en un número de doble precisión y un código de moneda.
Debe tener en cuenta que los números pueden ser grandes. Hay monedas donde el equivalente de decir diez dólares es más de 100,000 unidades. Y tenemos la suerte de que no haya monedas como el dólar zimbabuense en este momento, ¡donde podría tener cien billones de billetes!
Para mostrar monedas, necesitará alguna biblioteca; no tiene la oportunidad de hacerlo todo usted mismo. La pantalla depende de dos cosas: el código de moneda y la configuración regional del usuario. Piense cómo se mostrarían los dólares estadounidenses y canadienses con una configuración regional de EE. UU. Y una configuración regional de Canadá: en los EE. UU., Usted tiene $ vs CAN $, y en Canadá tiene US $ vs. $. Espero que esté integrado en el sistema operativo, o que tenga una buena biblioteca.
Para los cálculos, cualquier cálculo finalizará con un paso de redondeo. Tendrá que averiguar cómo debe realizar ese redondeo legalmente . Eso no es un problema de programación, es un problema legal. Por ejemplo, si calcula el IVA en el Reino Unido, debe calcular el impuesto por artículo o por línea de artículo y redondearlo a centavos. Lo que redondeas depende de la moneda. Pero las reglas obviamente dependen del país. No puede esperar que un cálculo que sea legalmente correcto en el Reino Unido sea legalmente correcto en Japón y viceversa.
fuente
Algunos problemas de ejemplo relacionados con la configuración regional:
Otro problema potencial es que incluso si almacena la moneda correctamente en un número de punto fijo, es posible que deba convertirse en un número de punto flotante porque la biblioteca utilizada para imprimir solo admite punto flotante.
fuente