Java中BigDecimal的几个坑

Some Problems of BigDecimal in Java

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 的构造函数:

BigDecimal参数类型为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的文档里针对这些问题都给开发者提供了明显的标注,只要在使用时多读文档和源码、多思考、多实验,这些问题也可以很好地避开。

文章评论
${fromAuthor ? '郄正元' : '游客'} 作者 ${gmtCreate}
${content}
${subList.length}
发表评论
${commentToArticle ? '' : parentContent}
字数:0/${maxCommentLength}
该邮箱地址仅用于接收其他用户的回复提醒,不会泄露