520 votos

¿Cómo tratar la precisión de los números en coma flotante en JavaScript?

Tengo la siguiente prueba ficticia script:

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

Esto imprimirá el resultado 0.020000000000000004 mientras que sólo debería imprimir 0.02 (si utiliza la calculadora). Por lo que he 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 existen funciones como toFixed o el redondeo sería otra posibilidad, pero me gustaría tener realmente el número entero impreso sin recortes ni redondeos. Sólo quería saber si alguno de vosotros tiene alguna solución bonita y elegante.

Por supuesto, si no, redondearé a unos 10 dígitos o así.

427voto

Michael Borgwardt Puntos 181658

Desde el Guía de coma flotante :

¿Qué puedo hacer para evitar este problema?

Depende del tipo de cálculos estés haciendo.

  • Si realmente necesita que sus resultados cuadren exactamente, especialmente cuando trabaja con dinero: utilice un decimal.
  • Si simplemente no quieres ver todos esos decimales extra: simplemente el resultado redondeado a un número número fijo de decimales mostrarlo.
  • Si no dispone de un tipo de datos decimal, una alternativa es trabajar con con números enteros, por ejemplo, calcular en céntimos. Pero esto requiere más trabajo y tiene algunas inconvenientes.

Tenga en cuenta que el primer punto sólo se aplica si realmente necesita un decimal comportamiento. La mayoría de la gente no necesita eso, sólo les irrita que sus programas no funcionen correctamente con números como 1/10 sin darse cuenta de que ni siquiera pestañearían ante el mismo error si se produjera con 1/3.

Si el primer punto se aplica realmente a usted, utilice BigDecimal para JavaScript o DecimalJS que realmente resuelve el problema en lugar de proporcionar una solución imperfecta.

111voto

linux_mike Puntos 449

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

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

A diferencia de la solución de Pedros, esta redondeará 0,999...repitiendo 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 utilizar toPrecision(7) y toPrecision(15) para obtener los mejores resultados. Véase esta pregunta para saber por qué.

64voto

Nirk Puntos 9999

Para los amantes de las matemáticas: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

Lo recomendable es utilizar factores de corrección (multiplicar por una potencia de 10 adecuada 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) sería algo así:

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 utilizar una biblioteca probada como SinfulJS

41voto

Nate Zaugg Puntos 1969

¿Sólo realizas multiplicaciones? Si es así, puedes utilizar a tu favor un secreto sobre la aritmética decimal. Se trata de que NumberOfDecimals(X) + NumberOfDecimals(Y) = ExpectedNumberOfDecimals . Es decir que si tenemos 0.123 * 0.12 entonces sabemos que habrá 5 decimales porque 0.123 tiene 3 decimales y 0.12 tiene dos. Así, si JavaScript nos diera un número como 0.014760000002 podemos redondear con seguridad al 5º decimal sin miedo a perder precisión.

24voto

Douglas Puntos 10417

Está buscando un sprintf para JavaScript, para que pueda escribir flotadores con pequeños errores (ya que se almacenan en formato binario) en un formato que usted espera.

Pruebe javascript-sprintf lo llamarías así:

var yourString = sprintf("%.2f", yourNumber);

para imprimir su número como un flotante con dos decimales.

También puede utilizar Número.toFixed() con fines de visualización, si prefiere no incluir más archivos sólo para el redondeo en coma flotante a una precisión determinada.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X