The default implementation of javascript\'s \"Number.toFixed\" appears to be a bit broken.
console.log((8.555).toFixed(2)); // returns 8.56
console.log((
Thanks for the answer pst. My implementation almost worked, but didn't in some cases because of floating point errors.
this line in my function is the culprit: Math.round(this * factor)
(it's on the Number.prototype, so "this" is the number); 8.575 * 100 comes out to 857.4999999999999, which in turn rounds down. this is corrected by changing the line to read as follows: Math.round(Math.round(this * factor * 100) / 100)
My entire workaround is now changed to:
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);
};
Check my answer
function toFixed( num, precision ) {
return (+(Math.round(+(num + 'e' + precision)) + 'e' + -precision)).toFixed(precision);
}
A consistent solution would be to add a fixed tolerance (epsilon) to each number before rounding. It should be small, but not too small.
For example, with an eps = 1e-9
, this:
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
Becomes this:
console.log((8.555 + eps).toFixed(2)); // returns 8.56
console.log((8.565 + eps).toFixed(2)); // returns 8.57
console.log((8.575 + eps).toFixed(2)); // returns 8.58
console.log((8.585 + eps).toFixed(2)); // returns 8.59
This is because of floating-point errors.
Compare (8.575).toFixed(20)
with (8.575).toFixed(3)
and imagine this proposition: 8.575 < real("8.575")
, where real is an imaginary function that creates a real number with infinite precision.
That is, the original number is not as expected and the inaccuracy has already been introduced.
One quick "workabout" I can think of is: Multiply by 1000 (or as appropriate), get the toFixed(0)
of that (still has a limit, but it's absurd), then shove back in the decimal form.
Happy coding.
Maybe it will help someone, this is fixed popular formatMoney() function, but with correct roundings.
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
It's probably related to floating point problems, see How to deal with floating point number precision in JavaScript?