todas - La mejor forma de almacenar valores de moneda en C++
funciones en c++ ejemplos (19)
Sé que un flotante no es apropiado para almacenar valores de moneda debido a errores de redondeo. ¿Hay una forma estándar de representar el dinero en C ++?
Miré en la biblioteca de impulso y no encontré nada al respecto. En Java, parece que BigInteger es el camino, pero no pude encontrar un equivalente en C ++. Podría escribir mi propia clase de dinero, pero prefiero no hacerlo si hay algo probado.
¡El mayor problema es redondearse!
19% de 42,50 € = 8,075 €. Debido a las reglas alemanas para el redondeo, esto es 8,08 €. El problema es que (al menos en mi máquina) 8,075 no se puede representar como doble. Incluso si cambio la variable en el depurador a este valor, termino con 8,0749999 ....
Y aquí es donde mi función de redondeo (y cualquier otra lógica de punto flotante que pueda pensar) falla, ya que produce 8,07 €. El dígito significativo es 4 y el valor se redondea hacia abajo. Y eso está completamente equivocado y no puede hacer nada al respecto a menos que evite usar valores de coma flotante siempre que sea posible.
Funciona muy bien si representas 42,50 € como Integer 42500000.
42500000 * 19/100 = 8075000. Ahora puede aplicar la regla de redondeo por encima de 8080000. Esto se puede transformar fácilmente en un valor de moneda por motivos de visualización. 8,08 €.
Pero siempre terminaría eso en una clase.
Almacene el monto en dólares y centavos como dos enteros separados.
Conozca SU rango de datos.
Un flotador solo es bueno para 6 a 7 dígitos de precisión, por lo que significa un máximo de alrededor de + -9999.99 sin redondeo. Es inútil para la mayoría de las aplicaciones financieras.
Un doble es válido para 13 dígitos, por lo tanto: + -99,999,999,999.99, pero tenga cuidado al usar números grandes. Reconozca que restar dos resultados similares elimina gran parte de la precisión (consulte un libro sobre Análisis numérico para ver si hay problemas potenciales).
El entero de 32 bits es bueno a + -2 Billones (escalar a centavos va a caer 2 decimales)
El entero de 64 bits manejará cualquier cantidad de dinero, pero nuevamente, tenga cuidado al convertir, y multiplique por varias tarifas en su aplicación que pueden ser flotantes / dobles.
La clave es entender el dominio de tu problema. ¿Qué requisitos legales tienes para la precisión? ¿Cómo mostrarás los valores? ¿Con qué frecuencia se llevará a cabo la conversión? ¿Necesitas internacionalización? Asegúrese de poder responder estas preguntas antes de tomar su decisión.
Depende de los requisitos de su empresa con respecto al redondeo. La forma más segura es almacenar un número entero con la precisión requerida y saber cuándo / cómo aplicar el redondeo.
Dice que ha buscado en la biblioteca de impulso y no ha encontrado nada al respecto. Pero ahí tienes multiprecision/cpp_dec_float que dice:
La raíz de este tipo es 10. Como resultado, puede comportarse sutilmente de forma diferente a los tipos de base-2.
Entonces, si ya está usando Boost, esto debería ser bueno para los valores y operaciones de la divisa, como su número de base 10 y precisión de 50 o 100 dígitos (mucho).
Ver:
#include <iostream>
#include <iomanip>
#include <boost/multiprecision/cpp_dec_float.hpp>
int main()
{
float bogus = 1.0 / 3.0;
boost::multiprecision::cpp_dec_float_50 correct = 1.0 / 3.0;
std::cout << std::setprecision(16) << std::fixed
<< "float: " << bogus << std::endl
<< "cpp_dec_float: " << correct << std::endl;
return 0;
}
Salida:
flotador: 0.3333333432674408
cpp_dec_float: 0.3333333333333333
* No estoy diciendo que el flotador (base 2) es malo y el decimal (base 10) es bueno. Simplemente se comportan de manera diferente ...
** Sé que esta es una publicación anterior y boost :: multiprecision se introdujo en 2013, así que quería comentarlo aquí.
Enteros, siempre: guárdelo como centavos (o lo que sea que sea la moneda más baja para la que está programando). El problema es que no importa lo que haga con punto flotante algún día encontrará una situación en la que el cálculo será diferente si lo hace en punto flotante Redondear en el último minuto no es la respuesta ya que los cálculos de la moneda real se redondean a medida que avanzan.
Tampoco puede evitar el problema cambiando el orden de las operaciones; esto falla cuando tiene un porcentaje que lo deja sin una representación binaria adecuada. Los contables se asustarán si te bajas por un solo centavo.
Habiendo tratado esto en los sistemas financieros reales, puedo decirles que probablemente quieran usar un número con al menos 6 decimales de precisión (asumiendo USD). Con suerte, ya que estás hablando de los valores de la moneda, no te saldrás de aquí. Hay propuestas para agregar tipos decimales a C ++, pero no conozco ninguno que esté realmente allí.
El mejor tipo nativo de C ++ para usar aquí sería largo doble.
El problema con otros enfoques que simplemente usan un int es que tienes que almacenar más que solo tus centavos. A menudo las transacciones financieras se multiplican por valores no enteros y eso te va a meter en problemas ya que $ 100.25 traducido a 10025 * 0.000123523 (por ejemplo, APR) va a causar problemas. Eventualmente terminarás en tierra flotante y las conversiones te costarán mucho.
Ahora el problema no ocurre en la mayoría de las situaciones simples. Te daré un ejemplo preciso:
Dado varios miles de valores de moneda, si multiplicas cada uno por un porcentaje y luego los sumas, terminarás con un número diferente que si hubieras multiplicado el total en ese porcentaje si no mantienes suficientes lugares decimales. Ahora esto podría funcionar en algunas situaciones, pero a menudo te darán varios centavos con bastante rapidez. En mi experiencia general, me aseguro de mantener una precisión de hasta 6 decimales (asegurándome de que la precisión restante esté disponible para la parte del número entero).
También entienda que no importa de qué tipo lo almacene si hace las matemáticas de una manera menos precisa. Si sus cálculos se realizan en terreno de precisión simple, no importa si lo está almacenando con doble precisión. Su precisión será correcta al cálculo menos preciso.
Ahora bien, si no haces otras operaciones que no sean simples sumas o restas y luego guardas el número, estarás bien, pero tan pronto como aparezca algo más complejo que eso, estarás en problemas.
Independientemente del tipo que elija, le recomiendo que lo ajuste en "typedef" para que pueda cambiarlo en otro momento.
La solución es simple, almacenar con la precisión que se requiera, como un entero desplazado. Pero al leer en convertir a doble flotante, los cálculos sufren menos errores de redondeo. Luego, al almacenar en la base de datos, multiplique a la precisión de entero que sea necesaria, pero antes de truncar como un entero agregue +/- 1/10 para compensar los errores de truncamiento, o +/- 51/100 para redondear. Pan comido.
Mire en la relativamente reciente Intelr Decimal Floating-Point Math Library . Es específicamente para aplicaciones de finanzas e implementa algunos de los nuevos estándares para aritmética de coma flotante binaria (IEEE 754r) .
No lo guarde solo como centavos, ya que acumulará errores al multiplicar los impuestos e intereses rápidamente. Como mínimo, conserve dos dígitos adicionales extra: $ 12.45 se almacenarán como 124,500. Si lo mantienes en un entero con signo de 32 bits, tendrás $ 200,000 para trabajar (positivo o negativo). Si necesita números más grandes o más precisión, un número entero de 64 bits firmado le dará todo el espacio que necesitará durante mucho tiempo.
Puede ser de cierta ayuda ajustar este valor en una clase, para darle un lugar para crear estos valores, hacer aritmética sobre ellos y formatearlos para su visualización. Esto también le daría un lugar central para llevar a cabo qué moneda se almacena (USD, CAD, EURO, etc.).
Nuestra institución financiera usa "doble". Como somos una tienda de "ingresos fijos", tenemos muchos algoritmos complicados y desagradables que utilizan el doble de todos modos. El truco está en asegurarse de que la presentación del usuario final no sobrepase la precisión del doble. Por ejemplo, cuando tenemos una lista de transacciones con un total en billones de dólares, tenemos que asegurarnos de no imprimir basura debido a problemas de redondeo.
Puedes probar el tipo de datos decimales:
https://github.com/vpiotr/decimal_for_cpp
Diseñado para almacenar valores orientados al dinero (saldo de dinero, tasa de cambio, tasa de interés), precisión definida por el usuario. Hasta 19 dígitos
Es una solución de solo encabezado para C ++.
Sugeriría que mantenga una variable por el número de centavos en lugar de dólares. Eso debería eliminar los errores de redondeo. Mostrarlo en el formato de dólares / centavos estándar debe ser una preocupación de la vista.
Una opción es almacenar $ 10.01 como 1001, y hacer todos los cálculos en centavos, dividiendo por 100D cuando visualiza los valores.
O use flotadores, y solo redonde en el último momento posible.
A menudo los problemas pueden mitigarse cambiando el orden de las operaciones.
En lugar del valor * .10 para un descuento del 10%, use (valor * 10) / 100, lo que ayudará significativamente. (recuerde que .1 es un binario repetitivo)
Utilizaría firmado largo de 32 bits y firmado mucho tiempo para 64 bits. Esto le dará la máxima capacidad de almacenamiento para la cantidad subyacente. Entonces desarrollaría dos manipuladores personalizados. Una que convierte esa cantidad en función de las tasas de cambio, y otra que formatea esa cantidad en la moneda de su elección. Puede desarrollar más manipuladores para varias operaciones / reglas financieras.
Yo recomendaría usar un int largo para almacenar la moneda en la denominación más pequeña (por ejemplo, el dinero estadounidense sería centavos), si se usa una moneda decimal.
Muy importante: asegúrese de nombrar todos los valores de su moneda de acuerdo con lo que realmente contienen. (Ejemplo: account_balance_cents) Esto evitará muchos problemas en el futuro.
(Otro ejemplo en el que esto aparece son los porcentajes. Nunca nombre un valor "XXX_percent" cuando en realidad contenga una proporción no multiplicada por cien).
seguir adelante y escribir su propio dinero ( http://junit.sourceforge.net/doc/testinfected/testing.htm ) o la clase de moneda () (dependiendo de lo que necesita). y probarlo.