zoukankan      html  css  js  c++  java
  • BigDecimal精度与相等比较的坑

    先想一下,创建BigDecimal对象的时候一般是怎么创建的?

    • new一个,传进去值
    • BigDecimal.valueOf方法,传进去值

    作为一个数字类型,经常有的操作是比较大小,有一种情况是比较是否相等。用equal方法还是compareTo方法?这里就是一个大坑

     1 //new 传进去一个double
     2 BigDecimal newZero = new BigDecimal(0.0);
     3 System.out.println(BigDecimal.ZERO.equals(newZero));
     4  
     5 //new 传进去一个字符串
     6 BigDecimal stringNewZero = new BigDecimal("0.0");
     7 System.out.println(BigDecimal.ZERO.equals(stringNewZero));
     8  
     9 //valueOf  传进去一个double
    10 BigDecimal noScaleZero = BigDecimal.valueOf(0.0);
    11 System.out.println(BigDecimal.ZERO.equals(noScaleZero));
    12  
    13 //valueOf  传进去一个double,再手动设置精度为1
    14 BigDecimal scaleZero = BigDecimal.valueOf(0.0).setScale(1);
    15 System.out.println(BigDecimal.ZERO.equals(scaleZero));

    用于比较的值全都是0,猜一猜上面几个equals方法返回的结果是什么?全都是true?no no no...

    true
    false
    false
    false

    惊不惊喜,意不意外?原因是什么呢?看一下BigDecimal的equals方法的实现:

     1 public boolean equals(Object x) {
     2     //类型不同,直接返回false
     3     if (!(x instanceof BigDecimal))
     4         return false;
     5     BigDecimal xDec = (BigDecimal) x;
     6     //同一个对象,直接返回true
     7     if (x == this)
     8         return true;
     9     //精度不同,直接返回false!!
    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  
    21     return this.inflated().equals(xDec.inflated());
    22 }

    从前面三个简单的判断就可以看出来,debug跟一下就知道是上面equals方法有三个返回false,都是因为精度不同。那么BigDecimal.ZERO的精度是多少呢?看下源码:

     1 // Cache of common small BigDecimal values.
     2 private static final BigDecimal zeroThroughTen[] = {
     3     new BigDecimal(BigInteger.ZERO,       0,  0, 1),
     4     new BigDecimal(BigInteger.ONE,        1,  0, 1),
     5     new BigDecimal(BigInteger.valueOf(2), 2,  0, 1),
     6     new BigDecimal(BigInteger.valueOf(3), 3,  0, 1),
     7     new BigDecimal(BigInteger.valueOf(4), 4,  0, 1),
     8     new BigDecimal(BigInteger.valueOf(5), 5,  0, 1),
     9     new BigDecimal(BigInteger.valueOf(6), 6,  0, 1),
    10     new BigDecimal(BigInteger.valueOf(7), 7,  0, 1),
    11     new BigDecimal(BigInteger.valueOf(8), 8,  0, 1),
    12     new BigDecimal(BigInteger.valueOf(9), 9,  0, 1),
    13     new BigDecimal(BigInteger.TEN,        10, 0, 2),
    14 };
    15  
    16  
    17 /**
    18  * The value 0, with a scale of 0.
    19  *
    20  * @since  1.5
    21  */
    22 public static final BigDecimal ZERO = zeroThroughTen[0];

    BigDecimal.ZERO值为0,精度为0.

    而上面几种返回false的case,都是因为精度不同。精度不同的原因,则是BigDecimal对象初始化的方式不同,从源码上看,前三种初始化的方式都不同。

    所以说,BigDecimal比较大小,还是用compareTo方法比较靠谱,改为compareTo之后,上面四个case返回的结果都是相等:

     1 BigDecimal newZero = new BigDecimal(0.0);
     2 System.out.println(BigDecimal.ZERO.compareTo(newZero));
     3  
     4 BigDecimal stringNewZero = new BigDecimal("0.0");
     5 System.out.println(BigDecimal.ZERO.compareTo(stringNewZero));
     6  
     7 BigDecimal noScaleZero = BigDecimal.valueOf(0.0);
     8 System.out.println(BigDecimal.ZERO.compareTo(noScaleZero));
     9  
    10 BigDecimal scaleZero = BigDecimal.valueOf(0.0).setScale(1);
    11 System.out.println(BigDecimal.ZERO.compareTo(scaleZero));

    输出结果

    0
    0
    0
    0

    由此联想到的一个更大的坑是,如果将BigDecimal的值作为HashMap的key,因为精度的问题,相同的值就可能出现hashCode值不同并且equals方法返回false,导致put和get就很可能会出现相同的值但是存取了不同的value。

    再想一想,小数类型在计算机中本来就不能精确存储,再把其作为HashMap的key就相当不靠谱了,以后还是少用。

    另外需要注意的一点是,写代码调别人写的方法时,最好是点进去看一下实现。再小再常用的方法,都可能埋着大坑

  • 相关阅读:
    Linq 入门系列 [Take,Skip,TakeWhile,SkipWhile]篇
    SqlString 引发的思考
    DLINQ
    Wrf 格式播放器
    仙剑奇侠传4序列号
    Asp.Net程序性能 浅谈
    Linq 扩展函数的应用
    正则表达式积累
    ajax 之取消服务器任务[转]
    Linq 演变的过程(delegate => Lamb => Linq)
  • 原文地址:https://www.cnblogs.com/z941030/p/9639717.html
Copyright © 2011-2022 走看看