Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 484|回复: 0

[Java基础知识]Java BigDecimal详解

[复制链接]
  • TA的每日心情
    开心
    2021-3-12 23:18
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2014-9-30 17:39:27 | 显示全部楼层 |阅读模式
    一、引言
        借用《Effactive java》这本书中的话,float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。但是,货币计算往往要求结果精确,这时候可以使用int、long或BigDecimal。本文主要讲述BigDecimal使用过程中的一些陷阱、建议和技巧。 二、不可变性
    BigDecimal是不可变类,每一个操作(加减乘除等)都会返回一个新的对象, 下面以加法操作为例。
        BigDecimal a =new BigDecimal("1.22");
        System.out.println("construct with a String value: " + a);
        BigDecimal b =new BigDecimal("2.22");
        a.add(b);
        System.out.println("a plus b is : " + a);

    我们很容易会认为会输出:
       construct with a String value: 1.22
       a plus b is :3.44  但实际上a plus b is : 1.22

    下面我们就来分析一下加法操作的源码
    1. public BigDecimal add(BigDecimal augend) {
    2.     long xs =this.intCompact; //整型数字表示的BigDecimal,例a的intCompact值为122
    3.     long ys = augend.intCompact;//同上
    4.     //初始化BigInteger的值,intVal为BigDecimal的一个BigInteger类型的属性
    5.     BigInteger fst = (this.intCompact !=INFLATED) ?null :this.intVal;
    6.     BigInteger snd =(augend.intCompact !=INFLATED) ?null : augend.intVal;
    7.     int rscale =this.scale;//小数位数
    8.     long sdiff = (long)rscale - augend.scale;//小数位数之差
    9.     if (sdiff != 0) {//取小数位数多的为结果的小数位数
    10.      if (sdiff < 0) {
    11.          int raise =checkScale(-sdiff);
    12.          rscale =augend.scale;
    13.          if (xs ==INFLATED ||(xs = longMultiplyPowerTen(xs,raise)) ==INFLATED)
    14.            fst =bigMultiplyPowerTen(raise);
    15.          }else {
    16.            int raise =augend.checkScale(sdiff);
    17.            if (ys ==INFLATED ||(ys =longMultiplyPowerTen(ys,raise)) ==INFLATED)
    18.              snd = augend.bigMultiplyPowerTen(raise);
    19.          }
    20.       }
    21.     if (xs !=INFLATED && ys !=INFLATED) {
    22.        long sum = xs + ys;
    23.        if ( (((sum ^ xs) &(sum ^ ys))) >= 0L)//判断有无溢出
    24.          //返回使用BigDecimal的静态工厂方法得到的BigDecimal实例
    25.          return BigDecimal.valueOf(sum,rscale);
    26.     }
    27.     if (fst ==null)
    28.         fst =BigInteger.valueOf(xs);//BigInteger的静态工厂方法
    29.     if (snd ==null)
    30.         snd =BigInteger.valueOf(ys);
    31.     BigInteger sum =fst.add(snd);
    32.     //返回通过其他构造方法得到的BigDecimal对象
    33.     return (fst.signum == snd.signum) ?new BigDecimal(sum,INFLATED, rscale, 0) :
    34.       new BigDecimal(sum,compactValFor(sum),rscale, 0);
    35. }
    复制代码
       因为BigInteger与BigDecimal都是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,所以a.add(b)虽然做了加法操作,但是a并没有保存加操作后的值,正确的用法应该是a=a.add(b); 减乘除操作也是一样的返回一个新的BigDecimal对象。

    三、构造函数和valueOf方法
    首先看如下一段代码:

      // use constructor BigDecimal(double)
      BigDecimal aDouble =new BigDecimal(1.22);
      System.out.println("construct with a double value: " + aDouble);   // use constructor BigDecimal(String)
      BigDecimal aString = new BigDecimal("1.22");
      System.out.println("construct with a String value: " + aString);   // use constructor BigDecimal.valueOf(double)
      BigDecimal aValue = BigDecimal.valueOf(1.22);
      System.out.println("use valueOf method: " + aValue);

    你认为输出结果会是什么呢?如果你认为第一个会输出1.22,那么恭喜你答错了,输出结果如下:
    construct with a double value: 1.2199999999999999733546474089962430298328399658203125
    construct with a String value: 1.22
    use valueOf method: 1.22  为什么会这样呢?JavaDoc对于BigDecimal(double)有很详细的说明:
    1、参数类型为double的构造方法的结果有一定的不可预知性。有人可能认为在Java中new BigDecimal(0.1)所创建的BigDecimal的值正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。

    2、另一方面,String 构造方法是完全可预知的:new BigDecimal("0.1") 将创建一个 BigDecimal,它的值正好等于期望的0.1。因此,比较而言,通常建议优先使用String构造方法。

    3、当 double 必须用作BigDecimal的来源时,请注意,此构造方法提供了一个精确转换;它不提供与以下操作相同的结果:先使用Double.toString(double)方法将double转换为String,然后使用BigDecimal(String)构造方法。要获取该结果,使用static valueOf(double)方法。 ?? BigDecimal.valueOf(double) 使用由 Double.toString(double)方法提供的 double的标准化字符串表示形式( canonical string representation) 将 double 转换成 BigDecimal 。这也是比较推荐的一种方式。
    ??
    BigDecimal.valueOf(double)还有一个重载的方法 BigDecimal.valueOf(long),对于某些常用值(0到10) BigDecimal在内部做了缓存, 如果传递的参数值范围为[0, 10], 这个方法直接返回缓存中相应的BigDecimal对象。

    java源码如下:
    1. /**
    2. * Translates a {@code long} value into a {@code BigDecimal}
    3. * with a scale of zero. This {@literal "static factory method"}
    4. * is provided in preference to a ({@code long}) constructor
    5. * because it allows for reuse of frequently used
    6. * {@code BigDecimal} values.
    7. *
    8. * @param val value of the {@code BigDecimal}.
    9. * @return a {@code BigDecimal} whose value is {@code val}.
    10. */
    11.     public static BigDecimal valueOf(long val) {
    12.     if (val >= 0 && val < zeroThroughTen.length)
    13.         return zeroThroughTen[(int)val];
    14.     else if (val != INFLATED)
    15.         return new BigDecimal(null, val, 0, 0);
    16.     return new BigDecimal(INFLATED_BIGINT, val, 0, 0);
    17. }
    18. // Cache of common small BigDecimal values.
    19. private static final BigDecimal zeroThroughTen[] = {
    20.     new BigDecimal(BigInteger.ZERO, 0, 0, 1),
    21.     new BigDecimal(BigInteger.ONE, 1, 0, 1),
    22.     new BigDecimal(BigInteger.valueOf(2), 2, 0, 1),
    23.     new BigDecimal(BigInteger.valueOf(3), 3, 0, 1),
    24.     new BigDecimal(BigInteger.valueOf(4), 4, 0, 1),
    25.     new BigDecimal(BigInteger.valueOf(5), 5, 0, 1),
    26.     new BigDecimal(BigInteger.valueOf(6), 6, 0, 1),
    27.     new BigDecimal(BigInteger.valueOf(7), 7, 0, 1),
    28.     new BigDecimal(BigInteger.valueOf(8), 8, 0, 1),
    29.     new BigDecimal(BigInteger.valueOf(9), 9, 0, 1),
    30.     new BigDecimal(BigInteger.TEN, 10, 0, 2),
    31. };
    复制代码
    附上相应的测试代码:
      BigDecimal a1 = BigDecimal.valueOf(10);
       BigDecimal a2 = BigDecimal.valueOf(10);
       System.out.println(a1 == a2); // true

       BigDecimal a3 = BigDecimal.valueOf(11);
       BigDecimal a4 = BigDecimal.valueOf(11);
       System.out.println(a3 == a4); // false

    四、equals方法
       BigDecimal.equals方法是有问题的.仅当你确定比较的值有着相同的标度时才可使用. 因此,当你校验相等性时注意 - BigDecimal有一个标度,用于相等性比较. 而compareTo方法则会忽略这个标度(scale).  BigDecimal的equals方法源码如下:
    1. @Override
    2. public boolean equals(Object x) {
    3.     // 必须是BigDecimal实例
    4.     if (!(x instanceof BigDecimal))
    5.         return false;
    6.     BigDecimal xDec = (BigDecimal) x;
    7.     if (x == this)
    8.         return true;
    9.     // 标度必须相同
    10.     if (scale != xDec.scale)
    11.         return false;
    12.     long s = this.intCompact;
    13.     long xs = xDec.intCompact;
    14.     if (s != INFLATED) {
    15.         if (xs == INFLATED)
    16.             xs = compactValFor(xDec.intVal);
    17.         return xs == s;
    18.     } else if (xs != INFLATED)
    19.         return xs == compactValFor(this.intVal);
    20.     return this.inflated().equals(xDec.inflated());
    21. }
    复制代码
    参见以下测试代码:
    // 打印false
      System.out.println(new BigDecimal("0.0").equals(new BigDecimal("0.00")));

      // 打印false
    System.out.println(new BigDecimal("0.0").hashCode() == (new BigDecimal("0.00")).hashCode());  // 打印0
    System.out.println(new BigDecimal("0.0").compareTo(new BigDecimal("0.00")));

    五、对除法使用标度
       BigDecimal对象的精度没有限制。如果结果不能终止,divide方法将会抛出ArithmeticException, 如1 / 3 = 0.33333...。所以强烈推荐使用重载方法divide(BigDecimal d, int scale, int roundMode)指定标度和舍入模式来避免以上异常。
    参见以下测试代码:

      //java.lang.ArithmeticException: Non-terminating decimal expansion;
       //no exact representable decimal result.
       try {
        BigDecimal.valueOf(1).divide(BigDecimal.valueOf(3));
       } catch (ArithmeticException ex) {
         System.out.println(ex.getMessage());
       }

    // always use a scale and the rounding mode of your choice
    // 0.33
    System.out.println(BigDecimal.valueOf(1).divide(BigDecimal.valueOf(3), 2, BigDecimal.ROUND_HALF_UP));

    六、总结
    (1)商业计算使用BigDecimal。  (2)使用参数类型为String的构造函数,将double转换成BigDecimal时用BigDecimal.valueOf(double),做除法运算时使用重载的方法divide(BigDecimal d, int scale, int roundMode)。  (3)BigDecimal是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,所以在做加减乘除运算时千万要保存操作后的值。  (4)尽量使用compareTo方法比较两个BigDecimal对象的大小。
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-20 17:28 , Processed in 0.458194 second(s), 48 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表