面试官:BigDecimal 的坑你踩过哪些?

139 阅读4分钟

Java 的 BigDecimal 类提供了高精度的十进制计算功能,适用于需要进行高精度计算的场景。但是啊,这个东东的坑可是有点多的哦?

1.equals() 比较

BigDecimal 重写了equals,使用 equals 方法比较两个 BigDecimal 对象时,可能会得到不正确的结果。BigDecimal 类默认情况下使用精度和标度来比较两个对象,而不是数值本身。如果需要比较两个 BigDecimal 对象的数值是否相等,应该使用 compareTo 方法,或者使用 equals 方法并传入一个 MathContext 对象来指定精度和舍入模式。

public boolean equals(Object x) {
    if (!(x instanceof BigDecimal))
        return false;
    BigDecimal xDec = (BigDecimal) x;
    if (x == this)
        return true;
    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());
}

2.divide()运算

使用 BigDecimal 的 divide 方法进行除法运算时,需要注意除不尽的情况。如果除数不能整除被除数,且未指定舍入模式,那么将会抛出 ArithmeticException 异常。为了避免这种情况,应该使用带有舍入模式参数的 divide 方法,或者使用 divide 方法的重载版本,在除不尽的情况下指定舍入模式和保留小数位数。  

public static void failDivide(){
    BigDecimal dividend = new BigDecimal("10");
    BigDecimal divisor = new BigDecimal("3");
    BigDecimal result = dividend.divide(divisor); // 在除不尽的情况下,会抛出 ArithmeticException 异常
   
}

加上舍入模式参数

public static void successDivide(){
    BigDecimal dividend = new BigDecimal("10");
    BigDecimal divisor = new BigDecimal("3");
    BigDecimal result = dividend.divide(divisor, 2, RoundingMode.HALF_UP); // 保留两位小数,采用四舍五入方式
}

3.stripTrailingZeros

使用 BigDecimal 的 stripTrailingZeros 方法删除小数位末尾的零时,可能会得到不符合预期的结果。如果一个 BigDecimal 对象表示的数值不包含小数部分,stripTrailingZeros 方法将会返回一个值为零的 BigDecimal 对象,而不是原对象本身。因此,在使用 stripTrailingZeros 方法时,需要注意这种情况,并在必要时对结果进行特殊处理。

public static void main(String[] args) {
    BigDecimal num1 = new BigDecimal("100");
    BigDecimal num2 = new BigDecimal("2");
    BigDecimal result = num1.divide(num2); // 结果为 50
    result = result.stripTrailingZeros(); // 删除末尾的零,结果为 5E+1
    System.out.println(result);
}

4.精度问题

在使用 BigDecimal 的 setScale 方法设置小数位数时,需要注意选择合适的舍入模式。如果选择错误的舍入模式,可能会导致数值出现误差。常用的舍入模式包括 HALF_UP、HALF_DOWN、HALF_EVEN 等。需要根据实际需求选择合适的舍入模式。

RoundingMode.UP:向远离零的方向舍入。

RoundingMode.DOWN:向靠近零的方向舍入。

RoundingMode.CEILING:向正无穷大的方向舍入。

RoundingMode.FLOOR:向负无穷大的方向舍入。

RoundingMode.HALF_UP:四舍五入,如果舍弃部分>=0.5,就向正无穷大方向舍入,否则向负无穷大方向舍入。

RoundingMode.HALF_DOWN:五舍六入,如果舍弃部分>0.5,就向正无穷大方向舍入,否则向负无穷大方向舍入。

RoundingMode.HALF_EVEN:银行家舍入法,四舍六入,五分看奇偶性,如果是偶数,就向正无穷大方向舍入,否则向负无穷大方向舍入。

RoundingMode.UNNECESSARY:如果需要舍入,直接抛出 ArithmeticException 异常。

5.类型转换的问题

在使用 BigDecimal 进行运算时,需要注意数据类型的转换。如果将 BigDecimal 对象和其他数据类型直接进行运算,可能会导致数据类型转换错误,从而产生意想不到的结果。为了避免这种情况,应该使用 BigDecimal 提供的方法进行类型转换,例如使用 BigDecimal 的 intValue、longValue、doubleValue 等方法将 BigDecimal 转换为其他数据类型。

BigDecimal num1 = new BigDecimal("10");
BigDecimal num2 = new BigDecimal("3");
double result = num1.divide(num2).doubleValue(); // 在结果无法精确表示为 double 类型时,会出现数据类型转换错误

在上面的示例中,我们首先使用 BigDecimal 进行除法运算,然后将结果转换为 double 类型。然而,如果除法的结果无法精确表示为 double 类型,将会出现数据类型转换错误。

为了避免这种情况,我们可以在将 BigDecimal 转换为 double 类型之前,先判断其是否超出了 double 类型的范围,例如:

BigDecimal num1 = new BigDecimal("10");
BigDecimal num2 = new BigDecimal("3");
double result = num1.divide(num2, 10, RoundingMode.HALF_UP).doubleValue(); // 先保留足够的小数位数,再进行转换
if (result == Double.POSITIVE_INFINITY || result == Double.NEGATIVE_INFINITY) {
    throw new ArithmeticException("Result is too large to be represented as a double");
}

你们还遇到过哪些坑呢?