在Java里如何使用BigDecimal处理高精度计算_Java数值精度解析

不能直接用 double 做金融计算,因其二进制浮点表示无法精确存储十进制小数(如 0.1),导致累加、比较出错;应使用 BigDecimal,且须用字符串或 valueOf 创建,四则运算调用对应方法,除法需指定精度与舍入模式,比较用 compareTo。

为什么不能直接用 double 做金融计算

因为 double 是 IEEE 754 浮点数,二进制无法精确表示很多十进制小数(比如 0.1),导致累加、比较时出现不可预测的误差。比如 0.1 + 0.2 == 0.3double 中返回 false。金融、计费、会计等场景必须避免这种误差,BigDecimal 是唯一可靠选择。

创建 BigDecimal 的正确方式:别用 double 构造器

这是最常踩的坑:new BigDecimal(0.1) 看似简洁,实则引入了 double 的原始误差。0.1 在 double 中实际存储为近似值,再转成 BigDecimal 只是“精确地保存了错误”。

正确做法只有两种:

  • 用字符串构造:new BigDec

    imal("0.1")
  • 用整数+标度构造:BigDecimal.valueOf(1, 1)(等价于 new BigDecimal("0.1")
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
BigDecimal sum = a.add(b); // 正确得到 "0.3"
// ❌ 不要写:new BigDecimal(0.1)

四则运算必须用方法,不能用 + - * /

BigDecimal 是不可变对象,没有重载算术操作符。所有运算都通过方法调用完成,且返回新实例:

  • 加法:a.add(b)
  • 减法:a.subtract(b)
  • 乘法:a.multiply(b)
  • 除法:a.divide(b, scale, roundingMode) —— 必须显式指定精度和舍入模式

除法尤其危险:a.divide(b) 若结果无限不循环(如 1/3),会抛 ArithmeticException。必须提供 scale(小数位数)和 RoundingMode(如 RoundingMode.HALF_UP)。

BigDecimal x = new BigDecimal("1");
BigDecimal y = new BigDecimal("3");
// ✅ 正确:指定精度和舍入
BigDecimal result = x.divide(y, 2, RoundingMode.HALF_UP); // "0.33"
// ❌ 错误:不指定会抛 ArithmeticException
// x.divide(y);

比较大小别用 == 或 equals(),要用 compareTo()

== 比较引用,equals() 会同时比较数值和标度(scale),导致 new BigDecimal("1.0").equals(new BigDecimal("1")) 返回 false —— 这在金额判断中极易出错。

统一用 compareTo()

  • a.compareTo(b) == 0 → 数值相等
  • a.compareTo(b) > 0 → a 大于 b
  • a.compareTo(b) → a 小于 b

另外,compareTo()null 敏感,调用前务必判空。

真正难的不是记住这些规则,而是把它们变成肌肉记忆——尤其在快速迭代时,一个 new BigDecimal(0.1) 或漏掉 divideRoundingMode,就可能让账单多扣一分钱,而这个错误在线上可能潜伏几周才暴露。