1. 为什么需要 BigDecimal?
在 Java 中,float
和 double
类型由于采用 IEEE 754 浮点数标准,存在精度丢失问题,不适合用于金融、货币等需要高精度计算的场景。例如:
System.out.println(0.1 + 0.2); // 输出:0.30000000000000004
BigDecimal
是 Java 提供的用于高精度计算的类,能够避免浮点数运算的精度问题,适用于:
- 金融计算(金额、利率)
- 科学计算(高精度实验数据)
- 商业计算(税率、折扣)
2. BigDecimal 的核心特性
(1) 不可变性(Immutable)
BigDecimal
是不可变类,所有算术运算(如 add
、subtract
、multiply
、divide
)都会返回一个新的 BigDecimal
对象,而不会修改原对象:
BigDecimal a = new BigDecimal("10.5");
BigDecimal b = new BigDecimal("2.0");
BigDecimal sum = a.add(b); // 返回新对象,a 和 b 不变
System.out.println(sum); // 12.5
(2) 精确的数值表示
BigDecimal
内部使用 BigInteger
存储未缩放的值(unscaled value)和一个缩放因子(scale),例如:
123.45
存储为unscaledValue = 12345
,scale = 2
1.2345
存储为unscaledValue = 12345
,scale = 4
这种存储方式避免了浮点数的二进制近似问题。
(3) 多种构造方式
BigDecimal
提供了多种构造方法:
BigDecimal b1 = new BigDecimal("123.456"); // 推荐:字符串构造,避免精度问题
BigDecimal b2 = BigDecimal.valueOf(123.456); // 推荐:静态工厂方法
BigDecimal b3 = new BigDecimal(123.456); // 不推荐:可能丢失精度
为什么推荐 String
构造?
new BigDecimal(0.1)
实际上存储的是0.1000000000000000055511151231257827021181583404541015625
(浮点数的二进制近似)new BigDecimal("0.1")
精确存储0.1
3. BigDecimal 的运算
(1) 基本运算
方法 | 说明 |
---|---|
add(BigDecimal) | 加法 |
subtract(BigDecimal) | 减法 |
multiply(BigDecimal) | 乘法 |
divide(BigDecimal) | 除法(需指定舍入模式) |
remainder(BigDecimal) | 取余 |
pow(int) | 幂运算 |
示例:
BigDecimal a = new BigDecimal("10.5");
BigDecimal b = new BigDecimal("3");
BigDecimal sum = a.add(b); // 13.5
BigDecimal product = a.multiply(b); // 31.5
BigDecimal quotient = a.divide(b, 2, RoundingMode.HALF_UP); // 3.50(保留2位小数)
(2) 除法必须指定舍入模式
BigDecimal.divide()
在不能整除时必须指定 RoundingMode
,否则会抛出 ArithmeticException
:
BigDecimal a = new BigDecimal("10");
BigDecimal b = new BigDecimal("3");
// 错误:未指定舍入模式,抛出 ArithmeticException
// BigDecimal result = a.divide(b);
// 正确:指定舍入模式
BigDecimal result = a.divide(b, 2, RoundingMode.HALF_UP); // 3.33
(3) 比较运算
BigDecimal
使用 compareTo()
进行比较,不要用 equals()
:
BigDecimal a = new BigDecimal("10.0");
BigDecimal b = new BigDecimal("10.00");
System.out.println(a.equals(b)); // false(scale 不同)
System.out.println(a.compareTo(b)); // 0(数值相等)
4. 舍入模式(RoundingMode)
BigDecimal
支持多种舍入模式:
模式 | 说明 |
---|---|
UP | 远离 0 舍入(1.1 → 2,-1.1 → -2) |
DOWN | 向 0 舍入(1.9 → 1,-1.9 → -1) |
CEILING | 向正无穷舍入(1.1 → 2,-1.1 → -1) |
FLOOR | 向负无穷舍入(1.9 → 1,-1.9 → -2) |
HALF_UP | 四舍五入(1.5 → 2,-1.5 → -2) |
HALF_DOWN | 五舍六入(1.5 → 1,-1.5 → -1) |
HALF_EVEN | 银行家舍入(1.5 → 2,2.5 → 2) |
示例:
BigDecimal num = new BigDecimal("1.235");
// 保留 2 位小数,四舍五入
BigDecimal rounded = num.setScale(2, RoundingMode.HALF_UP); // 1.24
5. BigDecimal 的优化与注意事项
(1) 使用静态工厂方法 valueOf()
BigDecimal.valueOf(double)
比 new BigDecimal(double)
更安全:
BigDecimal bad = new BigDecimal(0.1); // 不精确
BigDecimal good = BigDecimal.valueOf(0.1); // 精确
(2) 避免频繁创建对象
由于 BigDecimal
是不可变的,频繁运算会产生大量临时对象。在循环或高性能场景下,可以考虑优化:
BigDecimal sum = BigDecimal.ZERO; // 使用常量优化
for (int i = 0; i < 1000; i++) {
sum = sum.add(BigDecimal.valueOf(i));
}
(3) 注意 scale
的影响
BigDecimal
的 scale
会影响运算结果:
BigDecimal a = new BigDecimal("1.00");
BigDecimal b = new BigDecimal("1.0");
BigDecimal c = new BigDecimal("1");
System.out.println(a.equals(b)); // false(scale 不同)
System.out.println(a.compareTo(b)); // 0(数值相同)
6. 总结
BigDecimal
适用于高精度计算,避免float/double
的精度问题。- 推荐使用
String
或valueOf()
构造,避免精度丢失。 - 运算时必须处理舍入模式,特别是除法。
- 比较使用
compareTo()
,而非equals()
。 - 注意不可变性,运算结果需要重新赋值。
BigDecimal
是 Java 中处理精确计算的强大工具,正确使用可以避免许多数值计算的陷阱。