news 2026/4/2 16:18:29

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/3 3:48:43

ArcGIS大师之路500技---024照片展点

文章目录前言一、 数据准备二、 照片展点前言 你是否经历过这样的场景&#xff1f;外业调查拍摄了海量的现场照片&#xff0c;回到办公室后&#xff0c;却要对着GPS轨迹和一堆照片文件&#xff0c;绞尽脑汁地回忆“这张照片到底是在哪拍的&#xff1f;”。 繁琐的手动比对、容…

作者头像 李华
网站建设 2026/4/1 14:16:12

AI安全风险检测工具在医疗领域的关键应用与实践

AI安全风险检测工具在医疗领域的关键应用与实践 【免费下载链接】PyRIT 针对生成式人工智能系统的Python风险识别工具(PyRIT)是一款开源的自动化解决方案&#xff0c;它致力于赋能安全专家与机器学习开发工程师&#xff0c;使其能够主动检测并发现其构建的生成式AI系统中存在的…

作者头像 李华
网站建设 2026/4/1 19:19:41

基于深度学习的文物图像修复系统

基于深度学习的文物图像修复系统 1. 项目背景与意义 文物是人类文明的瑰宝&#xff0c;但随着时间的推移&#xff0c;许多文物&#xff08;如壁画、古画、照片等&#xff09;不可避免地遭受了自然风化、虫蛀、霉变或人为损坏&#xff0c;导致出现了裂缝、污渍、缺失等问题。传统…

作者头像 李华
网站建设 2026/4/1 13:33:55

Overleaf插件定制实战指南:3分钟搞定编辑器功能优化

Overleaf插件定制实战指南&#xff1a;3分钟搞定编辑器功能优化 【免费下载链接】overleaf A web-based collaborative LaTeX editor 项目地址: https://gitcode.com/GitHub_Trending/ov/overleaf 你是不是也遇到过这样的困扰&#xff1a;用Overleaf写论文时&#xff0c…

作者头像 李华
网站建设 2026/4/2 12:50:02

【OTA】自动化测试方案

目录 基于 Python + PyQt5 的 OTA 自动化测试工具方案 1. 背景与问题定义 2. 工具整体架构设计 2.1 架构分层 2.2 核心设计思想 3. OTA 自动化流程拆解(状态机) 4. PyQt5 UI 设计(任务控制台) 4.1 UI 功能 4.2 主窗口代码示例(PyQt5) 5. OTA 状态机与调度实现 5.1 Worker…

作者头像 李华