Tengo el siguiente script de prueba ficticio:
function test() {
var x = 0.1 * 0.2;
document.write(x);
}
test();
Esto imprimirá el resultado, 0.020000000000000004
mientras que solo debería imprimir 0.02
(si usa su calculadora). Según tengo entendido, esto se debe a errores en la precisión de multiplicación de coma flotante.
¿Alguien tiene una buena solución para que en tal caso obtenga el resultado correcto 0.02
? Sé que hay funciones como toFixed
o el redondeo sería otra posibilidad, pero me gustaría tener el número entero impreso sin ningún corte y redondeo. Solo quería saber si alguno de ustedes tiene una solución agradable y elegante.
Por supuesto, de lo contrario redondearé a unos 10 dígitos más o menos.
0.1
a un número finito de punto flotante binario.Respuestas:
De la guía de punto flotante :
Tenga en cuenta que el primer punto solo se aplica si realmente necesita un comportamiento decimal preciso específico . La mayoría de las personas no necesitan eso, simplemente están molestos porque sus programas no funcionan correctamente con números como 1/10 sin darse cuenta de que ni siquiera parpadearían ante el mismo error si ocurriera con 1/3.
Si el primer punto realmente se aplica a usted, use BigDecimal para JavaScript , que no es elegante en absoluto, pero en realidad resuelve el problema en lugar de proporcionar una solución imperfecta.
fuente
console.log(9332654729891549)
en realidad impresiones9332654729891548
(es decir, fuera por uno!);P
... Entre2⁵²
=4,503,599,627,370,496
y2⁵³
=9,007,199,254,740,992
los números representables son exactamente los enteros . Para el siguiente rango, de2⁵³
a2⁵⁴
, todo se multiplica por2
, por lo que los números representables son los pares , etc. Por el contrario, para el rango anterior de2⁵¹
a2⁵²
, el espaciado es0.5
, etc. Esto se debe simplemente a aumentar | disminuir la base | radix 2 | exponente binario en / del valor flotante de 64 bits (que a su vez explica el comportamiento 'inesperado' raramente documentado detoPrecision()
para valores entre0
y1
).Me gusta la solución de Pedro Ladaria y uso algo similar.
A diferencia de la solución de Pedros, esto redondeará 0.999 ... repitiendo y es exacto a más / menos uno en el dígito menos significativo.
Nota: Cuando se trata de flotantes de 32 o 64 bits, debe usar toPrecision (7) y toPrecision (15) para obtener mejores resultados. Vea esta pregunta para obtener información sobre por qué.
fuente
toPrecision
devuelve una cadena en lugar de un número. Esto puede no ser siempre deseable.(9.99*5).toPrecision(2)
= 50 en lugar de 49.95 porque toPrecision cuenta el número entero, no solo decimales. Luego puede usartoPrecision(4)
, pero si su resultado es> 100, entonces no tendrá suerte nuevamente, ya que permitirá los primeros tres números y un decimal, de esa manera desplazará el punto y lo hará más o menos inutilizable. Terminé usando en sutoFixed(2)
lugarPara los matemáticamente inclinados: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
El enfoque recomendado es utilizar factores de corrección (multiplicar por una potencia adecuada de 10 para que la aritmética ocurra entre enteros). Por ejemplo, en el caso de
0.1 * 0.2
, el factor de corrección es10
, y está realizando el cálculo:Una solución (muy rápida) se parece a:
En este caso:
Definitivamente recomiendo usar una biblioteca probada como SinfulJS
fuente
¿Solo estás realizando multiplicación? Si es así, puede utilizar a su favor un secreto claro sobre la aritmética decimal. Es decir que
NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals
. Es decir que si tenemos,0.123 * 0.12
entonces sabemos que habrá 5 lugares decimales porque0.123
tiene 3 lugares decimales y0.12
tiene dos. Por lo tanto, si JavaScript nos dio un número como0.014760000002
podemos redondear con seguridad al quinto decimal sin temor a perder precisión.fuente
Estas buscando un
sprintf
implementación para JavaScript, de modo que pueda escribir flotantes con pequeños errores (ya que están almacenados en formato binario) en el formato que espera.Pruebe javascript-sprintf , lo llamaría así:
para imprimir su número como flotante con dos decimales.
También puede usar Number.toFixed () para fines de visualización, si prefiere no incluir más archivos simplemente para el redondeo de punto flotante a una precisión dada.
fuente
Estoy descubriendo que BigNumber.js satisface mis necesidades.
Tiene buena documentación y el autor responde muy diligentemente a los comentarios.
El mismo autor tiene otras 2 bibliotecas similares:
Big.js
y Decimal.js
Aquí hay un código usando BigNumber:
fuente
---o---
---además---
--- como en ---
fuente
Esta función determinará la precisión necesaria a partir de la multiplicación de dos números de coma flotante y devolverá un resultado con la precisión adecuada. Elegante aunque no lo es.
fuente
Sorprendentemente, esta función aún no se ha publicado, aunque otras tienen variaciones similares. Es de los documentos web de MDN para Math.round (). Es conciso y permite una precisión variable.
console.log (precisionRound (1234.5678, 1)); // salida esperada: 1234.6
console.log (precisionRound (1234.5678, -1)); // salida esperada: 1230
ACTUALIZACIÓN: 20 / Ago / 2019 Acabo de notar este error. Creo que se debe a un error de precisión de coma flotante con Math.round ().
Estas condiciones funcionan correctamente:
Reparar:
Esto solo agrega un dígito a la derecha al redondear decimales. MDN ha actualizado la página Math.round para que alguien pueda proporcionar una mejor solución.
fuente
Solo tiene que decidir cuántos dígitos decimales realmente desea, no puede tener el pastel y comerlo también :-)
Los errores numéricos se acumulan con cada operación adicional y si no la corta temprano, solo crecerá. Las bibliotecas numéricas que presentan resultados que se ven limpios simplemente cortan los últimos 2 dígitos en cada paso, los coprocesadores numéricos también tienen una longitud "normal" y "completa" por la misma razón. Los cuf-offs son baratos para un procesador pero muy caros para usted en un script (multiplicando y dividiendo y usando pov (...)). Una buena práctica matemática le proporcionaría un piso (x, n) para hacer el corte por usted.
Entonces, como mínimo, debe hacer var / constante global con pov (10, n), lo que significa que decidió la precisión que necesita :-) Luego haga:
También podría seguir haciendo cálculos matemáticos y solo cortar al final, suponiendo que solo muestre y no haga if-s con resultados. Si puede hacer eso, entonces .toFixed (...) podría ser más eficiente.
Si está haciendo if-s / comparaciones y no quiere cortar, entonces también necesita una pequeña constante, generalmente llamada eps, que es un decimal más que el error máximo esperado. Digamos que su punto de corte son los dos últimos decimales; entonces su eps tiene 1 en el 3er lugar desde el último (3er menos significativo) y puede usarlo para comparar si el resultado está dentro del rango de eps esperado (0.02 -eps <0.1 * 0.2 <0.02 + eps).
fuente
Math.floor(-2.1)
es-3
. Así que quizás use, por ejemploMath[x<0?'ceil':'floor'](x*PREC_LIM)/PREC_LIM
floor
lugar deround
?Puede usar
parseFloat()
ytoFixed()
si desea evitar este problema para una operación pequeña:fuente
La función round () en phpjs.org funciona bien: http://phpjs.org/functions/round
fuente
0.6 * 3 ¡es increíble!)) Para mí esto funciona bien:
Muy muy simple))
fuente
8.22e-8 * 1.3
?Tenga en cuenta que para el uso general, es probable que este comportamiento sea aceptable.
El problema surge cuando se comparan esos valores de coma flotante para determinar una acción apropiada.
Con el advenimiento de ES6,
Number.EPSILON
se define una nueva constante para determinar el margen de error aceptable:Entonces, en lugar de realizar la comparación de esta manera
puede definir una función de comparación personalizada, como esta:
Fuente: http://2ality.com/2015/04/numbers-math-es6.html#numberepsilon
fuente
0.9 !== 0.8999999761581421
El resultado obtenido es correcto y bastante consistente en las implementaciones de coma flotante en diferentes lenguajes, procesadores y sistemas operativos: lo único que cambia es el nivel de inexactitud cuando el flotante es en realidad un doble (o superior).
0.1 en puntos flotantes binarios es como 1/3 en decimal (es decir, 0.3333333333333 ... para siempre), simplemente no hay una forma precisa de manejarlo.
Si se trata de flotadores, siempre espere pequeños errores de redondeo, por lo que también tendrá que redondear el resultado mostrado a algo sensato. A cambio, obtienes aritmética muy, muy rápida y poderosa porque todos los cálculos están en el binario nativo del procesador.
La mayoría de las veces la solución no es cambiar a aritmética de punto fijo, principalmente porque es mucho más lenta y el 99% del tiempo simplemente no necesita la precisión. Si se trata de cosas que necesitan ese nivel de precisión (por ejemplo, transacciones financieras), Javascript probablemente no sea la mejor herramienta para usar de todos modos (ya que desea aplicar los tipos de punto fijo, un lenguaje estático probablemente sea mejor) )
Estás buscando la solución elegante, entonces me temo que es esta: los flotadores son rápidos pero tienen pequeños errores de redondeo, siempre se redondea a algo sensato cuando se muestran sus resultados.
fuente
Para evitar esto, debe trabajar con valores enteros en lugar de puntos flotantes. Entonces, cuando desee tener una precisión de 2 posiciones, trabaje con los valores * 100, para 3 posiciones use 1000. Al mostrar, use un formateador para colocar en el separador.
Muchos sistemas omiten trabajar con decimales de esta manera. Esa es la razón por la cual muchos sistemas funcionan con centavos (como número entero) en lugar de dólares / euros (como punto flotante).
fuente
Problema
El punto flotante no puede almacenar todos los valores decimales exactamente. Por lo tanto, cuando se utilizan formatos de punto flotante, siempre habrá errores de redondeo en los valores de entrada. Los errores en las entradas, por supuesto, resultan en errores en la salida. En el caso de una función u operador discreto, puede haber grandes diferencias en la salida alrededor del punto donde la función u operador es discreto.
Entrada y salida para valores de coma flotante
Por lo tanto, cuando use variables de punto flotante, siempre debe ser consciente de esto. Y cualquier salida que desee de un cálculo con puntos flotantes siempre debe formatearse / condicionarse antes de mostrarse con esto en mente.
Cuando solo se utilizan operadores y funciones continuas, a menudo se redondea a la precisión deseada (no se trunca). Las características de formato estándar que se utilizan para convertir flotantes en cadenas generalmente lo harán por usted.
Debido a que el redondeo agrega un error que puede causar que el error total sea más de la mitad de la precisión deseada, la salida debe corregirse según la precisión esperada de las entradas y la precisión deseada de la salida. Debieras
Estas 2 cosas generalmente no se hacen y, en la mayoría de los casos, las diferencias causadas por no hacerlo son demasiado pequeñas para ser importantes para la mayoría de los usuarios, pero ya tenía un proyecto en el que los usuarios no aceptaban los resultados sin esas correcciones.
Funciones u operadores discretos (como módulo)
Cuando intervienen operadores o funciones discretas, es posible que se requieran correcciones adicionales para asegurarse de que el resultado sea el esperado. Redondear y agregar pequeñas correcciones antes de redondear no puede resolver el problema.
Es posible que se requiera una verificación / corrección especial en los resultados de cálculo intermedios, inmediatamente después de aplicar la función u operador discreto. Para un caso específico (operador de módulo), vea mi respuesta a la pregunta: ¿Por qué el operador de módulo devuelve un número fraccionario en javascript?
Mejor evitar tener el problema
A menudo es más eficiente evitar estos problemas mediante el uso de tipos de datos (formatos enteros o de punto fijo) para cálculos como este, que pueden almacenar la entrada esperada sin errores de redondeo. Un ejemplo de esto es que nunca debe usar valores de punto flotante para cálculos financieros.
fuente
Echa un vistazo a la aritmética de punto fijo . Probablemente resolverá su problema, si el rango de números en el que desea operar es pequeño (p. Ej., Moneda). Lo redondearía a unos pocos valores decimales, que es la solución más simple.
fuente
Prueba mi biblioteca de aritmética chilena, que puedes ver aquí . Si quieres una versión posterior, puedo conseguirte una.
fuente
No puede representar la mayoría de las fracciones decimales exactamente con los tipos de punto flotante binario (que es lo que ECMAScript usa para representar valores de punto flotante). Por lo tanto, no existe una solución elegante a menos que utilice tipos aritméticos de precisión arbitraria o un tipo de coma flotante basado en decimales. Por ejemplo, la aplicación Calculadora que se incluye con Windows ahora usa aritmética de precisión arbitraria para resolver este problema .
fuente
fuente
Tiene razón, la razón de esto es la precisión limitada de los números de coma flotante. Almacene sus números racionales como una división de dos números enteros y en la mayoría de las situaciones podrá almacenar números sin pérdida de precisión. Cuando se trata de imprimir, es posible que desee mostrar el resultado como fracción. Con la representación que propuse, se vuelve trivial.
Por supuesto, eso no ayudará mucho con los números irracionales. Pero es posible que desee optimizar sus cálculos de la forma en que causarán el menor problema (por ejemplo, detectar situaciones como
sqrt(3)^2)
.fuente
<pedant>
realidad, el OP lo</pedant>
Tuve un desagradable problema de error de redondeo con el mod 3. A veces, cuando debería obtener 0, obtenía .000 ... 01. Eso es bastante fácil de manejar, solo pruebe <= .01. Pero a veces obtenía 2.99999999999998. ¡AY!
BigNumbers resolvió el problema, pero introdujo otro problema, algo irónico. Al intentar cargar 8.5 en BigNumbers, me informaron que en realidad era 8.4999 ... y tenía más de 15 dígitos significativos. Esto significaba que BigNumbers no podía aceptarlo (creo que mencioné que este problema era algo irónico).
Solución simple al problema irónico:
fuente
Número de uso (1.234443) .toFixed (2); imprimirá 1.23
fuente
decimal.js , big.js o bignumber.js se pueden usar para evitar problemas de manipulación de punto flotante en Javascript:
enlace a comparaciones detalladas
fuente
Elegante, predecible y reutilizable
Abordemos el problema de una manera elegante y reutilizable. Las siguientes siete líneas le permitirán acceder a la precisión de coma flotante que desee en cualquier número simplemente agregando
.decimal
al final del número, fórmula oMath
función incorporada.¡Salud!
fuente
Utilizar
fuente
Math.round(x*Math.pow(10,4))/Math.pow(10,4);
. El redondeo siempre es una opción, pero yo sólo quería saber si hay alguna solución mejorno es elegante pero hace el trabajo (elimina los ceros finales)
fuente
Esto funciona para mi:
fuente
Salida utilizando la siguiente función:
Presta atención a la salida
toFixedCurrency(x)
.fuente