问题
BigDecimal(double)和BigDecimal(String)有什么区别?
答案
核心区别
BigDecimal(double):会继承double类型的精度误差,导致结果不精确。
BigDecimal(String):按字符串精确表示数值,无精度损失。
问题演示
// 使用double构造器
BigDecimal bd1 = new BigDecimal(0.1);
System.out.println(bd1);
// 输出:0.1000000000000000055511151231257827021181583404541015625
// ❌ 不是期望的0.1
// 使用String构造器
BigDecimal bd2 = new BigDecimal("0.1");
System.out.println(bd2);
// 输出:0.1
// ✅ 精确表示
// 计算对比
BigDecimal result1 = new BigDecimal(0.1).add(new BigDecimal(0.2));
System.out.println(result1);
// 输出:0.3000000000000000166533453693773481063544750213623046875
BigDecimal result2 = new BigDecimal("0.1").add(new BigDecimal("0.2"));
System.out.println(result2);
// 输出:0.3
根本原因
1. double的二进制表示缺陷
IEEE 754标准规定,double使用64位存储:
符号位(1位) + 指数(11位) + 尾数(52位)
问题:大部分十进制小数无法精确转换为二进制:
// 0.1的二进制表示(无限循环)
0.1(十进制) = 0.0001100110011001100110011...(二进制)
// double存储时必须截断
double d = 0.1; // 实际存储的值 ≈ 0.1000000000000000055511...
2. BigDecimal(double)的内部逻辑
// BigDecimal源码简化
public BigDecimal(double val) {
// 直接使用double的二进制表示
this.intVal = Double.doubleToLongBits(val);
// 保留了double的精度误差!
}
3. BigDecimal(String)的内部逻辑
public BigDecimal(String val) {
// 解析字符串,精确转换为BigInteger + scale
// "0.1" → intVal=1, scale=1 (表示1×10^-1)
// 完全精确,无精度损失
}
构造器对比
| 构造器 | 精度 | 性能 | 适用场景 |
|---|---|---|---|
new BigDecimal(double) | ❌ 不精确 | 快 | 不推荐使用 |
new BigDecimal(String) | ✅ 精确 | 较慢 | 金额、精确计算 |
BigDecimal.valueOf(double) | ⚠️ 有toString转换 | 中等 | 性能优化场景 |
推荐用法
✅ 正确做法
// 1. 使用String构造器
BigDecimal price = new BigDecimal("19.99");
// 2. 使用valueOf方法(内部调用Double.toString)
BigDecimal discount = BigDecimal.valueOf(0.15);
// 3. 使用整数构造后除法
BigDecimal rate = new BigDecimal(15).divide(new BigDecimal(100)); // 0.15
❌ 错误做法
// 直接使用double构造器
BigDecimal wrong = new BigDecimal(0.1); // ❌ 精度丢失
// 从double计算后传入
double d = 0.1 + 0.2;
BigDecimal wrong2 = new BigDecimal(d); // ❌ 精度已丢失
valueOf方法的特殊性
BigDecimal.valueOf(double) 是一个妥协方案:
// valueOf源码
public static BigDecimal valueOf(double val) {
// 先转为字符串,再构造BigDecimal
return new BigDecimal(Double.toString(val));
}
// 对比
System.out.println(new BigDecimal(0.1));
// 0.1000000000000000055511151231257827021181583404541015625
System.out.println(BigDecimal.valueOf(0.1));
// 0.1 ← Double.toString进行了格式化
注意:valueOf 虽然看起来是0.1,但本质上是通过 Double.toString 的四舍五入得到的,并非真正精确:
double d = 0.12345678901234567890;
System.out.println(BigDecimal.valueOf(d));
// 0.12345678901234568 ← 精度仍受double限制(17位有效数字)
实战案例
金额计算
// ❌ 错误:使用double导致精度问题
public BigDecimal calculateTotal(List<Double> prices) {
return prices.stream()
.map(BigDecimal::new) // ❌ 错误
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
// ✅ 正确:使用String或已有BigDecimal
public BigDecimal calculateTotal(List<String> prices) {
return prices.stream()
.map(BigDecimal::new) // ✅ 正确
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
// ✅ 最佳:直接使用BigDecimal类型
public BigDecimal calculateTotal(List<BigDecimal> prices) {
return prices.stream()
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
配置文件读取
// application.properties
discount.rate=0.15
// ❌ 错误
@Value("${discount.rate}")
private double discountRate;
BigDecimal discount = new BigDecimal(discountRate); // ❌
// ✅ 正确
@Value("${discount.rate}")
private String discountRate;
BigDecimal discount = new BigDecimal(discountRate); // ✅
性能考量
// JMH基准测试(百万次操作)
@Benchmark
public BigDecimal testDoubleConstructor() {
return new BigDecimal(0.123456789);
}
@Benchmark
public BigDecimal testStringConstructor() {
return new BigDecimal("0.123456789");
}
@Benchmark
public BigDecimal testValueOf() {
return BigDecimal.valueOf(0.123456789);
}
// 典型结果
// testDoubleConstructor: 50 ns/op
// testStringConstructor: 120 ns/op (慢2.4倍)
// testValueOf: 80 ns/op
结论:String构造器慢约2-3倍,但对于金额计算,精度优先于性能。
最佳实践总结
// ✅ 金额计算场景
BigDecimal price = new BigDecimal("99.99");
// ✅ 从整数计算
BigDecimal percent = BigDecimal.valueOf(15).divide(BigDecimal.valueOf(100));
// ✅ 常量定义
public static final BigDecimal TAX_RATE = new BigDecimal("0.06");
// ⚠️ 可容忍微小误差的科学计算
BigDecimal scientificValue = BigDecimal.valueOf(0.123456789);
// ❌ 永远不要这样做
BigDecimal wrong = new BigDecimal(0.1);
BigDecimal wrong2 = new BigDecimal(someDouble);
答题总结
BigDecimal(double) 会继承double的二进制表示缺陷,导致精度误差(如0.1存储为0.1000000…0555…),而 BigDecimal(String) 按字符串精确解析,无精度损失。金额计算必须使用String构造器,避免使用double。BigDecimal.valueOf(double) 通过 Double.toString 格式化,适合性能优化场景,但仍受double精度限制(17位有效数字)。