¿Existe una representación de datos única que funcione para todas las monedas (incluso las diferentes de dólares, euros y libras)?


12

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?


2
No todos los programadores trabajan en industrias que manipulan cantidades de divisas.
whatsisname

44
Oh por supuesto. Pero eso se puede decir sobre casi cualquier cosa. Creo que podemos suponer que quienes encontrarían útil esta pregunta serían aquellos que trabajan con divisas.
Kat

55
He realizado muchos trabajos de desarrollo para bancos y otras empresas de servicios financieros en Nueva York. Y puedo garantizarle que no hay una respuesta de "forma cerrada" a su pregunta. También podrías preguntar, "¿cuántas matemáticas tengo que saber?" Por ejemplo, en la década de 1990, los precios de las acciones pasaron de octavos y 256 a decimales, lo que requirió mucha reprogramación. ¿Quién podría haber adivinado que sucedería? Solo tiene que comprender el negocio que admiten sus aplicaciones y cómo representar mejor los datos (en este caso, la moneda) para satisfacer esas necesidades comerciales.
John Forkosh

44
Mi 0.02: El precio es una cosa. La moneda es otra.
Machado

3
Pensé, Oh, debe haber algún denominador común para todas las monedas. Pero tal vez no. La moneda de un centavo es actualmente 1⁄100 de una libra, pero era 1⁄240 de una libra. Entonces, no solo tiene una división de 1/240, sino que también tiene una fecha de cambio, y posiblemente una tasa de cambio por peniques viejos que aún no se ha cambiado. en.wikipedia.org/wiki/Penny_sterling ¡Ni siquiera me hagas empezar con los cascarones! en.wikipedia.org/wiki/Shell_money o el hecho de que el dinero requiere que la creencia se vuelva real: en.wikipedia.org/wiki/Money ¡ Gran pregunta incluso si no encaja bien con StackExchange!
GlenPeterson

Respuestas:


24

por ejemplo, con el dólar, nunca tiene una precisión de menos de $ 0.01

¿Oh enserio? ingrese la descripción de la imagen aquí

El antiguo problema de por qué no debe almacenar moneda como un número de coma flotante IEEE 754.

ingrese la descripción de la imagen aquí

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 :

System.out.println(1.03 - .42);

Produce 0.6100000000000001

Lo más revelador de esto no es el 1camino sentado a la derecha. Son los números extraños que tuvieron que usarse para obtenerlo. En lugar de usar el ejemplo más popular 0.1, tenemos que usar un ejemplo que muestre el problema y evite el redondeo que lo ocultaría.

Por ejemplo, ¿por qué funciona esto?

System.out.println(.01 - .02);

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.1lo 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:

ingrese la descripción de la imagen aquí

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.

En particular, ¿qué necesitamos saber para poder almacenar valores en alguna moneda e imprimirlos?

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.

ingrese la descripción de la imagen aquí


66
Realmente no estaba buscando otra explicación de por qué IEEE 754 no funciona por dinero. Mencioné en el OP que esto se ha explicado bien en otra parte. Del mismo modo, hay una razón por la que usé el término "uso físico". Principalmente porque, si bien los precios de la gasolina, los valores de la bolsa de valores, etc., pueden tener una precisión arbitraria, el dinero físico (y la gran cantidad de transacciones de usuarios) no pasan del lugar de los cientos (y varias piezas de software quieren representar solo lo que se puede pagar con dinero fisico). Una pieza de información que me preguntaba era si existían monedas que entraban en más decimales.
Kat

3
La "rareza" del punto flotante no se limita solo a la mitad frente a 1/10 de. El aspecto de "punto flotante" en el nombre también causa resultados a veces inesperados.
whatsisname

66
Antes de la decimalización, la moneda del Reino Unido era libras, chelines y peniques, (£ sd), con £ 1 == 20s, 1s = 12d, entonces £ 1 = 240d, así que 1d = 0.0046666666666, por lo que ni siquiera podía representarse como un decimal. Antes de unirse al euro, la lira italiana superó los 2346.4 (en 2001) a los dólares estadounidenses, por lo que cosas como los salarios y los precios de la vivienda a veces alcanzaron el límite superior de flotadores de precisión individuales y las cifras económicas alcanzaron los niveles superiores de dobles.
Steve Barnes

2
Solo argumentaría / señalaría que la moneda es una cosa, y el precio es otra. Puede tener una precisión de más de $ 0.01 en los precios, pero no puede pagar efectivamente con esa precisión.
Machado

1
Además, fotos familiares . Pft. :)
Machado

10

Puedo encontrar muchas preguntas sobre bibliotecas para representar cantidades en alguna moneda.

Las "bibliotecas" son innecesarias a menos que la biblioteca estándar de su idioma carezca de ciertos tipos de datos, como explicaré.

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).

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.

En particular, ¿qué necesitamos saber para poder almacenar valores en alguna moneda e imprimirlos?

Hay algunos puntos importantes.

  1. Use un tipo decimal real en lugar de coma flotante.

  2. 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).

  3. 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).

  4. 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.


En 2, creo que el tipo de cambio es suficiente para obtener al menos su propio número. Debe tener en cuenta que las tarifas cambian con el tiempo, lo que tiene un par de formas diferentes de manejo.
Izkata

2
+1. Además, las monedas cambian con el tiempo. En Brasil tuvimos repetidamente cambios de moneda durante los años 80 y 90, reduciendo los ceros y renombrando la moneda cuando fue necesario debido a una inflación fuera de control. Eso significa que cada aspecto de una moneda puede cambiar con el tiempo.
Machado

@Izkata ciertamente, he trabajado en sistemas que cambian monedas. Quería tocar el tema ya que es relevante. Sin embargo, ese no es el enfoque de la pregunta y probablemente podría escribir otra respuesta siempre que esta sea sobre el tema del intercambio de divisas.

'Las' bibliotecas 'son innecesarias a menos que la biblioteca estándar de su idioma carezca de ciertos tipos de datos, como explicaré'. Las monedas pueden tener mitades, cuartos, décimos, octavos, etc., lo que significa que al menos necesitamos una representación decimal para ellos. ¿Pero estamos completamente seguros de que no hay monedas con una pieza de 1/3 de la totalidad?
Daniel McLaury

4

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.


0

En particular, ¿qué necesitamos saber para poder almacenar valores en alguna moneda e imprimirlos?

  • "almacenar valores" : un tipo de datos de número de punto fijo y un tipo de moneda. Creo que estos son bastante obvios. El almacenamiento en un número de punto no fijo podría implicar un redondeo y cambiar el valor. El cálculo de la moneda sería un problema diferente.
  • "imprimirlos" : configuración regional del usuario. Si tiene suerte, obtiene la biblioteca estándar para hacer todo, por ejemplo, símbolo de moneda, separador de miles, separador decimal, precisión, etc.

Algunos problemas de ejemplo relacionados con la configuración regional:

  • 100 USD en la configuración regional de los EE. UU. Deben ser de $ 100.00, en otra configuración regional con punto como separador decimal sería de $ 100.00.
  • Algunos países usan miríadas en lugar de miles para agrupar números, por ejemplo, en lugar de 10,000.00 sería solo 1,0000.00
  • Por razones prácticas, las personas no imprimen todos los números para monedas pequeñas, por ejemplo, en lugar de $ 1,000,000,000.00, solo quieren que imprima $ 1 mil millones.

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.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.