二进制浮点数精度问题导致0.1+0.2≠0.3,在金融场景会造成对账漂移和审计风险,文中给出各语言正确处理金额的decimal方案。
最初发表于 acuity.press,作者:Sandeep Dhuri

让 AI 助手用任何主流语言"计算订单总额",大多数时候得到的都是二进制浮点数:double、float、number。这是互联网上最常见的货币表示方式,所以也是最常见的补全结果。然而在所有受监管领域,用二进制浮点数做货币计算都是错误的,原因在于 IEEE 754 的规定不可协商:0.1 + 0.2 ≠ 0.3。每次运算的误差很小,但会随着规模扩大而累积。对账差异、舍入争议、审计问题由此而生。
Delta's Law 5 用五个字阐明:"货币不用浮点数,无例外。" 该书由此推导出逆定理:类型即约束。(整整一代工程师从一部电影里学到了这种失败模式:《办公空间》里的香肠切片骗局,从机械原理上讲就是一个舍入故事。正是浮点数让它变得看似合理。)有两步措施可以解决这个问题。
第一步:在 prompt 中明确声明
模型无法遵守一个从未收到的约束。每个涉及金额的 prompt,都要在其约束块中加入一行:
Monetary values MUST use exact decimal representation (C#: decimal; Java: BigDecimal or integer minor units; Python: decimal.Decimal; TypeScript/JS: integer cents or a decimal library). Binary floating point (double/float/number) for money is prohibited. Rounding: banker's rounding at aggregation boundaries only, per [your policy].
这唯一一条约束改变了模型对"合理"的定义。Veracode 2026 年报告中关于模型为何在安全任务上失败的发现,完全适用于此处:训练语料编码了历史模式,唯有你明确声明的约束才能压过语料。
第二步:用类型系统强制执行
Prompt 引导生成方向;类型捕获引导遗漏的部分。该书的模式是一个领域 Money 类型,其基于 float 的构造函数存在但被标记为不可用——在 C# 中,是一个带有 [Obsolete(error: true)] 的构造函数,接受 double,因此任何 AI 或人类编写的代码试图用 float 构造 Money 时,编译就会失败,并附带解释原因的错误信息。Java:基于 BigDecimal/long-cents 的 Money 包装器,不提供 float 工厂。Python:传入 float 就抛异常的 Money 数据类。TypeScript:带品牌标记的 Cents 整数类型。配套代码仓库(MIT 许可)包含全部四种实现及测试。
两者配合比单独任何一半都更重要。Prompt 约束阻止了大多数不良生成;被"毒化"的构造函数将幸存者从静默的精度漂移转变为编译错误。从最昂贵的失败类型变为最便宜的。这种做法的一般形态是:先有规格说明,验证设计嵌入到产物之中,"看起来对"永远不是最后一道关卡。
为什么这值得一篇完整文章的篇幅来养成习惯
因为这类底层失败模式的ambient发生率并未下降。2025 至 2026 年间,AI 代码生成任务引入已知漏洞的比例一直维持在 45% 左右(Veracode,150+ 模型),且 AI 可归因的 CVE 逐月加速增长(佐治亚理工 Vibe Security Radar:2026 年 1 月 6 个,3 月达 35 个)。浮点货币问题就是金融包装下的同一现象:一种正确性约束,语料库没有编码,模型也不会主动补充。一行约束加一种类型,永久关闭这个漏洞。
这些年来,我在三个不同的代码库中追查过这种精确的分币级漂移。每次修复的 commit 消息都是一通道歉。
改编自《Delta:Closing the Specification Gap》(Dhuri, 2026)第 5 定律及第 4 章——免费阅读:https://acuity.press · DOI: 10.5281/zenodo.21584309。配套代码(MIT):github.com/sandeep-dhuri/specification-frame。
来源:Veracode, 2026 GenAI Code Security Report (2026);Georgia Tech SSLab, Vibe Security Radar (2026)。