精度损失而引发的 bug

Posted 机智的小小帅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了精度损失而引发的 bug相关的知识,希望对你有一定的参考价值。

精度损失而引发的 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

这两个等式是始终成立的

PHP 精度计算引发的灾难性Bug

  在维护一个比较老的金融项目时,发现精度的计算简单粗暴,先来看一下代码片段:

if($accountInfo[\'account_money\'] < $repayMoney ){
                rollback();
                return false;
}

  如上代码片段变量所示,上面的代码主要是比较帐户余额及还款数,两者均为双精度浮点数,稍作修改代码,让我们能看到一些输出才更直观

 if($accountInfo[\'account_money\'] < $repayMoney ){
                echo "{$accountInfo[\'account_money\']}\\n\\r";
                echo "{$repayMoney}\\n\\r";
                rollback();
                return false;
}

运行如上代码得到如下图结果

两个数均为1333.35,如果按上面的逻辑来说,不应该进入到 if 体内,但结果却恰恰相反,进行 if 体内并echo 出了两值,通过查阅相关api 发现,PHP 的精度运算,会在直接+,-,*,/,<,>等运算时丢失精度,因此结果并非如你所愿,在这里提现大家,在PHP中对精度运算,应调用相关API,如BC Math.

以上是关于精度损失而引发的 bug的主要内容,如果未能解决你的问题,请参考以下文章

PHP 中一个 False 引发的问题,差点让公司损失一百万

keras 中是不是有基于精度或召回而不是损失的优化器?

如何处理由于分组函数而导致的 JDBC 数字类型的精度损失

C# 中的双倍比较精度损失,加减双精度时发生精度损失

Java POI读取excel中数值精度损失

tensorflow VGG16 网络精度和损失不会改变