¿Cómo puedo lidiar con la precisión de los números de punto flotante en JavaScript? [duplicar]
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
cuando 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 toFixed
o 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.
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.
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é.
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.
¿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.12
entonces sabemos que habrá 5 decimales porque 0.123
tiene 3 decimales y 0.12
tiene dos. Por lo tanto, si JavaScript nos dio un número como este, 0.014760000002
podemos redondear con seguridad al quinto decimal sin temor a perder precisión.