整数溢出陷阱:用除法安全比较乘积
整数溢出陷阱:用除法安全比较乘积
在金融、电商、游戏等涉及大数值的业务中,经常出现类似判断:
if(balance>=unitPrice*quantity){// 余额足够支付}当unitPrice * quantity超出整型上限时,计算结果发生环绕,条件失效。尤其在 32 位环境下(int32_t上限约 21 亿),9 亿单价乘以 1 万数量,乘积 9 万亿远超上限,直接溢出为负数或小正数,导致错误放行或误拒。
本文给出一种零成本、完全等价的防御写法。
1. 溢出问题演示
假设使用int64_t存储金额(单位为分),手上余额balance = 9000 0000 0000(900亿分),需判断能否支付单价unitPrice = 9 0000 0000(9亿分)的 1 万件商品。
乘法表达式:
9 0000 0000 * 10000 = 9 0000 0000 0000 (9万亿)int64_t最大值约 922 亿亿,看似不会溢出,但若使用int32_t或uint32_t则会直接溢出。即便使用 64 位,若单价和数量进一步放大,依然会溢出。核心问题是:无法预知乘积是否越界。
2. 解决方案:除法比较
将原条件balance >= unitPrice * quantity等价转换为:
balance / quantity >= unitPrice前提:quantity > 0。这是业务上的自然约束(数量不能为 0 或负)。
转换后,除法运算的上限是balance,不会超出整型范围,彻底规避乘法溢出。
3. 等价性证明
设balance = q * quantity + r,其中0 ≤ r < quantity,则balance / quantity = q。
原条件:
balance >= unitPrice * quantity ⇔ q * quantity + r >= unitPrice * quantity ⇔ q + r/quantity >= unitPrice由于r/quantity < 1,且unitPrice为整数,因此q + r/quantity >= unitPrice等价于q >= unitPrice。即:
balance / quantity >= unitPrice整除截断不会造成误判,比较结果严格等价。
4. 决策流程
在实际编码中,可以先用乘法安全阈值判断是否走除法分支,以获得最佳性能:
其中MAX为当前整型的最大值。若unitPrice > MAX / quantity,乘积必溢出,走除法路径;否则乘法安全。
5. 注意事项
- quantity 必须为正整数。如果业务中可能出现 0 或负值,提前做参数校验。
- 整除不会影响正确性。比较运算不关心余数,截断后的商已经足以判定。
- 无需引入高精度或浮点库。原地使用原有整型,指令开销与乘法几乎无差别。
- 可用于无符号整数。所有讨论同样适用于
uint32_t/uint64_t,且无需担心符号位。
6. 总结
遇到形如A >= B * C且乘积可能溢出时,直接改写为A / C >= B(C > 0)。这是一种简洁、可移植、无性能损失的防御技术,无需扩展整数宽度,就能彻底消除隐性溢出 bug。
在数值范围不可预知的接口中,养成这种写法习惯,能从源头避免难以复现的边界错误。
