订阅更新

SQL 金额计算为什么有尾差?DECIMAL 怎么设计?

面试问 222 约 2 分钟读完

本文目录
  1. 面试怎么回答
  2. 先分清近似值和精确十进制
  3. 精度要看中间计算能长到多大
  4. 逐笔舍入和汇总后舍入回答不同问题
  5. 一个典型场景:尾差被错误归因给 DOUBLE
  6. 把精度检查做成可复算的规则
  7. 参考资料

面试怎么回答

金额问题我会从输入类型、中间精度和舍入规则排查。原始值尽量直接进入 DECIMAL,乘除和汇总要留足精度;逐笔舍入与汇总后舍入必须跟结算口径一致。分摊还需要稳定的尾差分配规则,最后按币种、批次和业务主键核对金额守恒,不能只看显示了几位小数。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

报表总金额和明细加总差了一分钱,明细看起来每行都保留两位小数。有人建议给结果再 ROUND 一次。问题是,这一分钱究竟来自存储误差,还是舍入顺序?

金额精度要沿输入、运算和输出全链路控制;十进制定点、舍入时点、币种单位与尾差分配规则需要同时明确。

先分清近似值和精确十进制

FLOAT、DOUBLE 用二进制近似表达很多十进制小数,不能假定累加结果与十进制业务计算完全一致。DECIMAL 或 NUMERIC 更适合要求精确十进制表达的金额,但它们也受精度、标度和运算规则约束。

如果源接口已经把金额转成浮点并产生误差,最后再 CAST 成 DECIMAL 只会固定当前近似值。更稳妥的路径是从原始字符串或明确的最小货币单位读取,并在采集时验证单位、符号和小数位。

精度要看中间计算能长到多大

DECIMAL(p,s) 的 p 是总有效位,s 是小数位,不能把 p 理解成整数位数。单笔金额能放下,不代表金额乘汇率、再汇总上亿笔后的结果也能放下。

我会把单笔上界、聚合规模、乘除运算和目标字段放在一起检查。不同引擎的精度推导、最大精度以及溢出处理并不相同,开发环境跑过一条普通样例,不能证明极值、负数和超长小数都安全。

逐笔舍入和汇总后舍入回答不同问题

比如三条待结算金额都是 0.014 元:每条按两位小数舍入后合计是 0.03,先合计得到 0.042 再舍入是 0.04。这是明确的示例数据,两条路径都能按各自规则计算正确,但口径不同。

结算按逐笔发生,报表就应汇总已确认的结算值;管理分析按高精度过程值计算,则要标注区别。正负恰好半值如何处理、汇率在哪一步转换,也应写进口径,不能交给前端显示格式决定。

同一组数,两种舍入顺序

一个典型场景:尾差被错误归因给 DOUBLE

比如费用表存储的已经是 DECIMAL,报表仍有尾差,团队反复扩大字段精度却没解决。沿链路对比后发现,明细先按比例分摊并逐行舍入,总表则直接保留原始费用。

原来的质量规则只校验字段类型与非空,没有检查分摊后总额守恒。处理方法是保留高精度分摊过程值,按明确且稳定的规则把尾差分给指定明细,并记录规则版本。不能每次随机补一分钱,否则重跑也会产生变化。

把精度检查做成可复算的规则

常用校验包括原币合计、本位币合计、换汇前后差额、分摊守恒以及逐笔与总账的舍入差异。空金额要区分未知、缺失和真实零值,负数要确认代表退款还是冲正。

金额按分存储能够简化一部分场景,但币种的小数单位并不全部相同,汇率与比例也仍需高精度运算。面试时讲清楚数据类型只是入口,再给出计算规则、异常样例和对账方法,回答才完整。

参考资料

刷题进度保存在当前浏览器