# 告别低精度累加：JavaScript Math.sumPrecise() 完全解析

> 深入解析 JavaScript Math.sumPrecise() 方法的工作原理、语法规则与浏览器兼容性，通过“大数吃小数”实例对比 reduce、for 循环和 Kahan 补偿求和的结果，说明它如何避免中间累加精度损失并正确舍入最终结果，同时介绍 Number 类型限制、空集合返回 -0、TypeError 异常及旧浏览器 Polyfill 等注意事项。

Author: DeathGhost
Published: 2026-07-28 20:38:21
Category: javascript
Canonical: https://www.deathghost.cn/article/javascript/128
Keywords: Math.sumPrecise, JavaScript, 浮点数精度, 累加误差, 精确求和, Kahan补偿求和, ECMAScript 2027,  TC39, 大数吃小数, reduce求和

如果你在 JavaScript 里做过求和运算，大概率遇到过这种情况：

0.1 + 0.2 === 0.3; // false，实际结果是 0.30000000000000004
这是 IEEE 754 双精度浮点数带来的经典精度问题。当你把一组数字放进循环或 reduce 里累加时，中间结果会不断舍入，最终误差可能逐渐累积。Math.sumPrecise() 正是为减少这类中间累加误差而设计的新方法。截至目前它已经进入 ECMAScript 2027 规范草案，新版本 Chrome、Firefox 和 Safari 等浏览器已开始原生支持，但旧版本浏览器仍可能无法使用。本文将带你弄清楚它解决了什么、怎么用，并给出可以直接运行的 Demo。

![Math.sumPrecise() - DeathGhost](/public/upload/article/2026/07/28/1785245276412885.jpg)

Math.sumPrecise()

## 为什么普通的累加会&ldquo;算不准&rdquo;？

先看一个经典的&ldquo;大数吃小数&rdquo;场景：

const numbers = [1e20, 1, -1e20];
const normalSum = numbers.reduce((acc, val) => acc + val, 0);
console.log(normalSum); // 0，数学上的结果应为 1
为什么会是 0？

1e20 加 1 时，由于双精度浮点数只能保持约 15～17 位有效数字，1 相对于 1e20 太小，在这一步被舍入丢失。因此，1e20 + 1 的浮点运算结果仍然是 1e20。然后再减去 1e20，最终得到 0。数学上的正确答案是 1，但累加过程中的中间部分和损失了精度。

Array.prototype.reduce 和普通 for 循环相加，每一步都会把中间结果保存为一个 IEEE 754 双精度浮点数，已经丢失的信息无法在后续步骤中恢复。这就是 Math.sumPrecise() 要解决的核心问题。

## Math.sumPrecise()：减少中间累加误差

Math.sumPrecise() 是一个静态方法，接收一个由 Number 值组成的可迭代对象（例如数组、TypedArray、Set 等），返回这些 Number 值的和。它返回的结果相当于先对输入的浮点数值进行精确数学求和，再将最终结果正确舍入为最接近的双精度浮点数。

### 基本语法

const sum = Math.sumPrecise(iterable);

- iterable：一个可迭代对象，其中每个元素都必须已经是 Number 类型。字符串、BigInt 等非 Number 值不会被自动转换，而是会导致 TypeError。

- 返回值：所有 Number 元素的和。如果可迭代对象为空，返回 -0，而不是普通的 +0。

Math.sumPrecise(["1", "2"]); // TypeError
Math.sumPrecise([1n, 2n]);   // TypeError

const emptySum = Math.sumPrecise([]);
console.log(Object.is(emptySum, -0)); // true
再来看刚才那个大数与小数混合的例子：

const numbers = [1e20, 1, -1e20];

const preciseSum = Math.sumPrecise(numbers);
console.log(preciseSum); // 1
ECMAScript 规范规定了 Math.sumPrecise() 必须产生的结果，但不会要求所有 JavaScript 引擎采用完全相同的内部算法。实现可以使用精确累加器、展开算法或其他能够满足规范正确舍入要求的技术。

## 传统求和方法 vs Math.sumPrecise()

下面给出一个可以直接在浏览器控制台或新建 HTML 文件中运行的示例。它会：

- 创建一组容易产生中间累加误差的数字。

- 对比普通 reduce、顺序 for 循环、Kahan 补偿求和以及 Math.sumPrecise() 的结果。

- 在浏览器不支持 Math.sumPrecise() 时给出提示。示例中的 Kahan 算法只是一种对照算法，并不是符合 Math.sumPrecise() 完整规范的 Polyfill。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>Math.sumPrecise Demo</title>
</head>
<body>
  <h2>浮点数累加对比</h2>
  <pre id="output"></pre>

  <script>
    // 构造测试数组：经典&ldquo;大数吃小数&rdquo; + 一组小数
    const numbers = [
      1e20, 1, -1e20,
      0.1, 0.2, 0.3, 0.4,
      0.5, 0.6, 0.7, 0.8, 0.9, 1.0
    ];

    // 方法1：Array.reduce 朴素求和
    const reduceSum = numbers.reduce((acc, val) => acc + val, 0);

    // 方法2：for 循环
    let loopSum = 0;
    for (let i = 0; i < numbers.length; i++) {
      loopSum += numbers[i];
    }

    // 方法3：Kahan 补偿求和
    // 注意：这只是对照算法，不是 Math.sumPrecise 的规范 Polyfill。
    function kahanSum(iterable) {
      let sum = 0;
      let compensation = 0;

      for (const num of iterable) {
        if (typeof num !== "number") {
          throw new TypeError("kahanSum only accepts Number values");
        }

        const y = num - compensation;
        const t = sum + y;
        compensation = (t - sum) - y;
        sum = t;
      }

      return sum;
    }

    const compensatedSum = kahanSum(numbers);

    // 方法4：原生 Math.sumPrecise（若可用）
    const nativeAvailable = typeof Math.sumPrecise === "function";
    const nativeSum = nativeAvailable
      ? Math.sumPrecise(numbers)
      : null;

    const output = document.getElementById("output");
    let result = `数组：[${numbers.join(", ")}]\n\n`;
    result += "输入 Number 值的精确数学和: 6.5\n\n";
    result += `reduce 求和             : ${reduceSum}\n`;
    result += `for 循环求和            : ${loopSum}\n`;
    result += `Kahan 补偿求和          : ${compensatedSum}\n`;

    if (nativeAvailable) {
      result += `Math.sumPrecise 原生    : ${nativeSum}\n\n`;
      result += "✅ 当前浏览器支持 Math.sumPrecise，本例返回 6.5。";
    } else {
      result += "Math.sumPrecise 原生    : 尚未支持\n\n";
      result +=
        `⚠️ 当前浏览器不支持 Math.sumPrecise。` +
        `本例中的 Kahan 结果为 ${compensatedSum}；` +
        "它可能改善部分累加误差，但不保证与 Math.sumPrecise 等价。";
    }

    output.textContent = result;
  </script>
</body>
</html>
在支持 Math.sumPrecise() 的浏览器中，你应该会看到类似下面的输出：

数组：[100000000000000000000, 1, -100000000000000000000, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]

输入 Number 值的精确数学和: 6.5

reduce 求和             : 5.5
for 循环求和            : 5.5
Kahan 补偿求和          : 5.5
Math.sumPrecise 原生    : 6.5
reduce 和 for 循环在累加时丢失了夹在两个大数之间的 1，所以最终得到 5.5。

普通的 Kahan 补偿求和可以改善很多常见的累加误差，但在这组特定顺序的数据中同样得到 5.5，没能保住夹在 1e20 和 -1e20 之间的 1。因此，不能将这个简单的 Kahan 实现当作 Math.sumPrecise() 的等价 Polyfill。

Math.sumPrecise() 按照规范对输入 Number 值进行精确累加并正确舍入，因此本例返回 6.5。

See the Pen [Math.sumPrecise Demo](https://codepen.io/editor/deathghost/pen/019fa8ea-e5fb-7fe9-b34d-76475a78612e) by 孙志锋 ([@deathghost](https://codepen.io/deathghost)) on [CodePen](https://codepen.io).

## 不只是数组求和

Math.sumPrecise() 可以接收不同类型的可迭代对象，例如 Set、Map 的 values() 和生成器；前提是迭代产生的每个元素都是 Number 值：

const set = new Set([0.1, 0.2, 0.3, 0.2]); // Set 自动去重
console.log(Math.sumPrecise(set));          // 0.6

function* values() {
  yield* [1e-10, 2e10, -2e10, 1e-10];
}
console.log(Math.sumPrecise(values()));     // 2e-10
它也可以直接处理 Float64Array 等类型化数组，不必手动编写求和循环。

## Math.sumPrecise() 不能解决所有小数精度问题

Math.sumPrecise() 避免的是求和过程中产生的中间舍入误差，但不会改变输入 Number 值本身的 IEEE 754 表示。例如：

console.log(Math.sumPrecise([0.1, 0.2]));
// 0.30000000000000004
这是因为 0.1 和 0.2 在进入方法之前就已经是最接近相应十进制数的二进制浮点近似值。Math.sumPrecise() 求出的是这些实际 Number 值的正确舍入和，而不是任意精度的十进制结果。因此，在货币等必须严格按照十进制规则计算的场景中，仍应使用整数最小单位、十进制运算库或专门的 Decimal 类型方案。

## 最后

- 为什么使用它？普通累加会在每一步舍入中间部分和，遇到&ldquo;大数吃小数&rdquo;或长数组时，结果可能明显偏离输入 Number 值的精确数学和。

- Math.sumPrecise() 避免中间精度损失，并把最终数学和正确舍入为最接近的双精度浮点数。

- 如何使用？调用 Math.sumPrecise(iterable)，并确保可迭代对象产生的每个元素都是 Number 值。

- 如何兼容？使用前可通过 typeof Math.sumPrecise === "function" 检测支持情况；生产项目如果需要兼容旧浏览器，应使用符合规范的 Polyfill，而不是把简单的 Kahan 求和当作等价实现。

在需要可靠浮点求和、且运行环境支持该方法时，Math.sumPrecise() 是比普通 reduce 或顺序循环更稳妥的选择。不过它解决的是累加过程中的中间舍入问题，并不会让 JavaScript 的 Number 变成任意精度十进制类型。你可以打开浏览器控制台运行上面的 Demo，看看当前浏览器是否已经支持这一特性。

## Related

- Previous: [JS原生的深拷贝API structuredClone()](https://www.deathghost.cn/article/javascript/120)
