news 2026/4/24 20:41:07

JavaScript 的数值计算精度:Kahan 求和算法在处理大量浮点数累加时的应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaScript 的数值计算精度:Kahan 求和算法在处理大量浮点数累加时的应用

各位同学,各位同仁,大家好!

今天,我们将深入探讨一个在日常编程中常常被忽视,但在处理大量数值数据时又至关重要的话题:JavaScript 中的浮点数计算精度。特别是,我们将聚焦于一个巧妙的算法——Kahan 求和算法,来解决在累加大量浮点数时可能出现的精度损失问题。

浮点数:数字世界的“近似”与挑战

在JavaScript(以及大多数现代编程语言)中,数字的表示遵循 IEEE 754 双精度浮点数标准。这意味着每个数字都由64位二进制数来存储,其中包括一个符号位、一个指数位和一个尾数(或称有效数字)位。这种表示方法在很大程度上能够高效地表示非常大或非常小的数字,但它并非没有代价。

问题根源:二进制无法精确表示所有十进制小数

浮点数的本质是使用二进制分数来近似表示实数。就像十进制分数1/3无法在有限位数的十进制中精确表示为0.333...一样,许多简单的十进制小数,如0.1,也无法在二进制中被精确表示。

例如,十进制的0.1在二进制中是一个无限循环小数:
0.00011001100110011...

由于计算机的存储空间有限,它必须在某个点截断这个无限序列,这就引入了微小的舍入误差。单个的舍入误差通常非常小,对大多数计算来说可以忽略不计。但当这些微小的误差在一系列计算中累积起来时,就可能导致最终结果与预期值产生显著偏差。

让我们看一个经典的JavaScript浮点数问题:

console.log(0.1 + 0.2); // 预期结果:0.3 // 实际输出:0.30000000000000004

这个结果令人惊讶,但它直接揭示了浮点数计算的内在特性。0.10.2都不能被精确表示,它们各自都有一个微小的舍入误差。当它们相加时,这些误差被合并,并且在结果中以一个微小的偏差显现出来。

IEEE 754 双精度浮点数概述

为了更好地理解精度问题,我们有必要简要回顾一下 IEEE 754 双精度浮点数的结构。一个64位的双精度浮点数被分解为三个部分:

  1. 符号位 (Sign Bit): 1位,表示数字的正负(0为正,1为负)。
  2. 指数位 (Exponent): 11位,用于表示小数点的位置,决定了数字的量级。通过一个偏移量(bias)来处理正负指数。
  3. 尾数/有效数字位 (Mantissa/Significand): 52位,用于表示数字的精确值。由于浮点数通常以规范化形式存储(即假定小数点前有一位非零数字,通常是1),所以这52位实际上提供了53位的精度(隐含的最高位1)。

这种结构允许表示的数字范围非常广,从大约5e-3241.8e+308。然而,它也意味着在任意两个可表示的浮点数之间存在着“间隙”。这些间隙的大小随着数字的绝对值增大而增大。

关键概念:机器精度 (Machine Epsilon)

Number.EPSILON是 JavaScript 提供的一个常量,它代表了1与大于1的最小浮点数之间的差值。在双精度浮点数中,它大约是2.220446049250313e-16。它衡量了浮点数计算的相对精度。当两个数字的差值小于或等于EPSILON乘以其中较大数字的绝对值时,它们可能被认为是相等的,或者说,它们的差异已经超出了浮点数可以区分的最小单位。

部分位数描述
符号位10表示正数,1表示负数
指数位11决定数字的量级,范围约从 -1022 到 1023
尾数位52决定数字的精确值,提供约15-17位十进制精度

正是由于这种有限的精度和间隙的存在,当我们在进行大量浮点数累加时,问题就会变得尤为突出。

朴素求和的陷阱:误差的累积与吞噬

最直观的求和方法就是简单地将所有数字逐个相加:

function naiveSum(numbers) { let sum = 0; for (let i = 0; i < numbers.length; i++) { sum += numbers[i]; } return sum; }

这种方法在大多数情况下都表现良好,但当满足以下两个条件时,它会暴露出严重的精度问题:

  1. 累加的数字数量非常大。
  2. 要累加的数字大小差异悬殊。例如,将大量非常小的数字累加到一个非常大的数字上。

误差吞噬 (Loss of Significance)

当一个非常小的浮点数被添加到一个非常大的浮点数时,小数字的有效位可能会完全丢失。这是因为在执行加法操作之前,计算机需要调整这两个数字的指数,使它们的有效数字对齐。如果两个数字的量级相差太大,小数字的有效位可能会被“右移”到超出尾数所能表示的范围,从而被舍弃。

举一个例子:
假设我们有一个非常大的数A = 1.0000000000000000e+15和一个非常小的数B = 1.0e-1
当我们计算A + B时:
1.0000000000000000e+15
+ 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
The difference betweenAandA + Bis so small that it falls within the rounding precision ofA. The effectivelyA + B = A.

实际案例:
假设你正在模拟一个粒子系统,需要对数百万个粒子在每个时间步长的能量进行累加。如果每个粒子的能量都是一个微小数,而总能量可能非常大,那么朴素求和将迅速积累误差。

// 模拟一个场景:将大量小值加到一个大值上 const NUM_ITERATIONS = 1000000; const largeValue = 1000000000000000; // 一个非常大的数 const smallValue = 0.0000000000000001; // 一个非常小的数 let numbersToSum = [largeValue]; for (let i = 0; i < NUM_ITERATIONS; i++) { numbersToSum.push(smallValue); } const naiveResult = naiveSum(numbersToSum); const expectedApproximateResult = largeValue + NUM_ITERATIONS * smallValue; // 理论上的精确和 console.log("--- 朴素求和示例 ---"); console.log("大值:", largeValue); console.log("小值:", smallValue); console.log("累加次数:", NUM_ITERATIONS); console.log("理论近似和:", expectedApproximateResult); console.log("朴素求和结果:", naiveResult); console.log("朴素求和误差:", Math.abs(naiveResult - expectedApproximateResult)); // 实际运行,你会发现 naiveResult 会比 expectedApproximateResult 小很多, // 甚至可能就是 largeValue 本身,因为所有 smallValue 都被“吞噬”了。

smallValue足够小,而largeValue足够大时,naiveResult的输出可能就是largeValue,这意味着所有NUM_ITERATIONSsmallValue的贡献都被完全抹杀了,这是一个灾难性的精度损失。

Kahan 求和算法:补偿式求和

为了应对这种累积的舍入误差,W. Kahan 在1960年代提出了一种精巧的解决方案,被称为Kahan 求和算法(Kahan summation algorithm),也称作补偿式求和(compensated summation)。

Kahan 算法的核心思想是:在每一次加法操作中,不仅仅是更新总和,还要记录因为舍入而“丢失”的那一部分值,并在下一次迭代中将其补偿回来。通过这种方式,即使每次丢失的量很小,它们也会被追踪并最终被加回到总和中,从而显著提高累加的精度。

算法变量:

  • sum: 当前的累加总和。
  • input: 当前要加入总和的数字。
  • y: 经过补偿后的input
  • t: 临时总和,用于计算新的sum
  • c: 补偿值(compensation),记录了上次加法中丢失的低位信息。

Kahan 算法的步骤:

  1. 初始化sum = 0.0c = 0.0
  2. 对于序列中的每个数字input
    a. 计算y = input - cy是当前数字减去上一次加法中丢失的补偿值。这样
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 12:32:43

Blade构建系统终极指南:新手快速上手指南

Blade构建系统终极指南&#xff1a;新手快速上手指南 【免费下载链接】blade-build Blade is a powerful build system from Tencent, supports many mainstream programming languages, such as C/C, java, scala, python, protobuf... 项目地址: https://gitcode.com/gh_mi…

作者头像 李华
网站建设 2026/4/19 22:38:16

16、探索 Linux:网络应用与文件管理指南

探索 Linux:网络应用与文件管理指南 在当今数字化时代,Linux 系统凭借其强大的功能和高度的可定制性,受到了越来越多用户的青睐。本文将深入介绍 Linux 系统中的网络应用和文件管理操作,帮助你更好地利用 Linux 系统的优势,提升工作和学习效率。 网络应用:即时通讯、文…

作者头像 李华
网站建设 2026/4/23 9:35:12

18、深入了解 Linux 文件系统:导航与分区指南

深入了解 Linux 文件系统:导航与分区指南 1. Linux 常见子目录及其内容 在 Linux 系统中,有许多重要的子目录,每个子目录都有其特定的用途。以下是一些常见的子目录及其内容: | 子目录 | 内容描述 | | — | — | | /usr/games | 系统上安装的游戏,除了那些可选择放置…

作者头像 李华
网站建设 2026/4/22 19:09:32

19、Linux系统使用指南:文件系统、磁盘管理与软件操作

Linux系统使用指南:文件系统、磁盘管理与软件操作 1. Linux文件系统与分区 在Linux中,文件系统的分区管理有着独特的方式。紧跟三位字母标识后的数字代表你所指的分区。例如,用户为Linux创建了三个分区,第一个IDE驱动器是一个单独的分区,分配给根分区;第二个IDE驱动器被…

作者头像 李华
网站建设 2026/4/18 15:23:14

24、Linux系统桌面与文本操作全攻略

Linux系统桌面与文本操作全攻略 1. 桌面快捷方式与主题设置 1.1 创建桌面快捷方式 在Linux系统中,创建桌面快捷方式可以方便我们快速启动程序。以下是具体步骤: 1. 选择要创建快捷方式的程序,但不要打开它。例如,若要为The GIMP创建快捷方式,将鼠标移至该程序上,然后…

作者头像 李华
网站建设 2026/4/23 18:54:10

31、Linux DVD-ROM 使用指南与相关知识详解

Linux DVD-ROM 使用指南与相关知识详解 1. DVD-ROM 内容概述 DVD-ROM 包含了安装和运行 Fedora Core 5 所需的一切,这相当于从 Fedora 项目网站下载的四张光盘内容,其中包括: - Fedora Core 5 :红帽赞助的免费版 Linux 最新且最棒版本的完整软件副本。若对源代码感兴趣…

作者头像 李华