暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

错误使用BigDecimal也会造成精度丢失

风尘博客 2020-02-09
1418

一、BigDecimal
的产生背景

首先我们先来看如下代码示例:

    @Test
    public void countDemo() {
    System.out.println(0.06+0.01);
    System.out.println(1.0-0.42);
    System.out.println(4.015*100);
    System.out.println(303.1/1000);
    }

    结果如下

      0.06999999999999999
      0.5800000000000001
      401.49999999999994
      0.30310000000000004

      问题在哪里呢?原因在于我们的计算机是二进制的。浮点数没有办法用二进制进行精确表示。

      Java
      float
      只能用来进行科学计算或工程计算,在大多数的商业计算中,一般采用java.math.BigDecimal
      类来进行精确计算。

      二、正确运用BigDecimal

      2.1 BigDecimal
      常量

      BigDecimal
      定义了几个常用的值,0
      1
      10
      ,静态的,可以通过类名直接引用如:BigDecimal.ZERO

        /**
        * The value 0, with a scale of 0.
        *
        * @since 1.5
        */
        public static final BigDecimal ZERO =
        zeroThroughTen[0];


        /**
        * The value 1, with a scale of 0.
        *
        * @since 1.5
        */
        public static final BigDecimal ONE =
        zeroThroughTen[1];


        /**
        * The value 10, with a scale of 0.
        *
        * @since 1.5
        */
        public static final BigDecimal TEN =
        zeroThroughTen[10];

        2.2 构造方法

          BigDecimal a = new BigDecimal();

          BigDecimal一共有4个构造方法

          1.BigDecimal(int)
           创建一个具有参数所指定整数值的对象;
          2.BigDecimal(double)
           创建一个具有参数所指定双精度值的对象;
          3.BigDecimal(long)
           创建一个具有参数所指定长整数值的对象;
          4.BigDecimal(String)
           创建一个具有参数所指定以字符串表示的数值的对象。

          2.3 BigDecimal
          加减乘除

          BigDecimal
          所创建的是对象,不能使用传统的+
          -
          *
          /
          等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。

            public BigDecimal add(BigDecimal value);                        //加法
            public BigDecimal subtract(BigDecimal value); //减法
            public BigDecimal multiply(BigDecimal value); //乘法
            public BigDecimal divide(BigDecimal value); //除法

            2.4 BigDecimal
            运算

            在使用BigDecimal
            类来进行计算的时候,主要三个步骤:

            1.float
            或者double
            变量构建BigDecimal
            对象。
            2.通过调用BigDecimal
            的加,减,乘,除等相应的方法进行算术运算。
            3.BigDecimal
            对象转换成float
            double
            int
            等类型。

            在一般开发过程中,我们数据库中存储的数据都是float
            double
            类型的。在进行拿来拿去运算的时候还需要不断的转化,这样十分的不方便。下面是一个工具类,该工具类提供加,减,乘,除运算。

              public class BigDecimalUtil {


              /**
              * 默认除法运算精度
              */
              private static final int DEF_DIV_SCALE = 10;


              /**
              * 提供精确的加法运算。
              * @param v1 被加数
              * @param v2 加数
              * @return 两个参数的和
              */
              public static double add(double v1,double v2){
              BigDecimal b1 = new BigDecimal(Double.toString(v1));
              BigDecimal b2 = new BigDecimal(Double.toString(v2));
              return b1.add(b2).doubleValue();
              }


              /**
              * 提供精确的减法运算。
              * @param v1 被减数
              * @param v2 减数
              * @return 两个参数的差
              */
              public static double subtract(double v1,double v2){
              BigDecimal b1 = new BigDecimal(Double.toString(v1));
              BigDecimal b2 = new BigDecimal(Double.toString(v2));
              return b1.subtract(b2).doubleValue();
              }


              /**
              * 提供精确的乘法运算。
              * @param v1 被乘数
              * @param v2 乘数
              * @return 两个参数的积
              */
              public static double multiply(double v1,double v2){
              BigDecimal b1 = new BigDecimal(Double.toString(v1));
              BigDecimal b2 = new BigDecimal(Double.toString(v2));
              return b1.multiply(b2).doubleValue();
              }


              /**
              * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
              * 小数点以后10位,以后的数字四舍五入。
              * @param v1 被除数
              * @param v2 除数
              * @return 两个参数的商
              */
              public static double div(double v1,double v2){
              return div(v1,v2,DEF_DIV_SCALE);
              }


              /**
              * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
              * 定精度,以后的数字四舍五入。
              * @param v1 被除数
              * @param v2 除数
              * @param scale 表示表示需要精确到小数点以后几位。
              * @return 两个参数的商
              */
              public static double div(double v1,double v2,int scale){
              if(scale<0){
              throw new IllegalArgumentException("The scale must be a positive integer or zero");
              }
              BigDecimal b1 = new BigDecimal(Double.toString(v1));
              BigDecimal b2 = new BigDecimal(Double.toString(v2));
              return b1.divide(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
              }


              /**
              * 提供精确的小数位四舍五入处理。
              * @param v 需要四舍五入的数字
              * @param scale 小数点后保留几位
              * @return 四舍五入后的结果
              */
              public static double divide(double v,int scale){
              //如果精确范围小于0,抛出异常信息
              if(scale<0){
              throw new IllegalArgumentException("The scale must be a positive integer or zero");
              }
              BigDecimal b = new BigDecimal(Double.toString(v));
              BigDecimal one = new BigDecimal("1");
              return b.divide(one,scale, BigDecimal.ROUND_HALF_UP).doubleValue();
              }
              }

              测试

                @Test
                public void bigDecimalUtilTest(){
                // 0.06
                System.out.println(BigDecimalUtil.add(0.05, 0.01));
                // 0.58
                System.out.println(BigDecimalUtil.subtract(1.0, 0.42));
                // 401.5
                System.out.println(BigDecimalUtil.multiply(4.015, 100));
                // 1.233
                System.out.println(BigDecimalUtil.div(123.3, 100));
                // 4.02
                System.out.println(BigDecimalUtil.divide(4.015, 2));
                }

                打印结果

                  0.06
                  0.58
                  401.5
                  1.233
                  4.02

                  2.5 比较BigDecimal

                  在比较两个BigDecimal
                  的值是否相等时,必须使用compareTo()
                  方法来比较,它根据两个值的大小分别返回负数、正数和0
                  ,分别表示小于、大于和等于。

                    @Test
                    public void compareDecimal() {
                    BigDecimal v1 = new BigDecimal("1.2");
                    BigDecimal v2 = new BigDecimal("1.20");
                    // 0
                    System.out.println(v1.compareTo(v2));
                    // false
                    System.out.println(v1.equals(v2));
                    }

                    三、BigDecimal
                    精度也丢失

                    仔细的你一定发现了,在工具类中使用BigDecimal
                    中,使用它的BigDecimal(String)
                    构造器创建对象。因为其他的如BigDecimal b = new BigDecimal(1)
                    这种,还是会发生精度丢失的问题。

                    示例

                      @Test
                      public void precisionLose() {
                      BigDecimal a = new BigDecimal(1.01);
                      BigDecimal b = new BigDecimal(1.02);
                      BigDecimal c = new BigDecimal("1.01");
                      BigDecimal d = new BigDecimal("1.02");
                      // 2.0300000000000000266453525910037569701671600341796875
                      System.out.println(a.add(b));
                      // 2.03
                      System.out.println(c.add(d));
                      }

                      结果

                        2.0300000000000000266453525910037569701671600341796875
                        2.03

                        四、总结

                        1.BigDecimal
                        用于表示精确的小数,常用于财务计算;
                        2.比较BigDecimal
                        的值是否相等,必须使用compareTo()
                        而不能使用equals()

                        示例代码

                        Github 示例代码[1]

                        文中链接

                        [1]
                         Github 示例代码: https://github.com/vanDusty/JDK/tree/master/JDK-5/src/main/java/cn/van/jdk/five/generic/bigdecimal


                        文章转载自风尘博客,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                        评论