一、问题
项目在 UAT,客户报了一个错误,说在提交一个单子的时候,系统报“主表总金额与明细汇总总金额不相等”,提交失败。
从界面上看,两个金额是相等的,但是程序却判断不相等。
二、分析原因
把问题提交到开发,但是开发上死活查不出原因,也不能再现错误,通过用户的账号提交报错的单子也没问题,提交成功。
我帮他分析原因。
我:这个检查是前台校验,还是后台校验
开发:后台校验
我:是不是提交时,没有保存数据,修改前确实不相等,导致检查不通过
开发:不会,单据投的金额是根据明细行自动汇总计算的,并且提交前强制保存数据。
我让测试试图再现这个问题。
厉害,测试竟然再现了这个问题,从界面上看,金额事项等的。看来问题确实存在。
我:你把校验逻辑再检查一下
开发:检查了好几遍了,没问题
我:校验逻辑发给我
开发:好的
(开发贴出代码如下。以下代码隐掉了非关键代码)
double pBckpje = 0.0; double pSumje = 0.0; //取主表总金额 //-----省略 //取明细汇总金额 //-----省略 if (CommonUtils.round(pBckpje, 2) != CommonUtils.round(pSumje, 2)) { throw new Exception("主表总金额与明细汇总总金额不相等"); }
我靠,竟然直接用 != 来判断浮点数是否相等!?
因为这段代码是为老程序员写的,我干脆一点没往这方面想,没有怀疑到浮点数比较这个问题。看到这个代码,我有点不敢相信,应该不会这么水啊!瞄到他这里用到了一个工具类的round方法,也许里边有什么玄机吧?
我:把这个round 代码贴出来:
开发:好的,
public static double round(double amt, int scale) { BigDecimal bigDecimal = new BigDecimal(String.valueOf(amt)); return bigDecimal.setScale(scale,BigDecimal.ROUND_HALF_UP).doubleValue(); }
看到这里我有点上火了。
这个方法用bigdecimal 仅仅做了 round,然后有又转换成了double返回。这样做对上面的比较一点用处没有啊。
三、如何比较浮点数
上面的比较方法显然是错误的。老鸟都知道,在二级制世界中,浮点数的表示是不精确的,直接用 <>=!=比较浮点数大小是很危险的,结果是不确定的。
比较浮点数必须要考虑精度,不考虑精度来比较浮点数大小是耍流氓。我之前的做法是基于精度对浮点数进行比较,比如,假设精度为2,如果 abs(d1-d2)<0.01,就说明 d1=d2,否则 d1<>d2。
我们可以在工具类中,实现一个浮点数比较的方法:
static int floatCompare( double d1, double d2, int scale ){ if ( Math.Abs( d1 - d2 ) < 1/10^scale ) return 0 ; //d1=d2 else if ( d1 - d2 >= 1/10^scale ) return 1 ; // d1>d2 return -1 ; //d1<d2 }
大体是这个意思把。以上是c语法,也不甚很严谨,没测试。
浮点数的比较方法,百度上一大堆,大家可以搜一下。这里就不详细讲解了。