问题

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位有效数字)。