Probablemente debería escalar sus valores decimales en 100 y representar todos los valores monetarios en centavos enteros. Esto es para evitar problemas con la lógica de punto flotante y la aritmética . No hay ningún tipo de datos decimales en JavaScript: el único tipo de datos numéricos es el punto flotante. Por lo tanto, generalmente se recomienda manejar el dinero como 2550
centavos en lugar de 25.50
dólares.
Considere eso en JavaScript:
var result = 1.0 + 2.0; // (result === 3.0) returns true
Pero:
var result = 0.1 + 0.2; // (result === 0.3) returns false
La expresión 0.1 + 0.2 === 0.3
regresa false
, pero afortunadamente la aritmética de enteros en coma flotante es exacta, por lo que los errores de representación decimal se pueden evitar al escalar 1 .
Tenga en cuenta que si bien el conjunto de números reales es infinito, solo un número finito de ellos (18,437,736,874,454,810,627 para ser exactos) puede representarse exactamente mediante el formato de coma flotante de JavaScript. Por lo tanto, la representación de los otros números será una aproximación del número real 2 .
1 Douglas Crockford: JavaScript: The Good Parts : Apéndice A - Awful Parts (página 105) .
2 David Flanagan: JavaScript: la guía definitiva, cuarta edición : 3.1.3 Literales en coma flotante (página 31) .
3000.57 + 0.11 === 3000.68
vuelvefalse
.Escalar cada valor en 100 es la solución. Probablemente sea inútil hacerlo a mano, ya que puede encontrar bibliotecas que lo hagan por usted. Recomiendo moneysafe, que ofrece una API funcional muy adecuada para aplicaciones ES6:
https://github.com/ericelliott/moneysafe
Funciona tanto en Node.js como en el navegador.
fuente
in$, $
nombres de los valores son ambiguos para alguien que no ha usado el paquete antes. Sé que fue la elección de Eric nombrar las cosas de esa manera, pero sigo sintiendo que es un error suficiente que probablemente cambiaría el nombre en la declaración de importación / desestructuración requerida.Money$afe has not yet been tested in production at scale.
. Solo señalarlo para que cualquiera pueda considerar si es apropiado para su caso de usoNo existe el cálculo financiero "preciso" debido a solo dos dígitos de fracción decimal, pero ese es un problema más general.
En JavaScript, puede escalar cada valor en 100 y usarlo
Math.round()
cada vez que puede ocurrir una fracción.Podría usar un objeto para almacenar los números e incluir el redondeo en su
valueOf()
método de prototipos . Me gusta esto:De esa manera, cada vez que use un objeto Money, se representará como redondeado a dos decimales. El valor sin redondear todavía es accesible a través de
m.amount
.Puede construir su propio algoritmo de redondeo
Money.prototype.valueOf()
, si lo desea.fuente
Money(0.1)
significa que el lexer de JavaScript lee la cadena "0.1" de la fuente y luego la convierte en un punto flotante binario y luego ya realizó un redondeo involuntario. El problema es sobre la representación (binario vs decimal) no sobre la precisión .use decimaljs ... Es una muy buena biblioteca que resuelve una parte difícil del problema ...
solo úsalo en todas tus operaciones.
https://github.com/MikeMcl/decimal.js/
fuente
Su problema proviene de la inexactitud en los cálculos de coma flotante. Si solo está usando el redondeo para resolver esto, tendrá un mayor error al multiplicar y dividir.
La solución está a continuación, sigue una explicación:
Tendrás que pensar en las matemáticas detrás de esto para entenderlo. Los números reales como 1/3 no se pueden representar en matemáticas con valores decimales ya que son infinitos (por ejemplo, .333333333333333 ...). Algunos números en decimal no se pueden representar en binario correctamente. Por ejemplo, 0.1 no se puede representar en binario correctamente con un número limitado de dígitos.
Para obtener una descripción más detallada, consulte aquí: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
Eche un vistazo a la implementación de la solución: http://floating-point-gui.de/languages/javascript/
fuente
Debido a la naturaleza binaria de su codificación, algunos números decimales no se pueden representar con una precisión perfecta. Por ejemplo
Si necesita usar JavaScript puro, debe pensar en la solución para cada cálculo. Para el código anterior, podemos convertir decimales en enteros enteros.
Evitar problemas con la matemática decimal en JavaScript
Hay una biblioteca dedicada para cálculos financieros con excelente documentación. Finance.js
fuente
Desafortunadamente, todas las respuestas hasta ahora ignoran el hecho de que no todas las monedas tienen 100 subunidades (por ejemplo, el centavo es la subunidad del dólar estadounidense (USD)). Las monedas como el dinar iraquí (IQD) tienen 1000 subunidades: un dinar iraquí tiene 1000 fils. El yen japonés (JPY) no tiene subunidades. Entonces, "multiplicar por 100 para hacer aritmética de enteros" no siempre es la respuesta correcta.
Además, para los cálculos monetarios, también debe realizar un seguimiento de la moneda. No puede agregar un dólar estadounidense (USD) a una rupia india (INR) (sin convertir primero uno a otro).
También hay limitaciones en la cantidad máxima que puede ser representada por el tipo de datos enteros de JavaScript.
En los cálculos monetarios, también debe tener en cuenta que el dinero tiene una precisión finita (generalmente de 0 a 3 puntos decimales) y el redondeo debe hacerse de formas particulares (por ejemplo, redondeo "normal" versus redondeo bancario). El tipo de redondeo a realizar también puede variar según la jurisdicción / moneda.
Cómo manejar el dinero en JavaScript tiene una muy buena discusión de los puntos relevantes.
En mis búsquedas encontré la biblioteca dinero.js que aborda muchos de los problemas con los cálculos monetarios . Todavía no lo he usado en un sistema de producción, así que no puedo dar una opinión informada al respecto.
fuente