Java为我们提供的int、float等几种基本数值类型,基本能够胜任很多业务场景。但是在某些需要较高精度的系统中,我们必须使用更高精度的数值类型,其中最常用的就是BigDecimal。然而,在实际使用中,这个类有一些我们稍不注意就会踩到的坑,下面我们就来总结一下:
计算后忘记赋值
BigDecimal的数值在实例初始化之后就是不可变的(类似于String类型),其中的所有计算函数都不会改变实例本身的值,而是会返回一个新的BigDecimal实例,示例如下:
BigDecimal a = new BigDecimal(1995);
a.add(BigDecimal.TEN);// 加10
System.out.println(a);// 打印出来的结果是1995
为了获取正确的计算结果,必须在计算后将函数的返回值赋值到原来的变量上,或者赋值到一个新的变量上:
BigDecimal a = new BigDecimal(1995);
a = a.add(BigDecimal.TEN);// 加10,并赋值到原来的变量上
System.out.println(a);// 打印出来的结果是2005
BigDecimal b = a.add(BigDecimal.TEN);// 加10,并赋值到新的变量上
System.out.println(b);// 打印出来的结果是2015
使用除法没有设定精度和舍入模式
这是一个很常见的问题,由于除法的结果存在除不尽的情况,而BigDecimal无法表示这种数值,因此在使用BigDecimal的除法时必须指定所需结果的精度和舍入模式,否则就会抛出 ArithmeticException
异常:
// 下面代码会抛出异常:Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
BigDecimal.TEN.divide(new BigDecimal(3));// 10除以3
正确的做法是为 divide
函数指定一个精度和舍入模式,以下方法都可以使用:
public BigDecimal divide(BigDecimal divisor, int roundingMode)
public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
public BigDecimal divide(BigDecimal divisor, RoundingMode roundingMode)
例如使用第3个函数,计算10除以3:
BigDecimal result = BigDecimal.TEN.divide(new BigDecimal(3), 4, RoundingMode.HALF_UP);// 10除以3,四舍五入,并保留4位小数
System.out.println(result);// 打印出来的结果是3.3333
使用有精度问题的构造函数
我们在项目中使用BigDecimal,就是看中了它的高精度,但是在通过构造函数创建实例时,有可能从一开始就踩到精度的坑里:
BigDecimal a = new BigDecimal(1.1);
System.out.println(a);// 打印出来的结果是1.100000000000000088817841970012523233890533447265625
😭真是出师未捷身先死,究其原因,是由于我们使用了参数类型为 double
的构造函数:
如上图所示,在这个构造函数的文档中,很明确的告诉我们它是有精度问题的,因为计算机在表示 double
类型的数值时本身就是不准确的,因此得到的BigDecimal实例也和我们想要的数值有误差。
文档中也告诉了我们,最好使用参数类型为 String
的构造函数来获得精确的浮点数:
BigDecimal a = new BigDecimal("1.1");
System.out.println(a);// 打印出来的结果是1.1
用错误的方式比较两个数值是否相等
有时候,我们需要判断两个BigDecimal的数值是否相等,错误地使用了 equals
函数:
System.out.println(new BigDecimal("1.0").equals(new BigDecimal("1.00")));// 打印出来的结果是false
这是由于BigDecimal的 equals
函数不仅会比较两个实例的数值是否相等,还会比较它们的精度。所以 equals
函数不适合比较两个BigDecimal的数值,应该使用 compareTo
函数:
boolean equalInValue = new BigDecimal("1.0").compareTo(new BigDecimal("1.00")) == 0;
System.out.println(equalInValue);// 打印出来的结果是true
如果 compareTo
函数返回的结果为0,就表示两个数值相等。
总结
这些就是在使用Java中的BigDecimal类时需要特殊注意的一些地方,很多时候稍不注意就会踩到这些坑里。实际上,BigDecimal的文档里针对这些问题都给开发者提供了明显的标注,只要在使用时多读文档和源码、多思考、多实验,这些问题也可以很好地避开。