¿Cómo puedo lidiar con la precisión de los números de punto flotante en JavaScript? [duplicar]

Resuelto Juri asked hace 14 años • 46 respuestas

Tengo el siguiente script de prueba ficticio:

function test() {
  var x = 0.1 * 0.2;
  document.write(x);
}
test();
Expandir fragmento

Esto imprimirá el resultado 0.020000000000000004cuando debería simplemente imprimirse 0.02(si usa su calculadora). Según tengo entendido, esto se debe a errores en la precisión de la multiplicación en coma flotante.

¿Alguien tiene una buena solución para que en tal caso obtenga el resultado correcto 0.02? Sé que hay funciones como toFixedo redondear sería otra posibilidad, pero realmente me gustaría imprimir el número completo sin ningún corte ni redondeo. Sólo quería saber si alguno de ustedes tiene alguna solución agradable y elegante.

Por supuesto, de lo contrario redondearé a unos 10 dígitos aproximadamente.

Juri avatar Sep 22 '09 14:09 Juri
Aceptado

De la Guía de punto flotante :

¿Qué puedo hacer para evitar este problema?

Eso depende del tipo de cálculos que estés haciendo.

  • Si realmente necesita que sus resultados sumen exactamente, especialmente cuando trabaja con dinero: use un tipo de datos decimal especial.
  • Si simplemente no desea ver todos esos decimales adicionales: simplemente formatee su resultado redondeado a un número fijo de decimales cuando lo muestre.
  • Si no tiene ningún tipo de datos decimal disponible, una alternativa es trabajar con números enteros, por ejemplo, hacer cálculos monetarios enteramente en centavos. Pero esto requiere más trabajo y tiene algunos inconvenientes.

Tenga en cuenta que el primer punto sólo se aplica si realmente necesita un comportamiento decimal preciso y específico . La mayoría de las personas no necesitan eso, simplemente les irrita que sus programas no funcionen 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, utilice BigDecimal para JavaScript o DecimalJS , que realmente resuelve el problema en lugar de proporcionar una solución imperfecta.

Michael Borgwardt avatar Aug 09 '2010 12:08 Michael Borgwardt

Me gusta la solución de Pedro Ladaria y uso algo similar.

function strip(number) {
    return (parseFloat(number).toPrecision(12));
}

A diferencia de la solución de Pedro, esto redondeará 0,999... repitiéndose y tiene una precisión de más/menos uno en el dígito menos significativo.

Nota: Cuando trabaje con flotantes de 32 o 64 bits, debe usar toPrecision(7) y toPrecision(15) para obtener mejores resultados. Consulte esta pregunta para obtener información sobre por qué.

linux_mike avatar Sep 04 '2010 23:09 linux_mike

Para aquellos con inclinaciones matemáticas: 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 se realice entre números enteros). Por ejemplo, en el caso de 0.1 * 0.2, el factor de corrección es 10, y estás realizando el cálculo:

> var x = 0.1
> var y = 0.2
> var cf = 10
> x * y
0.020000000000000004
> (x * cf) * (y * cf) / (cf * cf)
0.02

Una solución (muy rápida) se parece a:

var _cf = (function() {
  function _shift(x) {
    var parts = x.toString().split('.');
    return (parts.length < 2) ? 1 : Math.pow(10, parts[1].length);
  }
  return function() { 
    return Array.prototype.reduce.call(arguments, function (prev, next) { return prev === undefined || next === undefined ? undefined : Math.max(prev, _shift (next)); }, -Infinity);
  };
})();

Math.a = function () {
  var f = _cf.apply(null, arguments); if(f === undefined) return undefined;
  function cb(x, y, i, o) { return x + f * y; }
  return Array.prototype.reduce.call(arguments, cb, 0) / f;
};

Math.s = function (l,r) { var f = _cf(l,r); return (l * f - r * f) / f; };

Math.m = function () {
  var f = _cf.apply(null, arguments);
  function cb(x, y, i, o) { return (x*f) * (y*f) / (f * f); }
  return Array.prototype.reduce.call(arguments, cb, 1);
};

Math.d = function (l,r) { var f = _cf(l,r); return (l * f) / (r * f); };

En este caso:

> Math.m(0.1, 0.2)
0.02

Definitivamente recomiendo usar una biblioteca probada como SinfulJS.

SheetJS avatar Sep 20 '2013 02:09 SheetJS

¿Solo estás realizando multiplicaciones? Si es así, puedes utilizar a tu favor un claro secreto sobre la aritmética decimal. Eso es eso NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals. Es decir que si tenemos 0.123 * 0.12entonces sabemos que habrá 5 decimales porque 0.123tiene 3 decimales y 0.12tiene dos. Por lo tanto, si JavaScript nos dio un número como este, 0.014760000002podemos redondear con seguridad al quinto decimal sin temor a perder precisión.

Nate Zaugg avatar Aug 11 '2010 14:08 Nate Zaugg