PAYATHON 2026

Access 对 Double 类型列求和时结果不准确

支付老李

结论

这是典型的二进制浮点精度问题,并不是 Access 求和出错。

Double 无法精确表示大多数十进制金额。把字段格式设置为 Standard 并显示两位小数,只会改变显示效果,不会将底层数值限制为两位小数。因此:

  • 1.13686837721616E-13 是一个非常接近 0 的微小误差。
  • -2.99999999999727E-02 约等于 -0.03。
  • 使用 <> 精确比较时,即使两个值都显示为 728.57,底层数值也可能存在细微差异。

付款金额最好使用 Currency。也可以先在查询中转换成 Currency,再进行比较。如果无法修改数据类型,就应设置合理的允许误差,而不是直接使用 <>。

为什么显示相同,比较结果却不同

Double 使用 IEEE 754 二进制浮点格式。0.1、455.42、728.57 这类十进制小数通常无法用有限位二进制数精确保存,系统只能存储与其非常接近的近似值。

例如,Access 内部的两个值可能类似于:

Amount = 728.57000000000005
Total  = 728.56999999999994

格式化为两位小数后,两者都会显示为:

728.57

但执行精确比较:

Total <> Amount

结果仍然是 True。

同样,科学计数法表示的:

1.13686837721616E-13

实际是:

0.000000000000113686837721616

如果金额以“分”为最小单位,这个差值可以视为零。

推荐方案:使用 Currency 类型

Access 的 Currency 是定点数类型,可保留四位小数,适合存储和计算货币金额,也不会产生 Double 的二进制小数误差。

如果可以修改 SharePoint 列定义,应将相关金额列设为货币类型,并确认链接到 Access 后,字段被识别为 Currency。只把显示格式改成两位小数并不能解决问题。

如果链接表中的字段仍是 Double,可以在查询中使用 CCur() 转换。由于参与加法的任意字段为 Null 时,整个结果都会变成 Null,建议同时使用 Nz():

TotalAmount:
    CCur(Nz([Regular Payment], 0))
  + CCur(Nz([Principle], 0))
  + CCur(Nz([Interest Only], 0))
  + CCur(Nz([Annual Fee], 0))
  + CCur(Nz([Late Fee], 0))
  + CCur(Nz([Escrow], 0))
  + CCur(Nz([CPI], 0))
  + CCur(Nz([Payoff], 0))
  + CCur(Nz([Unapplied], 0))
  + CCur(Nz([Short Payoff], 0))

比较时,也要转换 Amount:

WHERE
    CCur(Nz([Regular Payment], 0))
  + CCur(Nz([Principle], 0))
  + CCur(Nz([Interest Only], 0))
  + CCur(Nz([Annual Fee], 0))
  + CCur(Nz([Late Fee], 0))
  + CCur(Nz([Escrow], 0))
  + CCur(Nz([CPI], 0))
  + CCur(Nz([Payoff], 0))
  + CCur(Nz([Unapplied], 0))
  + CCur(Nz([Short Payoff], 0))
  <> CCur(Nz([Amount], 0));

这种写法要求金额不能超出 Currency 的可表示范围,而且业务所需精度最多为四位小数。

无法改变字段类型时:使用允许误差

如果数据必须保留为 Double,就不要判断两个值是否完全相等,而应检查差值是否超过允许范围。

金额精确到“分”时,通常可以用半分钱 0.005 作为边界:

WHERE Abs(
    Nz([Amount], 0)
    - (
        Nz([Regular Payment], 0)
      + Nz([Principle], 0)
      + Nz([Interest Only], 0)
      + Nz([Annual Fee], 0)
      + Nz([Late Fee], 0)
      + Nz([Escrow], 0)
      + Nz([CPI], 0)
      + Nz([Payoff], 0)
      + Nz([Unapplied], 0)
      + Nz([Short Payoff], 0)
    )
) >= 0.005;

使用这个条件后:

  • 1.13686837721616E-13 不会被判定为不匹配。
  • -1.13686837721616E-13 不会被判定为不匹配。
  • 约为 -0.03 的差值仍会被识别出来。

如果业务规则是忽略所有不足一分钱的差异,也可以设置更小的容差,例如:

WHERE Abs([Amount] - [Total]) > 0.000001

具体阈值应根据金额精度和业务规则确定,不能只看显示的小数位数。

也可以按两位小数比较

如果业务明确规定所有金额都先舍入到“分”,可以比较舍入后的结果:

WHERE Round(Nz([Amount], 0), 2) <>
      Round(
          Nz([Regular Payment], 0)
        + Nz([Principle], 0)
        + Nz([Interest Only], 0)
        + Nz([Annual Fee], 0)
        + Nz([Late Fee], 0)
        + Nz([Escrow], 0)
        + Nz([CPI], 0)
        + Nz([Payoff], 0)
        + Nz([Unapplied], 0)
        + Nz([Short Payoff], 0),
          2
      );

Access/VBA 的 Round() 使用银行家舍入,中间值可能会舍入到最近的偶数。某些正好位于 .005 边界的值,可能不符合财务系统常用的“四舍五入”规则。更稳妥的长期方案,是从数据录入阶段开始使用 Currency,并统一金额的舍入规则。

注意事项

  • Decimal Places = 2 只控制显示,不会改变字段中实际保存的值。
  • Format() 返回的是格式化文本,不适合用于修复或比较金额。
  • 加法表达式中只要有一个字段为 Null,整个结果就会变成 Null,因此应根据业务含义使用 Nz(..., 0)。
  • 付款、余额和费用等金额应优先使用 Currency。Double 更适合科学计算和测量值。
  • -2.99999999999727E-02 并非异常结果,只是 -0.03 的浮点近似表示。显示时可以将其格式化为两位小数,但比较金额是否匹配时,应使用 Currency 或允许误差。

备注:内容仅供参考。