ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Tov的decimal为何比big.js快几十倍?128位精确十进制数实现原理拆解

Tov的decimal为何比big.js快几十倍?128位精确十进制数实现原理拆解

【免费下载链接】Tov

A TypeScript-shaped language built for coding agents. It compiles to small, fast native binaries.

项目地址:https://gitcode.com/gh_mirrors/to/Tov
点击查看免费下载

Tov 是一个 TypeScript 风格、可编译为原生二进制的高性能语言,它内置的精确十进制数类型decimal是本次拆解的主角。在官方基准中,处理 200 万笔账目的 decimal_ledger,Tov 仅需 109 ms,而 Node 上最常用的 big.js 要 1984 ms;20 万张发票的 decimal_invoice,Tov 用 52 ms 跑完,Node + big.js 则需要近 9 秒。同样的结果精确到分,速度差距却高达几十到上百倍。这背后的秘密只有一个:一个 128 位的内存布局 + 几乎覆盖全部工作负载的快路径。

先看差距:两个基准的成绩单

官方在 bench/README.md 中公布了 macOS(Apple M4 Max)上 5 次运行的中位数,所有实现打印的金额逐分一致(原始数据见 bench/results/20261014-002611.json):

基准Rust (rust_decimal)Node (big.js)Bun (big.js)Tov (decimal)
decimal_ledger(200 万笔)143 ms · 1.9 MB1984 ms · 182 MB1866 ms · 67 MB109 ms · 1.9 MB
decimal_invoice(20 万张)132 ms · 1.9 MB8973 ms · 159 MB6550 ms · 77 MB52 ms · 1.9 MB

注意两件事:

  • 时间:发票基准里 Tov 比 Node + big.js 快约 170 倍,比 Bun 快约 126 倍;
  • 内存:big.js 版本峰值占用 67~182 MB,而 Tov 只有 1.9 MB——和 C 程序同一量级。

这两个基准非常"真实":账目从文本"1234.56"解析金额、收 2.9% + 0.30 手续费、按 0.9234 汇率折算欧元;发票则含逐行的按比例分摊折扣(每行一次除法)和 8.25% 的税。源码分别在 bench/micro/decimal_ledger/main.tov 和 bench/micro/decimal_invoice/main.tov。

核心设计:整个 decimal 只占 128 位

Tov 的 decimal 实现全部在 C 运行时里,核心是 runtime/dec.c,快路径内联在 runtime/tov.h 中。它的关键定义(runtime/tov.h):

一个 decimal 是系数 × 10^(-scale),其中:

  • 系数是带符号整数,最多 34 位有效数字(与 IEEE decimal128 一致);
  • scale是 0..34 的十进制位数;
  • 两者被打包进128 位:系数左移 8 位,scale 塞进最低字节。
[ 符号+系数(最多34位有效数字)<< 8 ][ scale(0..34,1字节) ] 128 位 = 两个 64 位寄存器

这个布局带来三个决定性优势:

  1. 零堆分配:一个金额就是两个 64 位字,按值传递、直接进寄存器。0.1m + 0.2m === 0.3m这种运算没有任何对象被创建;
  2. 同 scale 运算退化为整数运算:加减、比较只需一次 128 位加法或比较(runtime/tov.h 中的内联函数);
  3. 乘法也常是一条指令:两个系数若都落在 64 位内,乘积就是一次 64×64→128 位乘法(runtime/tov.h)。

只有当结果超过 34 位、必须对齐 scale 时才走慢路径——runtime/dec.c 中 8 个 limb 的tv_wide缓冲配合 runtime/big.c 的长算术。而在金额场景里,价格、数量、税额几乎都是小数点后两位,慢路径几乎从不触发。

快路径全覆盖:为什么钱算起来这么便宜

decimal_ledger 的循环体是"解析文本 → 乘 0.029 → 加 0.30 → round(2) → 累加",每一步都被专门优化过:

操作慢的做法Tov 的快路径
从文本解析逐字符建数字数组≤19 字符时整个数在 64 位内直接累乘(runtime/dec.c)
相加对齐小数点、分配新对象scale 相同 → 一次 128 位加法
乘费率数字串乘法双 64 位系数 → 一次 128 位乘法
round(2)构造、舍入、再构造系数在 64 位内且丢弃 ≤18 位时,一次 64 位除法搞定(runtime/tov.h)
除以小数通用长除法除数 < 2^64 时,每次 19 位用预计算倒数做 128÷64 除法;除以 10 的幂则只改 scale(runtime/dec.c)

最漂亮的是最后一行:发票基准里每行都要(discount * totals[l] / sum).round(2)。Tov 把"除法+舍入"融合成一个函数tv_dec_div_round(runtime/dec.c)——数学上可证明某些条件下直接除到 k 位再舍入,与"先算 34 位商再舍入"结果一致,于是省掉了一整趟中间计算;条件不满足才退回通用路径。官方 bench/README.md 对这套设计的总结很精炼:

A decimal is 128 bits: a 34-digit coefficient and its scale, so same-scale+,-,<are single 128-bit operations, and a product of two 64-bit coefficients one multiply.

big.js 输在哪里?

对照 bench/micro/decimal_ledger/main.ts 就一目了然:big.js 是纯 JavaScript 库,每次amount.times(rate)、.plus(fixed)、.round(2)都在堆上新建一个 Big 对象,内部用数字数组/字符串做长运算,还要经过 JIT、内联缓存和垃圾回收。200 万笔账目 × 每笔 5 次运算 ≈ 上千万次对象分配——这正是 67~182 MB 峰值内存的来源。

而 Tov 的编译产物是 C 代码直接链接 runtime/ 运行时:decimal 值走寄存器、没有 GC、运算分支在__builtin_expect提示下被编译器排成直线代码。动态语言"每个值都是对象"的世界观,在这里被"每个值就是 16 个字节"彻底替换掉了。

精度保证:34 位有效数字与银行家舍入

快,不能以错为代价。Tov 的 decimal 语义(完整定义见 docs/spec.md 的 decimal 小节):

  • 加、减、乘完全精确,保留所有位数:1.10 + 2.205得3.305,1.10 * 3得3.30;
  • 结果超 34 位时按half-even(银行家舍入)舍入,整数部分超 34 位直接报错(trap);
  • round(places, mode)支持 TC39 Decimal 提案的 9 种舍入模式("halfEven"、"halfExpand"、"trunc"等),实现见 runtime/dec.c;
  • decimal("12.50")从文本精确解析,try捕获格式或超范围错误。

这也解释了为什么 Tov 敢让.tov文件直接写const price: decimal = 19.99(m后缀字面量19.99m亦可)——类型系统在编译期就锁死了语义,运行时只需执行那条 128 位快路径。

如何复现这组数据

仓库自带了完整的对拍工具链:四个语言版本(C / Rust / big.js / Tov)处理同样的数据、打印同样的结果,校验器会逐字比对输出。

python3 bench/run.py --only decimal_ledger,decimal_invoice --langs rust,node,bun,tov

运行说明见 bench/README.md。Tov 端的 decimal 正确性测试还在 tests/run/bigjs.tov 和 tests/run/bigjs_api.tov 中覆盖了与 big.js API 兼容的常见用法。

小结

  • 128 位布局(34 位系数 + 1 字节 scale,与 decimal128 同精度)让金额成为寄存器里的值,而非堆上的对象;
  • 同 scale 加减 = 一次 128 位运算,双 64 位乘法 = 一次乘法,除法按 19 位一块用倒数加速,"除+舍入"还能融合;
  • 慢路径只在超出 34 位时出现,金额场景几乎不触发;
  • 对比之下,big.js 的每次运算都是一次对象分配 + JIT + GC,几十到上百倍的差距自然而来。

这正是 Tov 的整体思路的缩影:TypeScript 的写法,C 的骨架,把钱算得快,还算得对。

【免费下载链接】Tov

A TypeScript-shaped language built for coding agents. It compiles to small, fast native binaries.

项目地址:https://gitcode.com/gh_mirrors/to/Tov
点击查看免费下载
上一篇:sql-tap源码深挖②:不用SQL解析器写出查询归一化算法的完整实现
下一篇:企业集成 Univer 前必看:Excel 格式丢失、数据错乱的 4 个真实雷区

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

返回列表