其他分享
首页 > 其他分享> > 事故总结集锦-BigDecimal在金额计算中丢失精度导致的资损事故 10(一周一更)

事故总结集锦-BigDecimal在金额计算中丢失精度导致的资损事故 10(一周一更)

作者:互联网

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情

背景

我们在使用金额计算或者展示金额的时候经常会使用BigDecimal,也是涉及金额时非常推荐的一个类型,BigDecimal自身也提供了很多构造器方法,这些构造器方法使用不当可能会造成不必要的麻烦甚至是金额损失,从而引起事故资损。

接下来我们看下收银台出的一起事故。

【问题描述】

【事故级别】

【过程】

【故障原因】 BigDecimal在金额计算中丢失精度

原因分析:

首先我们先用一段代码复现问题根源,如下所示:

public static void main(String[] args) {
    BigDecimal bigDecimal=new BigDecimal(88);
    System.out.println(bigDecimal);
    bigDecimal=new BigDecimal("8.8");
    System.out.println(bigDecimal);
    bigDecimal=new BigDecimal(8.8);
    System.out.println(bigDecimal);
}
复制代码

执行结果如下:

image.png

通过测试发现,当使用double或者float这些浮点数据类型时,会丢失精度,String、int则不会;这是为什么呢?

我们点开构造器方法看下源码:

public static long doubleToLongBits(double value) {
    long result = doubleToRawLongBits(value);
    // Check for NaN based on values of bit fields, maximum
    // exponent and nonzero significand.
    if ( ((result & DoubleConsts.EXP_BIT_MASK) ==
          DoubleConsts.EXP_BIT_MASK) &&
         (result & DoubleConsts.SIGNIF_BIT_MASK) != 0L)
        result = 0x7ff8000000000000L;
    return result;
}
复制代码

问题就处在 doubleToRawLongBits 这个方法上,在jdk中double类(float与int对应)中提供了double与long转换,doubleToRawLongBits就是将double转换为long,这个方法是原始方法(底层不是java实现,是c++实现的)。double之所以会出问题,是因为小数点转二进制丢失精度。BigDecimal在处理的时候把十进制小数扩大N倍让它在整数上进行计算,并保留相应的精度信息

image.png

总结

所以,在涉及到精度计算的过程中,我们尽量使用String类型来进行转换,正确用法如下:

BigDecimal bigDecimal2=new BigDecimal("8.8");
BigDecimal bigDecimal3=new BigDecimal("8.812");
System.out.println( bigDecimal2.compareTo(bigDecimal3));
System.out.println( bigDecimal2.add(bigDecimal3));
复制代码

BigDecimal创建出来的是对象,我们不能用传统的加减乘除对其进行运算,必须使用他的方法,在我们数据库存储里,如果我们使用的是double或者float类型,需要进行来回的转换后进行计算,非常不方便。

工具分享

所以在这里整理出一个util类供大家使用。


import java.math.BigDecimal;

/**

  • @Author shuaige

  • @Date 2022/4/17

  • @Version 1.0
    **/
    public class BigDecimalUtils {
    public static BigDecimal doubleAdd(double v1, double v2) {
    BigDecimal b1 = new BigDecimal(Double.toString(v1));
    BigDecimal b2 = new BigDecimal(Double.toString(v2));
    return b1.add(b2);
    }
    public static BigDecimal floatAdd(float v1, float v2) {
    BigDecimal b1 = new BigDecimal(Float.toString(v1));
    BigDecimal b2 = new BigDecimal(Float.toString(v2));
    return b1.add(b2);
    }
    public static BigDecimal doubleSub(double v1, double v2) {
    BigDecimal b1 = new BigDecimal(Double.toString(v1));
    BigDecimal b2 = new BigDecimal(Double.toString(v2));
    return b1.subtract(b2);
    }
    public static BigDecimal floatSub(float v1, float v2) {
    BigDecimal b1 = new BigDecimal(Float.toString(v1));
    BigDecimal b2 = new BigDecimal(Float.toString(v2));
    return b1.subtract(b2);
    }

    public static BigDecimal doubleMul(double v1, double v2) {
    BigDecimal b1 = new BigDecimal(Double.toString(v1));
    BigDecimal b2 = new BigDecimal(Double.toString(v2));
    return b1.multiply(b2);
    }
    public static BigDecimal floatMul(float v1, float v2) {
    BigDecimal b1 = new BigDecimal(Float.toString(v1));
    BigDecimal b2 = new BigDecimal(Float.toString(v2));
    return b1.multiply(b2);
    }

    public static BigDecimal doubleDiv(double v1, double v2) {
    BigDecimal b1 = new BigDecimal(Double.toString(v1));
    BigDecimal b2 = new BigDecimal(Double.toString(v2));
    // 保留小数点后两位 ROUND_HALF_UP = 四舍五入
    return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);
    }
    public static BigDecimal floatDiv(float v1, float v2) {
    BigDecimal b1 = new BigDecimal(Float.toString(v1));
    BigDecimal b2 = new BigDecimal(Float.toString(v2));
    // 保留小数点后两位 ROUND_HALF_UP = 四舍五入
    return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);
    }
    /**

    • 比较v1 v2大小
    • @param v1
    • @param v2
    • @return v1>v2 return 1 v1=v2 return 0 v1<v2 return -1
      */
      public static int doubleCompareTo(double v1, double v2) {
      BigDecimal b1 = new BigDecimal(Double.toString(v1));
      BigDecimal b2 = new BigDecimal(Double.toString(v2));
      return b1.compareTo(b2);
      }
      public static int floatCompareTo(float v1, float v2) {
      BigDecimal b1 = new BigDecimal(Float.toString(v1));
      BigDecimal b2 = new BigDecimal(Float.toString(v2));
      return b1.compareTo(b2);
      }
      }
      复制代码

来源:https://juejin.cn/post/7087404273503305736

标签:10,BigDecimal,事故,b2,v1,v2,toString,集锦,new
来源: https://www.cnblogs.com/konglxblog/p/16391845.html