首页
/ d3 全精度浮点求和:d3.Adder、d3.fsum 与 d3.fcumsum 实战指南

d3 全精度浮点求和:d3.Adder、d3.fsum 与 d3.fcumsum 实战指南

2026-09-05 12:28:32作者:裴锟轩Denise

本篇技术指南围绕 D3 中「全精度浮点数加法」这一专题展开,覆盖 d3.Adder 累加器类、d3.fsum 高精度求和与 d3.fcumsum 高精度累积求和三个 API 的完整用法、参数语义与典型示例。读完本文,你将理解 IEEE 754 双精度加法为什么会丢失精度、D3 提供了哪些手段来补偿这种误差,并能在数据汇总(如体重总和、金额累加)场景下正确选用 fsum/fcumsumsum/cumsum 两套 API。

为什么需要“全精度”加法

JavaScript 的 Number 采用 IEEE 754 双精度浮点表示。当一个大数与一个极小的数相加时,小数部分可能因位数有限而被直接舍掉,例如在普通加法下,1 + 1e-14 的结果仍然是 11e-14 这一项“消失”了。如果之后再做 1 - 1,最终结果会是 0,而不是数学上正确的 1e-14。同样地,把 0.1 连续累加 10 次,朴素加法会得到 0.9999999999999999,而不是数学上的 1

D3 针对这个问题提供了专门的 API。对应文档 docs/d3-array/add.md 开篇即点明其定位:Add floating point numbers with full precision(以全精度方式对浮点数求和)。

在 D3 主包中,这些 API 来自 d3-array 子模块:package.json 声明的依赖为 "d3-array": "^3.2.4",而 src/index.js 通过 export * from "d3-array" 将其完整透传。因此当你以常规方式引入主包时,d3.Adderd3.fsumd3.fcumsum 都可直接调用:

import * as d3 from "d3";
// d3.Adder、d3.fsum、d3.fcumsum 均由 d3-array 重新导出

d3.Adder:可复用的全精度累加器

Adder 是一个类形式的累加器,适合在“数据分批到达、需要边到边加”的场景中复用同一个累加状态。

new Adder()

const adder = new d3.Adder();

创建一个初始值为 0 的新累加器。

adder.add(number)

adder.add(42)

把指定的 number 加到累加器当前值上,并返回累加器自身(this),因此支持链式调用:

const total = new d3.Adder().add(0.1).add(0.1).add(0.1);
console.log(+total); // 0.3

adder.valueOf()

adder.valueOf() // 42

返回累加器当前值的 IEEE 754 双精度表示。文档特别指出,它的最佳用法是短写形式 +adder(一元加号触发类型强制转换),或者显式地 Number(adder)

const adder = new d3.Adder();
for (const x of measurements) adder.add(x);
const total = +adder;   // 等价于 adder.valueOf() 与 Number(adder)

d3.fsum:一次性高精度求和

fsum 是对任意 iterable 求全精度总和的函数,是朴素 d3.sum 的高精度替代品:

d3.fsum([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]) // 1

这正是朴素加法会输出 0.9999999999999999 的场景——fsum 通过补偿机制保留了每一步的舍入误差,最终还原出数学上精确的 1

d3.sum 相同,fsum 也支持可选的 accessor 函数,用于对对象数组求和。accessor 会被调用三次参数:元素 d、下标 i 和整个数组 data,其返回值参与累加:

d3.fsum(penguins, (d) => d.body_mass_g) // 1437000

文档给出的取舍建议是:Although slower, d3.fsum can replace d3.sum wherever greater precision is neededfsum 更慢,但在需要更高精度的任何地方都可以替换 d3.sum)。也就是说它不是免费的——精度换性能,二者按场景取舍。

作为对照,d3.sum 的文档明确说明其语义:返回给定数字可迭代对象的和,忽略 undefinednullNaN,若可迭代对象中不含任何数字则返回 0

d3.sum([1, 2, 2, 2, NaN, 3, null]) // 10
d3.sum(penguins, (d) => d.body_mass_g) // 1437000

需要提醒的是,fsum 文档本身未逐条罗列缺失值处理细节;在含缺失数据的真实数据上使用时,建议先结合 d3-array 的源码与测试 确认边界行为,或与 d3.sum 的结果交叉验证。

d3.fcumsum:高精度累积求和

fcumsum 返回一个 Float64Array,其中第 i 项是前 i + 1 个数的全精度前缀和:

d3.fcumsum([1, 1e-14, -1]) // [1, 1.00000000000001, 1e-14]

这个例子完整展示了补偿求和的价值:

  • 朴素加法:1 + 1e-141(小项被舍入吞掉),再 1 + (-1)0
  • fcumsum:中间结果保留了 1e-14 的误差项,最终第三项精确还原为 1e-14

同样支持 accessor 形式,返回的 Float64Array 长度与输入一致:

d3.fcumsum(penguins, (d) => d.body_mass_g) // [3750, 7550, 10800, 10800, 14250, …]

文档建议:Although slower, d3.fcumsum can replace d3.cumsum when greater precision is needed。对照 d3.cumsum 的文档,其语义为返回与输入等长的 Float64Array 前缀和,忽略 undefinedNaN(便于跳过缺失数据),无可加数字时返回全零:

d3.cumsum([1, 1, 2, 3, 5]) // [1, 2, 4, 7, 12]
d3.cumsum(penguins, (d) => d.body_mass_g) // [3750, 7550, 10800, 10800, …]

fcumsum 的典型应用是绘制带间隙的堆叠面积图或运行总量曲线:当序列中混有极大和极小的量级(如大额与小额订单交替)时,朴素 cumsum 的前缀和可能出现“平台被拉平、小增量丢失”的视觉误差,而 fcumsum 能保持单调性可信。

API 选型对照

维度 d3.sum d3.fsum d3.cumsum d3.fcumsum
精度 朴素 IEEE 754 累加 全精度(误差补偿) 朴素 IEEE 754 累加 全精度(误差补偿)
返回类型 number number Float64Array Float64Array
缺失值(undefined / null / NaN) 明确忽略 文档未逐条罗列,见上文说明 明确忽略 文档未逐条罗列
无数字时 返回 0 返回全零
性能 文档注明“slower” 文档注明“slower”
替代关系 需要更高精度时替代 d3.sum 需要更高精度时替代 d3.cumsum

经验法则:金额、质量、测量值等“绝对误差敏感”的汇总用 fsum/fcumsum;对精度不敏感的计数类统计用 sum/cumsum 以获得更好的性能。

仓库层面的佐证

  • 版本前提package.json 显示本仓库为 D3 7.9.0,依赖 d3-array ^3.2.4,且要求 Node >=12engines 字段);fsum 系列 API 在 D3 v7 的 ESM 结构下随 d3-array 一同导出,以上用法适用于该版本区间。
  • 历史沿革CHANGES.md 记录了 d3.fsumd3.Adder 自 D3 早期大版本起即作为 d3-array 的新增能力引入,说明二者属于 D3 数组工具箱中的“老牌”高精度成员,而非近期实验性 API。
  • 文档链接可验证性test/docs-test.js 会对 docs 目录下所有 Markdown 的相对链接与锚点做爬取校验(链接必须指向真实存在的 .md 文件及 {#anchor} 锚点)。本文引用的 docs/d3-array/summarize.md#sum#cumsum 等锚点正是该测试所保障的目标形式。
  • 实现归属fsum/fcumsum 的具体实现位于 d3-array 包(文档中标注的源文件为该包内的 src/fsum.jsAdderfsum 同源),本仓库通过 src/index.jsexport * from "d3-array" 透传,因此本仓库内不含该算法的本地副本;从文档描述与 D3 一贯的实现路线看,其原理属于补偿求和(compensated summation)一类算法——在每一步加法中额外追踪被舍入掉的误差并在后续步骤回填,这正是 fcumsum([1, 1e-14, -1]) 末项能精确得到 1e-14 的原因。

小结

D3 用一组小而完整的 API 解决了浮点累加的精度痛点:d3.Adder 提供可链式复用的累加状态,d3.fsumd3.fcumsum 分别对应 d3.sumd3.cumsum 的全精度版本,且保持相同的 accessor 调用约定(didata 三参数)。代价是更慢的速度,收益是在“大数 + 小数”混合序列下不失真。汇总数据前先问一句:这个总和里有没有量级相差超过 10 个数量级的项?如果有,切换到 f 前缀版本是零成本的正确性升级。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
590
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
904
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
889
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.52 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
983
503
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384