还在reduce求和吗?该使用Math.sumPrecise()方法啦

这篇文章发布于 2026年08月14日,星期五,19:20,归类于 CSS相关。 阅读 43 次, 今日 42 次 没有评论

 

一、数组求和新方法

之前大家数组求和,肯定多半都是使用的数组的reduce方法.

例如:

const sum = [1, 2, 3, 4, 5, 6].reduce((a, b) => a + b, 0);
// 结果 sum: 21

如下截图所示:

reduce求和示意

在大多数时候,上面的求和计算是没问题的。

但是,如果计算的数值超大,超过了JS的整数范围;或者是小数,就可能出问题。

比方说:

const arr = [0.1, 0.2, 0.3];
// reduce求和
const sum = arr.reduce((prev, curr) => prev + curr, 0);

console.log(sum);   
// 预期:0.6
// 实际输出:0.6000000000000001
const values = [1e20, 0.1, -1e20];

const sum = values.reduce((a, b) => a + b, 0);
console.log("sum", sum);
// 预期:sum 0.1
// 实际:sum 0

这怎么弄?

在过去,开发人员会把数组每一项都乘以100,1000这种,然后再计算,例如:

const sum = [0.1, 0.2, 0.3].reduce((prev, curr) => prev + curr * 100, 0) / 100;
console.log(sum);   
// 预期:0.6
// 实际输出:0.6

或者使用Number.toPrecision()方法对输出值进行精度格式化处理。

例如:

const moneyList = [0.1, 0.2, 0.7, 0.3];
const total = moneyList.reduce((s, v) => s + v, 0);

console.log(total); 
// 预期:1.3
// 实际:1.2999999999999998

console.log(Number(total.toPrecision(16))); 
// 预期:1.3
// 实际:1.3

然而,上面这些方法都不是完全之策,例如 toPrecision() 方法在下面这个案例中就是无效的:

[1e20, 0.1, -1e20].reduce((s, v) => s + v, 0).toPrecision(16)
// 结果还是0.000000000000000 ❌

那怎么办呢?

有没有什么一了百了的方法呢?

有!

那就是Math对象新增的sumPrecise()方法。

一键求和,同时保证精度。

二、数组求和利器Math.sumPrecise()

从今往后,数组求和,再也无需使用reduce()方法了,直接Math.sumPrecise()方法一把梭。

使用非常简单,例如:

const sum = Math.sumPrecise([1, 2, 3, 4, 5, 6]);
// 结果 sum: 21

同时,再也没有小数计算精度不准的问题了。

const sum = Math.sumPrecise([0.1, 0.2, 0.3]);
// 结果 sum: 0.3

超大的整数计算也不会有问题了!

const values = [1e20, 0.1, -1e20];
const sum = Math.sumPrecise(values);
console.log("sum", sum);
// sumTwo 0.1

太棒啦,表情包

三、兼容性、Polyfill与结语

各大浏览器对Math.sumPrecise()方法的支持非常积极,目前领先的几个版本都已经支持。

Math sumPrecise兼容性

对于不兼容的浏览器,可以引入Polyfill代码,参考示意:

if (typeof Math.sumPrecise !== 'function') {
  Object.defineProperty(Math, 'sumPrecise', {
    value: function sumPrecise(items) {
      // 参数校验
      if (items == null) {
        throw new TypeError('Math.sumPrecise requires an iterable argument.');
      }
      if (typeof items[Symbol.iterator] !== 'function') {
        throw new TypeError('Math.sumPrecise: argument is not iterable.');
      }

      var sum = 0;
      var compensation = 0; // Neumaier 补偿值
      var count = 0;
      var hasNegZero = false;
      var hasNonZero = false;

      for (var value of items) {
        var num = Number(value);

        // 任意元素为 NaN,直接返回 NaN
        if (Number.isNaN(num)) {
          return NaN;
        }

        // 任意元素为 ±Infinity,处理无穷大相消问题
        if (!isFinite(num)) {
          if (sum === Infinity && num === -Infinity) return NaN;
          if (sum === -Infinity && num === Infinity) return NaN;
          sum = num;
          hasNonZero = true;
          count++;
          continue;
        }

        // 记录 -0
        if (num === 0) {
          if (!isFinite(sum) || hasNonZero) {
            // 有实际数值时 -0 不影响结果
          } else if (Object.is(num, -0)) {
            hasNegZero = true;
          }
          count++;
          continue;
        }

        hasNonZero = true;

        // Neumaier 补偿求和核心
        var t = sum + num;
        if (Math.abs(sum) >= Math.abs(num)) {
          compensation += (sum - t) + num;
        } else {
          compensation += (num - t) + sum;
        }
        sum = t;

        count++;
        if (count > 2 ** 53) {
          throw new RangeError('Math.sumPrecise: iterable is too large.');
        }
      }

      // 全是零(含 -0)的情况
      if (!hasNonZero) {
        return hasNegZero ? -0 : 0;
      }

      // 有无穷大时,补偿值无意义,直接返回
      if (!isFinite(sum)) {
        return sum;
      }

      return sum + compensation;
    },
    writable: true,
    configurable: true,
  });
}

边界测试用例:

// 精度问题
Math.sumPrecise([0.1, 0.2, 0.3])          // 0.6
Math.sumPrecise([0.1, 0.1, 0.1, 0.1,
                 0.1, 0.1, 0.1, 0.1,
                 0.1, 0.1])                // 1

// 特殊值
Math.sumPrecise([])                        // 0
Math.sumPrecise([-0])                      // -0
Math.sumPrecise([-0, -0])                  // -0
Math.sumPrecise([-0, 0])                   // 0(正零优先)
Math.sumPrecise([Infinity, -Infinity])     // NaN
Math.sumPrecise([NaN, 1])                  // NaN
Math.sumPrecise([Infinity, 1, 2])          // Infinity

// 非数组可迭代
Math.sumPrecise(new Set([1, 2, 3]))        // 6
Math.sumPrecise((function*() {
  yield 0.1; yield 0.2; yield 0.3;
})())                                      // 0.6

看起来还挺像那么回事。

好了,就说这么多吧。

大家周末愉快。

周末愉快

(本篇完)

分享到:


发表评论(目前没有评论)