2011-03-30 14 views
6

La implementación predeterminada de "Número.aFija" de javascript parece estar un poco rota.roto en Implementación ajustada

console.log((8.555).toFixed(2)); // returns 8.56 
console.log((8.565).toFixed(2)); // returns 8.57 
console.log((8.575).toFixed(2)); // returns 8.57 
console.log((8.585).toFixed(2)); // returns 8.59 

Necesito un método de redondeo que sea más consistente que eso.

En el rango entre 8.500 y 8.660, los siguientes números no se redondean correctamente.

8.575 
8.635 
8.645 
8.655 

He tratado de arreglar la implementación del prototipo de la siguiente manera, pero está solo a medio camino. ¿Alguien puede sugerir algún cambio que lo haga funcionar de manera más consistente?

Number.prototype.toFixed = function(decimalPlaces) { 
    var factor = Math.pow(10, decimalPlaces || 0); 
    var v = (Math.round(this * factor)/factor).toString(); 
    if (v.indexOf('.') >= 0) { 
     return v + factor.toString().substr(v.length - v.indexOf('.')); 
    } 
    return v + '.' + factor.toString().substr(1); 
}; 
+1

Me imagino que este es un error de precisión de punto flotante. –

+0

[http://stackoverflow.com/questions/566564/javascript-functions-math-round0-vs-tofixed0-and-browser-inconsistencies](http://stackoverflow.com/questions/566564/javascript-functions-math -round0-vs-tofixed0-and-browser-inconsistencies) similar? – Prescott

+0

Acabo de probar los ejemplos anteriores en Chromium V12.0 y recibí los siguientes resultados: 8.55 8.56 8.57 8.59. Por lo tanto, su millaje puede variar según su implementación de JavaScript. – HBP

Respuesta

5

Esto es debido a errores de punto flotante.

Compare (8.575).toFixed(20) con (8.575).toFixed(3) e imagine esta proposición: 8.575 < real("8.575"), donde real es una función imaginaria que crea un número real con una precisión infinita.

Es decir, el número original no es el esperado y la inexactitud ya se ha introducido.

Un rápido "workabout" que puedo pensar es: Multiplicar por 1000 (o según corresponda), obtener el toFixed(0) de eso (todavía tiene un límite, pero es absurdo), luego retroceder en el formato decimal.

Happy coding.

+0

naturalmente, no puedo usar "toFixed" en mi anulación de la misma función. Tuve que seguir usando "Math.round", pero tu respuesta me indicó la dirección correcta. Ty – Joshua

+0

@Joshua: Sí, puedes. Simplemente almacene una copia del archivo'Fixed' en una variable, y 'apply()' en el número. – Eric

7

Gracias por la respuesta pst. Mi implementación casi funcionó, pero no en algunos casos debido a errores de coma flotante.

esta línea en mi función es el culpable: Math.round (esto * Factor)

(está en el Number.prototype, por lo que "este" es el número); 8.575 * 100 sale a 857.4999999999999, que a su vez redondea hacia abajo. esto se corrige cambiando la línea que diga lo siguiente: Math.round (Math.round (esto * Factor * 100)/100)

toda Mi solución es ahora cambiado a:

Number.prototype.toFixed = function(decimalPlaces) { 
    var factor = Math.pow(10, decimalPlaces || 0); 
    var v = (Math.round(Math.round(this * factor * 100)/100)/factor).toString(); 
    if (v.indexOf('.') >= 0) { 
     return v + factor.toString().substr(v.length - v.indexOf('.')); 
    } 
    return v + '.' + factor.toString().substr(1); 
}; 
0

Check my answer

function toFixed(num, precision) { 
    return (+(Math.round(+(num + 'e' + precision)) + 'e' + -precision)).toFixed(precision); 
} 
0

Tal vez pueda ayudar a alguien, se trata de la función fija formatMoney populares(), pero con redondeos correctas.

Number.prototype.formatMoney = function() { 
    var n = this, 
    decPlaces = 2, 
    decSeparator = ",", 
    thouSeparator = " ", 
    sign = n < 0 ? "-" : "", 
    i = parseInt(n = Math.abs(+n || 0)) + "", 
    j = (j = i.length) > 3 ? j % 3 : 0, 
    decimals = Number(Math.round(n +'e'+ decPlaces) +'e-'+ decPlaces).toFixed(decPlaces), 
    result = sign + (j ? i.substr(0, j) + thouSeparator : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + thouSeparator) + (decPlaces ? decSeparator + Math.abs(decimals-i).toFixed(decPlaces).slice(2) : ""); 
    return result; 
}; 

(9.245).formatMoney(); // returns 9,25 
(7.5).formatMoney(); // returns 7,50 
(8.575).formatMoney(); // returns 8,58