BigDecimal使用注意避坑

BigDecimal精度问题与正确使用指南
文章讨论了BigDecimal在Java中处理高精度计算的重要性,以及在初始化、除法运算和比较时可能遇到的精度问题。初始化BigDecimal时,直接从float或double转换可能导致精度丢失,应使用valueOf()或转换为字符串。进行除法运算时需设定精度,否则会抛出异常。同时,不应使用equals方法比较BigDecimal大小,而应使用compareTo。

在java.math包中提供了对大数字的操作类,用于进行高精确计算,如BigInteger,BigDecimal类。而平常我们开发中使用最多的float和double只能适用于一般的科学和工程计算,如果要在比较精确的计算方面如货币,那么使用float和double会相应的丢失精度,因此用于精密计算大数字的类BigDecimal就必不可少了。所以BigDecimal适合商业计算场景,用来对超过16位有效位的数进行精确的运算。但是BigDecimal的使用并不像float和double那样,使用不当造成的后果更严重,下面就来看下我们项目中踩过BigDecimal的坑:

一. BigDecimal的初始化精度丢失问题

先来看下面代码的运行结果:

BigDecimal bd1 = new BigDecimal(0.1);
System.out.println("bd1="+bd1);
BigDecimal bd2 = new BigDecimal("0.1");
System.out.println("bd2="+bd2);
BigDecimal bd3 = BigDecimal.valueOf(0.1);
System.out.println("bd3="+bd3);

输出结果:

bd1=0.1000000000000000055511151231257827021181583404541015625
bd2=0.1
bd3=0.1

如果是float或double类型转Bigdecimal,不要使用new BigDecimal()转, 使用valueOf()方法 或 new BigDecimal(“”)转成string,否则有可能出现精度问题。

《Effective Java》这本书里说过:
如果需要精确的答案,请避免使用float和double

因为float和double执行的是二进制浮点运算,二进制有些情况下不能准确的表示一个小数,就像十进制不能准确的表示1/3(1/3=0.3333…)也就是说二进制表示小数的时候只能够表示能够用1/(2^n)的和的任意组合,例如:

  • 0.5能够表示,因为它可以表示成为1/2
  • 0.75也能够表示,因为它可以表示成为1/2+1/(2^2)
  • 0.875也能够表示,因为它可以表示成为1/2+1/(22)+1/(23)
  • 但是0.1不能够精确表示,因为它不能够表示成为1/(2^n)的和的形式
System.out.println(0.5*3);
System.out.println(0.1*3);

大家可以本地执行下这两行代码,看下输出结果就知道为什么二进制不能表示0.1却可以表示0.5了。所以其实不是BigDecimal的问题,BigDecimal就是为了满足精确运算存在的,问题出在0.1它本身就一个不准确的值,这其实跟BigDecimal无关,但在使用的时候需要注意用法。

二. BigDecimal在进行除法运算时需设置精度,否则对于除不尽的情况会抛出异常

继续看下面的代码执行结果:

BigDecimal bd4 = new BigDecimal("10");
BigDecimal bd5 = new BigDecimal("3");
System.out.println(bd4.divide(bd5));

输出结果:

Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
at java.math.BigDecimal.divide(BigDecimal.java:1690)
at BigDecimalTest.main(BigDecimalTest.java:38)

应该向下面这样设置小数点后的位数,以及超出后是四舍五入和向上/向下取整或者直接舍弃:

System.out.println(bd4.divide(bd5,2,BigDecimal.ROUND_DOWN));

第二个参数表示小数位数,第三个参数表示超出的位数直接舍弃(当然也可以设置四舍五入,向上取整等)

三. 不要使用BigDecimal的equals方法比较大小, 否则可能会因为精度问题导致比较结果和预期的不一致

BigDecimal bd1 = new BigDecimal("0");
BigDecimal bd2 = new BigDecimal("0.0");
System.out.println(bd1.equals(bd2));
System.out.println(bd1.compareTo(bd2) == 0)

输出结果:

equals:false
compareTo:true

如果你无法确定你的BigDecimal值有小数情况,最好用compareTo!

要让两个 `BigDecimal` 相加,必须使用其 `.add()` 方法,并将结果重新赋值给一个变量,因为 **`BigDecimal` 是不可变类(immutable)**,它的任何数学运算都不会修改原对象。 --- ## ✅ 正确的相加方式 ```java import java.math.BigDecimal; BigDecimal a = new BigDecimal("10.50"); BigDecimal b = new BigDecimal("20.30"); BigDecimal result = a.add(b); System.out.println(result); // 输出: 30.80 ``` --- ### 🔍 关键点解释: - `a.add(b)`:返回一个新的 `BigDecimal` 对象,表示 `a + b` - `a` 和 `b` 本身没有被修改(不可变性) - 必须用一个变量接收返回值,否则结果“丢失” ❌ 错误写法(常见误区): ```java a.add(b); // ❌ 结果没保存,a 的值仍然是原来的 ``` --- ## ✅ 完整示例代码 ```java import java.math.BigDecimal; public class BigDecimalAddExample { public static void main(String[] args) { BigDecimal num1 = BigDecimal.valueOf(100.25); BigDecimal num2 = new BigDecimal("75.75"); BigDecimal sum = num1.add(num2); System.out.println("相加结果: " + sum); // 输出: 176.00 } } ``` > ⚠️ 注意:推荐使用 `new BigDecimal("字符串")` 或 `BigDecimal.valueOf(double)` 来创建对象,免 `double` 精度问题。 --- ## ✅ 控制精度与舍入模式(可选) 如果你希望在相加后保留指定位数的小数,可以使用 `setScale()`: ```java BigDecimal sum = num1.add(num2).setScale(2, BigDecimal.ROUND_HALF_UP); // 或更推荐的写法: BigDecimal sum = num1.add(num2).setScale(2, RoundingMode.HALF_UP); ``` 这能确保结果如 `30.80` 而不是 `30.8`,适用于金额显示场景。 --- ## ✅ 多个 BigDecimal 相加(扩展) 如果你想对多个数求和,可以用 Stream: ```java List<BigDecimal> numbers = Arrays.asList( new BigDecimal("10.00"), new BigDecimal("20.00"), new BigDecimal("30.50") ); BigDecimal total = numbers.stream() .filter(Objects::nonNull) // 防止 null .reduce(BigDecimal.ZERO, BigDecimal::add); System.out.println("总和: " + total); // 60.50 ``` --- ## ✅ 常见错误与指南 | 错误 | 说明 | 正确做法 | |------|------|---------| | `new BigDecimal(0.1)` | `double` 字面量精度丢失 | 改用 `new BigDecimal("0.1")` | | `a.add(b)` 不赋值 | 结果丢失 | 写成 `a = a.add(b)` | | 使用 `==` 比较 | 比较的是引用 | 使用 `.compareTo()` 或 `.equals()` | | 忽略 null 判断 | 可能空指针 | 先判断是否为 null | --- ## ✅ 总结 ✅ 正确地让两个 `BigDecimal` 相加的方式是: ```java BigDecimal result = a.add(b); ``` 记住三点: 1. `BigDecimal` 是**不可变的** 2. `.add()` 返回新实例,必须**接收返回值** 3. 推荐使用字符串构造器防止精度问题 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值