Прерванная реализация toFixed
По умолчанию реализация javascript "Number.toFixed" выглядит немного сломанной.
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
Мне нужен метод округления, который более последователен.
В диапазоне от 8.500 до 8.660 следующие номера не округляются правильно.
8.575
8.635
8.645
8.655
Я попытался исправить реализацию прототипа следующим образом, но это только на полпути. Может ли кто-нибудь предложить какие-либо изменения, которые заставили бы его работать более последовательно?
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
Это связано с ошибками с плавающей запятой.
Сравните (8.575).toFixed(20)
с (8.575).toFixed(3)
и представьте это предложение: 8.575 < real("8.575")
, где real - это мнимая функция, которая создает вещественное число с бесконечной точностью.
То есть исходное число не соответствует ожидаемому, и неточность уже введена.
Одна быстрая "рабочая нагрузка", о которой я могу думать, заключается в следующем: умножить на 1000 (или, если необходимо), получить toFixed(0)
от этого (все еще есть предел, но это абсурдно), а затем вернуться в десятичной форме.
Счастливое кодирование.
Ответ 2
Спасибо за ответ pst. Моя реализация почти сработала, но не в некоторых случаях из-за ошибок с плавающей запятой.
эта строка в моей функции является виновником:
Math.round(этот * коэффициент)
(это на Number.prototype, поэтому "this" - это число);
8.575 * 100 выходит на 857.4999999999999, что в свою очередь округляет.
это исправляется путем изменения строки следующим образом:
Math.round(Math.round(этот * коэффициент * 100)/100)
Теперь все мое обходное решение изменено на:
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);
};
Ответ 3
Вероятно, это связано с проблемами с плавающей запятой, см. Как справиться с точностью чисел с плавающей запятой в JavaScript?
Ответ 4
Проверьте мой ответ
function toFixed( num, precision ) {
return (+(Math.round(+(num + 'e' + precision)) + 'e' + -precision)).toFixed(precision);
}
Ответ 5
Возможно, это поможет кому-то, это фиксированная популярная функция formatMoney(), но с правильными округлениями.
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