BigDecimal计算的这些坑,让我的程序产生难以想象的Bug

2022-05-05 19:25:32 浏览数 (3)

通常我们在金融、科学等场景,会使用BigDecimal。然而如果我们不注意BigDecimal的精度问题,计算结果偏差可能会很大,最终会产生难以想象的Bug。

1意想不到的加减乘除

代码语言:javascript复制
@Test
public void test3() {
    BigDecimal param1 = new BigDecimal(0.1);
    BigDecimal param2 = new BigDecimal(0.2);
    //加法
    BigDecimal add = param1.add(param2);
    System.out.println("加法:"   add);

    //减法
    BigDecimal subtract = param2.subtract(param1);
    System.out.println("减法:"   subtract);

    //乘法
    BigDecimal multiply = param1.multiply(param2);
    System.out.println("乘法:"   multiply);

    //除法
    BigDecimal divide = param2.divide(param1);
    System.out.println("除法:"   divide);
}

运行结果

程序的结果并不是我们想要的结果。这里实际是将数值做了二进制转换存在计算机中,取出时又转成了10进制,导致值产生了变化。

改进方法如下:

我们将

代码语言:javascript复制
BigDecimal param1 = new BigDecimal(0.1);
BigDecimal param2 = new BigDecimal(0.2);

更改为

代码语言:javascript复制
BigDecimal param1 = new BigDecimal("0.1");
BigDecimal param2 = new BigDecimal("0.2");

运行结果

所以我们在使用 BigDecimal 表示和计算浮点数时,一定要使用字符串的构造方法来初始化 BigDecimal

2除法运算记得保留小数点位数

代码语言:javascript复制
@Test
public void test5() {
    BigDecimal bParam = new BigDecimal("1");
    BigDecimal bParam2 = new BigDecimal("3");
    BigDecimal divide = bParam.divide(bParam2);
    System.out.println(divide);
}

如上代码我们1除以3时结果会是无线循环小数,运行结果如下:

直接抛出异常了。为此,在使用BigDecimal做除法运算时,我们记得保留小数点位数。

代码语言:javascript复制
@Test
public void test5() {
    BigDecimal bParam = new BigDecimal("1");
    BigDecimal bParam2 = new BigDecimal("3");
    BigDecimal divide = bParam.divide(bParam2, 3, BigDecimal.ROUND_HALF_EVEN);
    System.out.println(divide);
}

运行结果为:

3烦人的小数点

如果你有一个Double类型的数字,还有一个BigDecimal类型的数字,现在我要对这两个数字做乘法:

代码语言:javascript复制
Double dParam = 100d;
BigDecimal bParam = new BigDecimal("0.02");
BigDecimal toBigDecimal = new BigDecimal(dParam.toString());
BigDecimal multiply = bParam.multiply(toBigDecimal);

如上代码,我将Double类型的100dBigDecimal类型的0.02相乘,我期望得到值为2.00。我按照上面的方法做乘法后,得到的数为

小数点后面居然有3个0,想当然不是我想要的。

实际上我们只要做做小改动:

代码语言:javascript复制
@Test
public void test4() {
    Double dParam = 100d;
    BigDecimal bParam = new BigDecimal("0.02");
    BigDecimal toBigDecimal = new BigDecimal(dParam.toString());
    BigDecimal multiply = bParam.multiply(toBigDecimal);
    System.out.println(multiply.setScale(2, BigDecimal.ROUND_HALF_EVEN));
}

运算结果:

大部分业务需求会保留小数位数,我们可以在结果中设置小数位数即可。

4别用equals判断

我们在对包装类进行比较时,通常使用equals比较值,但是在BigDecimal中使用equals可能就不会得到我们想要的结果。

代码语言:javascript复制
@Test
public void test6() {
    System.out.println(new BigDecimal("1.0").equals(new BigDecimal("1")));
}

返回结果为:

扒开这里的equals源码我们发现equals比较的是BigDecimalvaluescale,即比较了值和小数点位数。我们往往只需要比较值,那么这里可以使用compareTo方法:

代码语言:javascript复制
@Test
public void test6() {
    System.out.println(new BigDecimal("1.0").compareTo(new BigDecimal("1")));
}

运行结果:

0代表的就是相等了。

这篇文章就写到这里了,如果对你有用,欢迎转发。

1 人点赞