金额精度审查 · 2026-10-09 13:20

这是审查报告里标了「🔴 完全未查」的两个维度之一。
结论先说:金额计算全部整数运算,未发现浮点混进分的问题。

一、结论

项结论
后端金额是否用浮点✅ 全程整数分,无浮点参与计算
C 端金额是否用浮点✅ 全程整数分,浮点只在最终显示时出现
部分退款的分摊✅ 守恒,尾差有明确归属
发现问题0 个(本次)

二、怎么查的(可复核)

后端:91 处初筛 → 逐处判类型

初筛规则(金额 ×0.01、round(fen)、float(fen)、浮点字面量)命中 91 处,

逐处判读后分成三类:

类型数量判定
分↔元单位换算(int(fen)/100、int(round(price*100)))80✅ 正常,转换点在边界
整数运算含 //(unit_fen * quantity、total * pct // 100)8✅ 全整数,无精度损失
round() 作用于金额3✅ 作用在「元→分」的换算结果上

后端:float() 的全部用途(逐个确认没有一个是金额)

sync.py:74            float(latitude/longitude)     ← 经纬度
stalls.py:27          round(float(v), 2)             ← 摊位费展示(读fen 转元)
weather.py:39-40      float(WEAHTER_LAT/LNG)         ← 天气坐标
weather.py:193        float(score)                   ← 评分
miniapp_status.py:111 float(v) / 118 int(float(v))   ← 状态值
admin_ops.py:330      float(rating)                  ← 评分

🔴 没有一个是金额计算。

Decimal 的用途

admin_ops.py:851      str(Decimal(fen) / 100)        ← 分→元展示(Decimal 是对的用法)
tasks_full.py:128     reward_credits * Decimal("0.7") ← 工分(不是人民币)

三、🔴 重点验算:部分退款的分摊(最可能算错的地方)

代码(miniapp_orders.py L1800-1805)

key_refund_fen = key_deduct_fen * pending_fen // total_fen
cash_paid_fen = total_fen - key_deduct_fen
cash_refund_fen = cash_paid_fen * pending_fen // total_fen
# 分币尾差归属:总 pending_fen = cash_refund + key_refund
diff = pending_fen - (cash_refund_fen + key_refund_fen)
cash_refund_fen += diff                      # 🔑 尾差补齐

验算(含尾差)

totalkeyDeductpendingkeyRcashR合计守恒
1000030005000150035005000✅
6880006880006880068800✅
99911011✅
1000333667222445667✅
688002000045867133333253445867✅
10013033✅
101011✅
111101✅

✅ 全部守恒。 L1804-1805 的尾差补齐是关键 —— 没有它,四种场景会差 1 分。

⚠️ 一个理论边界(实践中不可达)

pending_fen > total_fen 时不守恒(如 total=100, key=50, pending=150 → 75+75=150≠pending=150,其实守恒)。

实测也没有负数。 且 pending_fen 是 total_fen 的子集(未核销金额),

逻辑上不可能大于。不构成风险。


四、✅ C 端:整数运算 + 溢出上限 + 未知退中性

pages/redemption-code/redemption-code.ts(voucher 待付金额汇总)

const MAX_TOTAL_FEN = 100000000          // 100万元上限
if (dueFen + r.fen > MAX_TOTAL_FEN) { hasUnknown = true; continue }
...
dueFen += r.fen                          // 整数累加
// 显示时才转浮点
return '到店支付 ¥' + (dueFen % 100 === 0 ? String(dueFen / 100) : (dueFen / 100).toFixed(2))

三个做对的地方:

  1. 累加全程整数,/ 100 只在最后拼字符串时出现
  2. 有溢出上限 MAX_TOTAL_FEN,超出即视为「不可判定」
  3. 未知状态退中性:hasUnknown ⇒ 返回「支付信息以现场核验为准」,

不猜总额(与记忆里「任一单 unknown ⇒ 整批中性」的口径一致)

  1. 注释明确写了「基于分计算,无浮点长尾」—— 知道这个坑

C 端金额只有 1 处涉及 fen 运算(另一处命中是base64 图片误报)。


五、✅ 这套做法值得推广的地方

「整数分 + 显示时才转浮点」是这个项目做得最对的一件事之一。

对比常见错误写法:

// ❌ 常见错误:浮点累加
let total = 0
for (const o of orders) total += o.price      // price 是元(浮点)
// ✅ 本项目:整数累加
let dueFen = 0
for (const o of orders) dueFen += o.fen// fen 是分(整数)

建议把这个写进 docs/代码审查标准与流程.md 的 BLOCKER 列表:

金额一律用整数分计算,浮点只允许出现在最终展示层。
判断依据:能否回答「这个数从头到尾有没有经过浮点?」

六、🔴 本轮仍未覆盖(诚实标注)

未查说明
🟠 后端 91 处只逐处判了类型,未逐处追数据流大部分是 int(fen)/100 的展示转换,风险极低;但不是 100% 逐处追
🔴 前端 wxml/wxss与金额无关,但仍未审
🟠 微信支付侧的金额校验wxpay.py 里下单金额与实际计算是否一致,未查
🔴 优惠券/抵扣叠加后的分摊只查了「定金抵扣 + 部分退款」,未查多券叠加

七、我在这轮的一个错(记录)

我第一次验算时只复制了 L1801-1803 三行,漏了 L1804-1805 的尾差补齐,

算出「4 个场景两法差 1 分」,

差点误判成 🔴 bug。

发现方式:输出与代码注释矛盾(注释明明写了「分币尾差归属」)⇒ 回读代码才发现漏了两行。

⇒ 🔴 复制代码验算时必须核对行数,注释和代码对不上时以代码为准、但要问自己「是不是漏了」。