打破到固定的实现

IT技术 javascript
2021-03-08 02:35:07

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);
};
5个回答

这是因为浮点错误。

比较(8.575).toFixed(20)(8.575).toFixed(3)想象这个命题:8.575 < real("8.575"),其中真正是创建具有无限精度实数的虚函数。

也就是说,原来的数字不符合预期,已经引入了不准确。

我能想到的一个快速“解决方法”是:乘以 1000(或酌情),得到toFixed(0)那个(仍然有限制,但很荒谬),然后以十进制形式推回。

快乐编码。

@约书亚:是的,你可以。只需将 toFixed` 的副本存储在变量中,并将apply()其存储到数字中。
2021-05-05 02:35:07
自然,我不能在覆盖相同功能时使用“toFixed”。我不得不坚持使用“Math.round”,但您的回答为我指明了正确的方向。
2021-05-08 02:35:07

感谢您的回答。我的实现几乎有效,但在某些情况下由于浮点错误而无效。

我函数中的这一行是罪魁祸首:Math.round(this * factor)

(它在 Number.prototype 上,所以“这个”就是数字);8.575 * 100 得出 857.4999999999999,依次向下取整。这是通过将行更改为如下更正的: Math.round(Math.round(this * factor * 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);
};

一致的解决方案是在四舍五入之前为每个数字添加一个固定的容差 (epsilon)。它应该很小,但不能太小。

例如,对于eps = 1e-9,这个:

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

变成这样:

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

也许它会帮助某人,这是固定的流行 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

检查我的答案

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