被辞退了,因为小数点计算错误

故事背景

今天我一个同事跟我吐槽,说他朋友因为程序问题,被公司辞退了,而且还没有任何补偿。我一听马上问,是删库跑路了嘛,这么严重。他说比这个还严重,说因为BigDecimal小数点四舍五入出现问题,导致订单金额偏低,公司损失了十几万美金,而且因为发现的晚,订单都已经发货了,钱要不回来了,造成很大的影响。虽然他朋友是公司老员工,但是发生这么大的事情,也只能引咎辞职了,而且因为个人问题导致公司权益受损,公司有权辞退,并且不进行任何赔偿。

我听完这个事情,久久无法回神,对众多小公司而言,因为用户量不高,服务宕机一段时间,其实不会直接造成非常大的影响(滴滴这种独角兽除外),而金额计算错误导致的问题,大多数都是非常致命的错误,目前金额计算一般都采用BigDecimal来进行运算,但是如果BigDecimal不会用或者没用好,也是会造成严重的线上问题。 为了引以为戒,博主特意整理了BigDecimal的易错场景,来培训团队成员,让大家引以为戒,可以用好BigDecimal,计算好金额,保住自己的饭碗。

易错点一:BigDecimal构造参数导致精度丢失问题

BigDecimal decimal = new BigDecimal(0.01); 打印实际值:0.01000000000000000020816681711721685132943093776702880859375 建议使用:BigDecimal.valueOf()方法赋值,比如: BigDecimal decimal1 = BigDecimal.valueOf(0.01); 同时,如果在高并发或者大量对象创建场景时,也不建议使用new BigDecimal方式创建对象,否则会影响性能,同样推荐BigDecimal.valueOf方式创建对象。

知识点:所以推荐使用BigDecimal.valueOf来赋值,确保金额数据的精度正确。

易错点二:正确使用两个BigDecimal对象的大小比较

num1.equals(num2) 或者 num1.compareTo(num2) 都是比较两个数的大小,但是它们有区别: equals会先比较值,再比较精度;而compareTo直接比较值,不会比较精度。 BigDecimal decimal2 = new BigDecimal("0.01"); BigDecimal decimal3 = new BigDecimal("0.010"); //false,因为精度不一样 decimal2.equals(decimal3); //true, 只比较数值,不会比较精度 decimal2.compareTo(decimal3)==0;

知识点:所以要根据业务正确选择比较大小方法,确保业务逻辑的正确性。

易错点三:做除法运算时,必须设定精度和选择正确的舍入模式

BigDecimal d1 = BigDecimal.valueOf(1.00); BigDecimal d2 = BigDecimal.valueOf(3.00); BigDecimal d3 = d1.divide(d2);

上述代码执行后,会直接抛出异常:java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result. 正确写法: // 精度设置为2位,选择向远离零的方向四舍五入 BigDecimal d3 = d1.divide(d2, 2, RoundingMode.HALF_UP); BigDecimal舍入模式:

  • RoundingMode.UP:向远离零的方向舍入
  • RoundingMode.DOWN:向靠近零的方向舍入
  • RoundingMode.CEILING:向正无穷方向舍入
  • RoundingMode.FLOOR:向负无穷方向舍入
  • RoundingMode.HALF_UP:向远离零的方向四舍五入
  • RoundingMode.HALF_DOWN:向靠近零的方向四舍五入
  • RoundingMode.HALF_EVEN:银行家舍入法,遵循IEEE 754标准

知识点:所以做除法运算时,务必设定精确位数,避免系统异常崩溃。并且正确理解舍入模式的含义,有助于满足业务的需求。

易错点四:BigDecimal对象一旦设值不可修改原则问题

BigDecimal使用setScale方法设置精度时,原对象不会被修改,需要用新对象去接收。 BigDecimal num = new BigDecimal("1.2345"); // num变量并没有变化,还是4位小数; num.setScale(2, RoundingMode.HALF_UP); // result 才是四舍五入后并保持2位小数点。 BigDecimal result = num.setScale(2, RoundingMode.HALF_UP);

知识点:我们要理解不可变的特性,不要出现理解上的歧义,导致业务出现问题。

复盘反思

BigDecimal只是Java功能中小到不能再小的功能点,但是却有这么多的注意事项。写了这么多年代码之后,我可以明显的感觉到,初级开发和高级开发有着非常明显区别,特别是对待程序严谨性上,经验越多的程序员,可以想到更多的异常场景,从而保证最后的开发质量。