第 71 个主题,共 91 个· 软件工程与数字化交付

币种安全的成本汇总 (Currency-Safe Cost Rollup)

用常见的二进制浮点数(f64)把大量货币条目——月度账单、各地点成本、多年预算影响数字——加起来,会累积微小的表示误差,因为大多数十进制小数(如 $1,234.56)无法在二进制浮点数中精确表示。每个误差都很小,但一个把数百或数千个条目跨越多年相加的大模型,可能偏离几分之一分——而且偏差取决于加法的顺序,因而不可复现。用精确十进制算术(或整数最小单位)做的货币汇总可以精确相加,与会计系统和复式记账必须对到分的方式一致。

为什么重要

这是一种有大量文献记录的基础软件缺陷:Goldberg 1991 年发表于 ACM Computing Surveys 的论文 "What Every Computer Scientist Should Know About Floating-Point Arithmetic",是说明二进制浮点数为何无法精确表示大多数十进制金额、以及为何把很多这样的数相加会放大误差的标准参考。NHS 的卫生经济学和财务模型经常把多年、多个成本类别相加——总拥有成本和预算影响分析都会跨年份把许多 f64 成本条目加总。当模型必须对到分——手工算总数的审计必须得到完全相同的数字——算术本身就必须是精确十进制,而不是浮点数。

计算方法

朴素汇总:        总计 = Σ f64(条目_i)       —— 取决于顺序的漂移
币种安全汇总:    总计 = Σ Decimal(条目_i)   —— 精确、可复现

应用百分比调整(例如应急费用):
  调整后 = 总计 × 乘数            —— 精确的 Decimal 结果,小数位数可能多于
                                    该币种最小单位的指数
  舍入后 = 舍入(调整后, 币种指数, 舍入规则)
                                  —— 舍入规则(half-up 对 half-even/银行家舍入)
                                    必须明确指出

请注意两步纪律:把精确的 Decimal 乘以乘数,可能得到比币种实际使用更多的小数位(例如两位小数的金额乘以两位小数的乘数得到三位小数)——这一中间精度不会被自动截断;只有带有指明舍入规则的明确舍入步骤,才会把它降到币种真正的最小单位指数。

算例

十二张完全相同的月账单,每张 $1,234.56,用精确十进制算术相加:$1,234.56 × 12 = 恰好 $14,814.72;相比之下,用 IEEE-754 双精度把 f64 常量 1234.56 加十二次,可能因加法顺序而偏离几分之一分——这是真实且有文献记载的缺陷类型,对建立在精确十进制 Money 算术上的模型不是问题。

现在对该 $14,814.72 总额应用标准的 5% 预算影响应急费用(乘数 1.05):$14,814.72 × 1.05 = $15,555.456——三位小数,因为乘法是精确的,不会自动舍入到币种的两位小数。用银行家舍入(half-even)明确舍入到 2 位小数,得到恰好 $15,555.46。

与软件工程的关联

这是“金融软件用 Decimal,不用 float”这一原则背后的直接基础课——与本仓库的总拥有成本和预算影响分析模块明确相关,这两个模块目前都用普通浮点数汇总成本。正确性的论证并不要求立即迁移这些模型;它指出的是系统何时必须对到分,因而不得用二进制浮点数做其货币算术。关于与之配对的问题——如何拆分(而不是相加)一个总额而不损失一分钱——见精确到分的成本分摊。

陷阱

  • 链条中途转成 float:在计算中途把货币值取出为浮点数(有些 Money 库甚至把这种转换方法命名为“lossy”作为明确警告),会悄悄放弃此后每一步计算的精确性保证。
  • “Decimal 太慢,不值得在意”:把精确十进制算术当作不必要的负担而否定,而财务报告需要的是正确性和可审计性,不是原始吞吐量。
  • 应用应急百分比却不说明舍入规则:half-up 与 half-even(银行家舍入)可能改变最后一分钱;舍入约定本身必须是明确声明、可审计的选择——关于 HM Treasury Green Book 对应急费用调整和乐观偏差的指导,见成本效益分析,这正是该舍入步骤所适用的那类数字。

参考来源