c currency

Cómo representar moneda o dinero en C



currency (4)

TL; DR 1 ¿Cuál es un enfoque preciso y sostenible para representar la moneda o el dinero en C?

Antecedentes de la pregunta:
Esto ha sido respondido para varios otros idiomas, pero no pude encontrar una respuesta sólida para el lenguaje C.

  • C # ¿Qué tipo de datos debo usar para representar dinero en C #?

  • Java ¿Por qué no usar Double o Float para representar la moneda?

  • Objective-C ¿Cómo representar dinero en Objective-C / iOS?

Nota: Hay muchas más preguntas similares para otros idiomas, solo hice algunas para fines de representación.

Todas esas preguntas se pueden resumir en "usar un tipo de datos decimal ", donde el tipo específico puede variar según el idioma.

Hay una pregunta relacionada que termina sugiriendo usar un enfoque de "punto fijo", pero ninguna de las respuestas aborda el uso de un tipo de datos específico en C.

Del mismo modo, he examinado bibliotecas de precisión arbitrarias como GMP , pero no me queda claro si este es el mejor enfoque para usar o no.

Supuestos simplificadores:

  • Suponga una arquitectura basada en x86 o x64, pero mencione cualquier suposición que pueda afectar una arquitectura basada en RISC, como un chip Power o un chip Arm.

  • La precisión en los cálculos es el requisito principal. La facilidad de mantenimiento sería el próximo requisito. La velocidad de los cálculos es importante, pero es terciaria a los otros requisitos.

  • Los cálculos deben ser capaces de soportar de manera segura las operaciones precisas para el mill , así como los valores de soporte que van hasta los billones (10 ^ 9)

Diferencias de otras preguntas:

Como se señaló anteriormente, este tipo de pregunta se ha hecho anteriormente para varios otros idiomas. Esta pregunta es diferente de las otras preguntas por un par de razones.

Usando la respuesta aceptada de: ¿Por qué no usar Double o Float para representar la moneda? , destaquemos las diferencias.

( Solución 1 ) Una solución que funciona en casi cualquier idioma es usar enteros en su lugar y contar centavos. Por ejemplo, 1025 sería $ 10.25. Varios idiomas también tienen tipos incorporados para lidiar con el dinero. ( Solución 2 ) Entre otros, Java tiene la clase BigDecimal y C # tiene el tipo decimal.

Se agregó énfasis para resaltar las dos soluciones sugeridas

La primera solución es esencialmente una variante del enfoque de "punto fijo". Hay un problema con esta solución porque el rango sugerido (centavos de seguimiento) es insuficiente para los cálculos basados ​​en el molino y se perderá información significativa en el redondeo.

La otra solución es usar una clase decimal nativa que no esté disponible dentro de C.

Del mismo modo, la respuesta no considera otras opciones, como crear una estructura para manejar estos cálculos o usar una biblioteca de precisión arbitraria. Esas son diferencias comprensibles ya que Java no tiene estructuras y por qué considerar una biblioteca de terceros cuando hay soporte nativo dentro del lenguaje.

Esta pregunta es diferente de esa pregunta y otras preguntas relacionadas porque C no tiene el mismo nivel de soporte de tipo nativo y tiene características de lenguaje que los otros idiomas no tienen. Y no he visto ninguna de las otras preguntas que aborden las múltiples formas en que esto podría abordarse en C.

La pregunta:
Según mi investigación, parece que float no es un tipo de datos apropiado para representar la moneda dentro de un programa C debido a un error de coma flotante.

¿Qué debo usar para representar dinero en C, y por qué ese enfoque es mejor que otros enfoques?

1 Esta pregunta comenzó en una forma más corta, pero los comentarios recibidos indicaron la necesidad de aclarar la pregunta.


La mejor representación de dinero / moneda es usar un tipo de coma flotante de precisión suficientemente alta como double que tenga FLT_RADIX == 10 . Estas plataformas / cumplidores son raros ya que la gran mayoría de los sistemas tienen FLT_RADIX == 2 .

Cuatro alternativas: enteros, coma flotante no decimal, coma flotante decimal especial, estructura definida por el usuario.

Enteros : una solución común utiliza el recuento de enteros de la denominación más pequeña en la moneda de elección. Ejemplo contando centavos de dólar en lugar de dólares. El rango de enteros debe ser razonablemente amplio. Algo como long long lugar de int como int solo puede manejar alrededor de +/- $ 320.00. Esto funciona bien para tareas de contabilidad simples que involucran sumar / restar / múltiples, pero comienza a agrietarse con divisiones y funciones complejas como se usa en los cálculos de intereses. Fórmula de pago mensual . La matemática entera firmada no tiene protección contra desbordamiento. Se debe tener cuidado al redondear los resultados de la división. q = (a + b/2)/b no es lo suficientemente bueno.

Punto flotante binario : 2 trampas comunes: 1) uso de float que a menudo es de precisión insuficiente y 2) redondeo incorrecto. El uso del pozo double aborda el problema # 1 para muchos límites de contabilidad. Sin embargo, el código todavía necesita usar una ronda a la unidad monetaria mínima deseada para obtener resultados satisfactorios.

// Sample - does not properly meet nuanced corner cases. double RoundToNearestCents(double dollar) { return round(dollar * 100.0)/100.0; }

Una variación de double es usar una cantidad double de la unidad más pequeña (0.01 o 0.001). Una ventaja importante es la capacidad de redondear simplemente usando la función round() que por sí sola cumple con los casos de esquina.

Punto flotante decimal especial Algunos sistemas proporcionan un tipo "decimal" distinto del double que cumple con decimal64 o algo similar. Aunque esto maneja la mayoría de los problemas anteriores, se sacrifica la portabilidad.

La estructura definida por el usuario (como fixed-point ), por supuesto, puede resolver todo, excepto que es propenso a errores de código y es trabajo (en Instead ). El resultado puede funcionar perfectamente pero sin rendimiento.

Conclusión Este es un tema profundo y cada enfoque merece una discusión más amplia. La respuesta general es: no hay una solución general ya que todos los enfoques tienen debilidades significativas. Por lo tanto, depende de los detalles de la aplicación.

[Editar]
Dadas las ediciones adicionales de OP, recomendamos usar el número double de la unidad monetaria más pequeña (ejemplo: $ 0.01 -> double money = 1.0; ). En varios puntos del código siempre que se requiera un valor exacto , use round() .

double interest_in_cents = round( Monthly_payment(0.07/12 /* percent */, N_payments, principal_in_cents));

Mi bola de cristal dice que para 2022 los EE. UU. Perderán los $ 0.01 y la unidad más pequeña será de $ 0.05. Usaría el enfoque que mejor pueda manejar ese cambio.


Si la velocidad es su principal preocupación, use un tipo integral escalado a la unidad más pequeña que necesite representar (como un molino , que es 0.001 dólares o 0.1 centavos). Por lo tanto, 123456 representa $123.456 .

El problema con este enfoque es que puede quedarse sin dígitos; un int sin signo de 32 bits puede representar algo así como 10 dígitos decimales, por lo que el valor más grande que podría representar bajo este esquema sería $9,999,999.999 . No es bueno si necesita lidiar con valores de miles de millones.

Otro enfoque es usar un tipo de estructura con un miembro integral para representar la cantidad total en dólares y otro miembro integral para representar la cantidad fraccional en dólares (nuevamente, escalado a la unidad más pequeña que necesita representar, ya sea centavos, molinos o algo así) más pequeño), similar a la estructura timeval que ahorra segundos enteros en un campo y nanosegundos en el otro:

struct money { long whole_dollars; // long long if you have it and you need it int frac_dollar; };

Un int es más que lo suficientemente ancho como para manejar el escalado que cualquier persona sensata usaría. whole_dollars firmado en caso de que la parte de los whole_dollars sea ​​0.

Si le preocupa más almacenar valores arbitrariamente grandes, siempre hay BCD , que puede representar muchos más dígitos que cualquier tipo de punto flotante o integral integral.

Sin embargo, la representación es solo la mitad de la batalla; también debe poder realizar operaciones aritméticas en estos tipos, y las operaciones en moneda pueden tener reglas de redondeo muy específicas. Por lo tanto, tendrá que tener eso en cuenta al decidir sobre su representación.


Utilice un tipo de datos entero (long long, long, int) o una biblioteca aritmética BCD (decimal codificado en binario). Debe almacenar décimas o centésimas de la cantidad más pequeña que mostrará. Es decir, si usa dólares estadounidenses y presenta centavos (centésimas de dólar), sus valores numéricos deben ser enteros que representen molinos o molinos (décimas o centésimas de centavo). Las cifras significativas adicionales asegurarán que su interés y cálculos similares se redondeen consistentemente.

Si usa un tipo entero, asegúrese de que su rango sea lo suficientemente grande como para manejar las cantidades de preocupación.


int (32 o 64 según lo necesite) y piense en centavos o centavos parciales según sea necesario. Con 32 bits y pensando en centavos, puede representar hasta 40 millones de dólares en un solo valor. Con 64 bits, está mucho más allá de todo el departamento de Estados Unidos jamás combinado .

Hay algunos problemas al hacer los cálculos que debe tener en cuenta para no dividir la mitad de los números significativos.

Es un juego de conocer los rangos y cuándo el redondeo después de la división está bien.

Por ejemplo, hacer una ronda adecuada (de la variante hacia arriba .5) después de la división se puede hacer agregando primero la mitad del numerador al valor y luego haciendo la división. Sin embargo, si está financiando, necesitará un sistema redondo un poco más avanzado, aunque aprobado por sus contadores.

long long res = (amount * interest + 500)/1000;

Solo convierta a dólar (o lo que sea) cuando se comunique con el usuario.