精度损失而引发的 bug
本周碰到因为 精度损失,导致 分段计算的结果之和 ≠ 整体计算的结果
基本背景
有一个佣金功能,需要计算销售人员每个月的佣金以及销售人员所有月份的总佣金。
佣金金额 = 销售额 * 佣金比例
其中 销售额 和 佣金 的精度都是当前货币的最小单位。目前货币单位为美元,最小单元为美分。
最初的实现 (有 bug)
单月佣金 = 单月销售额 * 佣金比例
总佣金 = 总计销售额 * 佣金比例
异常 case
上面的实现在一般情况下的确是没有问题的。
但是偶尔会出现: 总佣金 ≠ 单月佣金之和的情况
举个例子,假设佣金比例为 0.3
销售人员在一月的销售金额为 3333 美分,则他一月的佣金为 3333 * 0.3 = 999.9 -> 999 美分 (美分即为最小单位了, 直接取整)
销售人员在二月的销售金额为 3333 美分,则他二月的佣金为 3333 * 0.3 = 999.9 -> 999 美分
销售人员总计销售额为 6666 美分,则它总佣金为 6666 * 0.3 = 1999.8 -> 1999 美分
此时 999 + 999 = 1998 ≠ 1999
原因是将浮点数强行转换为整数而造成了精度损失。
这种精度损失是无法避免的,我们能做的只是让结果看起来不那么怪。
在这种情况下:用户发现 每个月的佣金之和 ≠ 总佣金,可能就会觉得非常奇怪。
如何让 每个月的佣金之和 = 总佣金 呢?
有两种解决方案
资本家的做法: 优化总佣金的计算方法
原来的佣金计算方法为
单月佣金 = 单月销售额 * 佣金比例
总佣金 = 总计销售额 * 佣金比例
现改为
单月佣金 = 单月销售额 * 佣金比例
总佣金 = 单月佣金之和
应用到上面的 case 里,总佣金就不在是 1999 了,而是 999 + 999 = 1998。
足足少支付了一分钱,资本家心满意足的离开现场
不贪小便宜的做法: 优化单月佣金的计算方法
总佣金 = 总计销售额 * 佣金比例
第 n 月佣金 = 第 (1 ~ n) 个月的总佣金 - 第 (1 ~ n - 1) 个月的总佣金
应用到上面的例子中:
一月的佣金 = 3333 * 0.3 - 0 = 999
二月的佣金 = (3333 + 3333) * 0.3 - 3333 * 0.3 = 1999 - 999 = 1000
总佣金 = 6666 * 0.3 = 1999 = 999 + 1000
没有贪墨劳动人民的一分血汗钱!
总结
由于进度损失
m * p + n * p ≠ (m + n) * p
的现象是无法避免的
可以使用加减法来替代乘法
因为
m * p + n * p = m * p + n * p
m * p + ((m + n) * p - (m * p)) = (m + n) * p
这两个等式是始终成立的