协慌网

登录 贡献 社区

如何处理 JavaScript 中的浮点数精度?

我有以下虚拟测试脚本:

function test() {
  var x = 0.1 * 0.2;
  document.write(x);
}
test();

这将打印结果0.020000000000000004而应该只打印0.02 (如果使用计算器)。据我了解,这是由于浮点乘法精度的错误。

有没有人有一个好的解决方案,这样在这种情况下我可以得到0.02的正确结果?我知道还有诸如toFixed或四舍五入之类的函数,但是我真的想在不进行任何舍入和四舍五入的情况下真正打印出完整的数字。只想知道你们中的一个人是否有一些不错的,优雅的解决方案。

当然,否则我将四舍五入到十位数左右。

答案

浮点指南

我应该怎么做才能避免这个问题?

这取决于您正在执行哪种计算。

  • 如果确实需要精确地将结果加起来,尤其是在使用金钱时,请使用特殊的十进制数据类型。
  • 如果您只是不想看到所有这些多余的小数位:只需在显示结果时将结果的格式四舍五入到固定的小数位数即可。
  • 如果没有可用的十进制数据类型,则替代方法是使用整数,例如,完全用美分进行货币计算。但这是更多的工作,并且有一些缺点。

请注意,只有在您确实需要特定的精确十进制行为时,第一点才适用。大多数人不需要它,只是因为他们的程序无法正确处理 1/10 之类的数字而感到恼火,却没有意识到如果发生 1/3,即使出现相同的错误也不会眨眼。

如果第一点确实适用于您,请对 JavaScript使用 BigDecimal,这一点都不优雅,但实际上可以解决问题,而不是提供不完善的解决方法。

我喜欢 Pedro Ladaria 的解决方案,并使用类似的方法。

function strip(number) {
    return (parseFloat(number).toPrecision(12));
}

与 Pedros 解决方案不同,此操作将舍入为 0.999 ... 重复,并且精确到最低有效数字的正负一一。

注意:处理 32 或 64 位浮点数时,应使用 toPrecision(7)和 toPrecision(15)以获得最佳结果。有关此问题的信息,请参见此问题。

对于数学倾向: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

推荐的方法是使用校正因子(乘以 10 的适当幂,以便在整数之间进行算术运算)。例如,在0.1 * 0.2的情况下,校正因子为10 ,您正在执行计算:

> var x = 0.1
> var y = 0.2
> var cf = 10
> x * y
0.020000000000000004
> (x * cf) * (y * cf) / (cf * cf)
0.02

一个(非常快的)解决方案看起来像:

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); };

在这种情况下:

> Math.m(0.1, 0.2)
0.02

我绝对建议使用经过测试的库,例如SinfulJS