BigDecimal精度与相等比较的坑

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

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

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

//new 传进去一个double
BigDecimal newZero = new BigDecimal(0.0);
System.out.println(BigDecimal.ZERO.equals(newZero));

//new 传进去一个字符串
BigDecimal stringNewZero = new BigDecimal("0.0");
System.out.println(BigDecimal.ZERO.equals(stringNewZero));

//valueOf  传进去一个double
BigDecimal noScaleZero = BigDecimal.valueOf(0.0);
System.out.println(BigDecimal.ZERO.equals(noScaleZero));

//valueOf  传进去一个double,再手动设置精度为1
BigDecimal scaleZero = BigDecimal.valueOf(0.0).setScale(1);
System.out.println(BigDecimal.ZERO.equals(scaleZero));

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

true
false
false
false

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

public boolean equals(Object x) {
    //类型不同,直接返回false
    if (!(x instanceof BigDecimal))
        return false;
    BigDecimal xDec = (BigDecimal) x;
    //同一个对象,直接返回true
    if (x == this)
        return true;
    //精度不同,直接返回false!!
    if (scale != xDec.scale)
        return false;
    long s = this.intCompact;
    long xs = xDec.intCompact;
    if (s != INFLATED) {
        if (xs == INFLATED)
            xs = compactValFor(xDec.intVal);
        return xs == s;
    } else if (xs != INFLATED)
        return xs == compactValFor(this.intVal);

    return this.inflated().equals(xDec.inflated());
}

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

// Cache of common small BigDecimal values.
private static final BigDecimal zeroThroughTen[] = {
    new BigDecimal(BigInteger.ZERO,       0,  0, 1),
    new BigDecimal(BigInteger.ONE,        1,  0, 1),
    new BigDecimal(BigInteger.valueOf(2), 2,  0, 1),
    new BigDecimal(BigInteger.valueOf(3), 3,  0, 1),
    new BigDecimal(BigInteger.valueOf(4), 4,  0, 1),
    new BigDecimal(BigInteger.valueOf(5), 5,  0, 1),
    new BigDecimal(BigInteger.valueOf(6), 6,  0, 1),
    new BigDecimal(BigInteger.valueOf(7), 7,  0, 1),
    new BigDecimal(BigInteger.valueOf(8), 8,  0, 1),
    new BigDecimal(BigInteger.valueOf(9), 9,  0, 1),
    new BigDecimal(BigInteger.TEN,        10, 0, 2),
};


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

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

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

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

BigDecimal newZero = new BigDecimal(0.0);
System.out.println(BigDecimal.ZERO.compareTo(newZero));

BigDecimal stringNewZero = new BigDecimal("0.0");
System.out.println(BigDecimal.ZERO.compareTo(stringNewZero));

BigDecimal noScaleZero = BigDecimal.valueOf(0.0);
System.out.println(BigDecimal.ZERO.compareTo(noScaleZero));

BigDecimal scaleZero = BigDecimal.valueOf(0.0).setScale(1);
System.out.println(BigDecimal.ZERO.compareTo(scaleZero));

输出结果

0
0
0
0

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

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

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

坚持原创技术分享,您的支持将鼓励我继续创作!
0%