试问:
System.out.println(0.1 + 0.2);
输出什么?
❌ 不是0.3!而是0.30000000000000004
!
这就是浮点数计算的精度问题,而BigDecimal
正是Java给出的完美解决方案!
double a = 0.1;
double b = 0.2;
System.out.println(a + b == 0.3); // false!
场景 | double的问题 | 后果 |
---|---|---|
商品价格计算 | 0.06+0.01=0.069999999999 | 少收1分钱 |
利息计算 | 累计误差越来越大 | 对不上账 |
税务计算 | 四舍五入不准确 | 法律风险 |
BigDecimal bad = new BigDecimal(0.1); // ❌ 仍带浮点误差
BigDecimal good = new BigDecimal("0.1"); // ✅ 推荐字符串构造
BigDecimal best = BigDecimal.valueOf(0.1); // ✅ 内部优化
BigDecimal a = new BigDecimal("10");
BigDecimal b = a.add(new BigDecimal("20"));
// a仍然是10,b是30
BigDecimal num1 = new BigDecimal("0.1");
BigDecimal num2 = new BigDecimal("0.2");
// 加法
BigDecimal sum = num1.add(num2); // 0.3
// 减法
BigDecimal diff = num1.subtract(num2); // -0.1
// 乘法
BigDecimal product = num1.multiply(num2); // 0.02
// 除法(必须指定舍入模式)
BigDecimal quotient = num1.divide(num2, RoundingMode.HALF_UP); // 0.5
模式 | 3.5舍入 | 4.5舍入 | -1.6舍入 |
---|---|---|---|
UP (远离零) |
4 | 5 | -2 |
DOWN (趋向零) |
3 | 4 | -1 |
CEILING (向正无穷) |
4 | 5 | -1 |
FLOOR (向负无穷) |
3 | 4 | -2 |
HALF_UP (四舍五入) |
4 | 5 | -2 |
HALF_DOWN (五舍六入) |
3 | 4 | -2 |
// 错误示范
new BigDecimal(0.1); // 实际值:0.100000000000000005551115...
// 正确做法
new BigDecimal("0.1"); // 精确值
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.10");
System.out.println(a.equals(b)); // false(标度不同)
System.out.println(a.compareTo(b) == 0); // true(推荐方式)
// 抛出ArithmeticException
BigDecimal result = a.divide(b);
// 必须指定舍入模式
BigDecimal result = a.divide(b, 2, RoundingMode.HALF_UP);
new BigDecimal("1e-3"); // 正确解析为0.001
new BigDecimal(1e-3); // 又变成浮点误差!
操作 | BigDecimal耗时 | double耗时 |
---|---|---|
100万次加法 | 120ms | 15ms |
BigDecimal.valueOf(0.1) == BigDecimal.valueOf(0.1); // ❌不要用==比较
// 以分为单位存储(避免小数)
class Order {
private long amount; // 单位:分
public BigDecimal getAmount() {
return BigDecimal.valueOf(amount, 2);
}
}
BigDecimal price = new BigDecimal("99.99");
BigDecimal taxRate = new BigDecimal("0.13");
// 含税价 = 价格 * (1 + 税率)
BigDecimal total = price.multiply(
BigDecimal.ONE.add(taxRate)
).setScale(2, RoundingMode.HALF_UP); // 保留2位小数
BigDecimal totalAmount = new BigDecimal("1000.00");
BigDecimal ratioA = new BigDecimal("0.6");
BigDecimal ratioB = new BigDecimal("0.4");
BigDecimal partA = totalAmount.multiply(ratioA)
.setScale(2, RoundingMode.HALF_UP);
BigDecimal partB = totalAmount.subtract(partA); // 确保总额不变
private static final BigDecimal HUNDRED = new BigDecimal("100");
// 而不是每次都new
BigDecimal percent = amount.divide(HUNDRED, 2, RoundingMode.HALF_UP);
// 过早舍入会累积误差
BigDecimal a = new BigDecimal("1.234").setScale(2, RoundingMode.HALF_UP);
BigDecimal b = new BigDecimal("5.678").setScale(2, RoundingMode.HALF_UP);
a.add(b); // 6.91(精度已损失)
// 应在最终结果舍入
BigDecimal sum = new BigDecimal("1.234").add(new BigDecimal("5.678"))
.setScale(2, RoundingMode.HALF_UP); // 6.91
// 大批量计算时部分使用double
double d = bigDecimal.doubleValue();
// ...中间计算
return new BigDecimal(d).setScale(2, RoundingMode.HALF_UP);
BigDecimal num = new BigDecimal("1000.00");
// 保留两位小数,去除尾部零
String str = num.setScale(2, RoundingMode.HALF_UP)
.stripTrailingZeros()
.toPlainString(); // 1000
int scale = new BigDecimal("123.4500").scale(); // 4
int realScale = new BigDecimal("123.4500")
.stripTrailingZeros().scale(); // 2
// JDBC设置
preparedStatement.setBigDecimal(1, amount);
// JPA/Hibernate
@Column(precision = 19, scale = 4)
private BigDecimal price;
金融计算三原则:
记住这个黄金法则:
// 好代码
BigDecimal total = price.multiply(quantity)
.setScale(2, RoundingMode.HALF_UP);
// 坏代码
double total = price * quantity; // 精度丢失警告!