KernelRanch
返回文章列表

金额差一分钱怎么查:Decimal 的输入与舍入时机

2026-11-12 #python#decimal#data-quality

金额对不上时,先记录原始十进制字符串、舍入规则和舍入发生的位置。使用 Decimal 后,逐行舍入再求和与求和后舍入仍可能产生不同结果,接口和账单必须选定同一套规则。

1. 三笔小数就能复现差额

保存为 decimal_lab.py,运行 python decimal_lab.py。这里演示非负金额,最多十二位整数和四位小数,舍入到两位小数采用 ROUND_HALF_UP。这是本例的业务约定。

import re
from decimal import Decimal, ROUND_HALF_UP, localcontext

CENT = Decimal("0.01")

def parse_amount(text):
    if not isinstance(text, str) or not re.fullmatch(r"(0|[1-9][0-9]{0,11})(\.[0-9]{1,4})?", text):
        raise ValueError("invalid amount")
    return Decimal(text)

def settle(amount):
    return amount.quantize(CENT, rounding=ROUND_HALF_UP)

with localcontext() as ctx:
    ctx.prec = 32
    exact = parse_amount("2.675")
    assert settle(exact) == Decimal("2.68")
    assert settle(Decimal(2.675)) == Decimal("2.67")
    rows = [parse_amount("0.005")] * 3
    per_line = sum(map(settle, rows), Decimal("0"))
    on_total = settle(sum(rows, Decimal("0")))
    assert (per_line, on_total) == (Decimal("0.03"), Decimal("0.02"))
    print("from string:", settle(exact))
    print("from float:", settle(Decimal(2.675)))
    print("per line:", per_line, "on total:", on_total)

for invalid in ["NaN", "Infinity", "1e9", "-1", "1.23456"]:
    try:
        parse_amount(invalid)
    except ValueError:
        pass
    else:
        raise AssertionError(invalid)
print("invalid inputs: rejected")

2. 把差额定位到具体步骤

实测结果发生了什么修复位置
字符串输入得到 2.68,float 输入得到 2.67float 在进入 Decimal 前已有二进制表示误差从 JSON 字符串或原始十进制文本解析
逐行结算 0.03,整单结算 0.02三次舍入与一次舍入产生不同累计值明确行项目和总额的结算顺序
NaN、指数形式或过多小数被拒绝输入超出本例契约API 返回字段校验错误

从客户端到数据库都要保留金额的表示约定。若客户端先算成浮点数再转成字符串,服务端收到的误差已经存在。整数分适合已经结算到分的金额,折扣比例和中间计算仍需要独立精度。

3. 分摊折扣时验收总和

例如将 1.00 平均分给三行,先分配 0.33、0.33、0.33,还剩 0.01。按固定规则把余数给某一行,例如按行 ID 排序后分配。验收时同时检查每行精度、各行之和、负数或退款的规则。将分摊结果存下来,后续退款引用原结果。

示例上下文精度 32 足够覆盖这些小数据。批量汇总和乘除运算需要根据金额上限、数量和中间结果重新确定精度。新增舍入规则时,用半分边界和正负数各做一组回归用例。

小结

保留十进制输入,显式指定舍入规则,并在固定的业务步骤结算。对账时同时保留行金额和总金额,才能定位差额发生在哪一次计算。

数据来源与检索时间

检索时间 2026-11-12。

来源URL对应内容
Python 3.11 官方文档https://docs.python.org/3.11/library/decimal.htmlDecimal 构造、上下文精度与 quantize

核验说明

  • 主示例在本地 Python 3.11.9 下执行,检查正文中的断言与输出。
  • 实验范围是文中构造的输入和本地运行条件,部署时需按实际数据规模与运行环境核验。