1. BigDecimal的构造陷阱与精度问题第一次用BigDecimal时我踩了个大坑。当时需要计算商品价格顺手写了new BigDecimal(0.1)结果打印出来的值让我傻眼了0.1000000000000000055511151231257827021181583404541015625。后来才知道用double类型构造BigDecimal就像用有漏洞的水桶装水还没开始计算就已经漏了精度。1.1 为什么double构造器会丢失精度计算机用二进制存储double类型数据而0.1在二进制中是无限循环小数就像1/3在十进制中等于0.333...。这导致double实际存储的值与表面值存在微小差异。测试代码很能说明问题System.out.println(new BigDecimal(0.1)); // 输出0.100000000000000005551115123125... System.out.println(new BigDecimal(0.1)); // 输出0.11.2 四种构造方式对比构造方式精度保证推荐场景性能开销BigDecimal(double)❌不推荐使用低BigDecimal(String)✅金额字符串输入中BigDecimal(int)✅整数精确计算低valueOf(double)✅处理第三方double数据中最佳实践财务系统里我坚持用字符串构造器比如new BigDecimal(1250.99)。如果必须处理double数据比如从数据库读取就用BigDecimal.valueOf(0.1)它底层会先转字符串再构造。2. 不可变性与四则运算的坑去年团队有个新人写了这样的代码BigDecimal total BigDecimal.ZERO; item.getPrice().add(discount); // 计算结果没保存结果订单总价永远为0这就是没理解BigDecimal的不可变性(immutable)。所有运算都会返回新对象原对象不变。2.1 正确运算姿势// 加法 BigDecimal a new BigDecimal(1.23); BigDecimal b new BigDecimal(2.34); BigDecimal sum a.add(b); // 必须接收返回值 // 链式运算 BigDecimal result a.add(b) .multiply(new BigDecimal(0.9)) .subtract(new BigDecimal(1.5));2.2 财务计算三原则始终用新对象接收结果就像String的运算检查Null金额字段要判空Objects.requireNonNull(amount)使用常量BigDecimal.ZERO比new BigDecimal(0)更高效3. 除法运算的异常处理做分账系统时我遇到过最头疼的异常java.lang.ArithmeticException: Non-terminating decimal expansion当除不尽时比如1除以3必须指定舍入方式。3.1 两种处理方案方案一指定精度和舍入模式BigDecimal dividend new BigDecimal(10); BigDecimal divisor new BigDecimal(3); // 保留2位小数四舍五入 BigDecimal quotient dividend.divide(divisor, 2, RoundingMode.HALF_UP);方案二使用MathContextMathContext mc new MathContext(4, RoundingMode.HALF_EVEN); // 银行家舍入法 BigDecimal result dividend.divide(divisor, mc);3.2 财务舍入规则选择舍入模式1.255-1.255适用场景HALF_UP四舍五入1.26-1.26普通金额计算HALF_EVEN银行家舍入1.26-1.26金融统计减少偏差DOWN截断1.25-1.25税费计算利于纳税人4. 金额比较与等值判断早期我犯过用equals比较金额的错误BigDecimal d1 new BigDecimal(2.0); BigDecimal d2 new BigDecimal(2.00); System.out.println(d1.equals(d2)); // false4.1 正确比较方式值比较用compareToif (d1.compareTo(d2) 0) { // 数值相等忽略精度 }严格比较用精度标准化// 统一保留2位小数再比较 boolean equal d1.setScale(2, RoundingMode.HALF_UP) .equals(d2.setScale(2, RoundingMode.HALF_UP));5. 财务计算实战案例最近做的电商优惠分摊系统需要处理这样的场景订单总价119.99元优惠券减免20元需要按商品金额比例分摊优惠5.1 分摊算法实现ListItem items Arrays.asList( new Item(手机, new BigDecimal(5999.99)), new Item(耳机, new BigDecimal(399.99)) ); BigDecimal total items.stream() .map(Item::getPrice) .reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal coupon new BigDecimal(20); // 优惠券20元 items.forEach(item - { // 计算分摊比例保留6位小数 BigDecimal ratio item.getPrice() .divide(total, 6, RoundingMode.HALF_UP); // 计算分摊金额保留2位 BigDecimal discount coupon.multiply(ratio) .setScale(2, RoundingMode.HALF_UP); System.out.printf(%s 分摊优惠%.2f元%n, item.getName(), discount); });5.2 金额格式化的坑直接调用toString()可能输出科学计数法System.out.println(new BigDecimal(123456789.12345)); // 输出123456789.12345 System.out.println(new BigDecimal(123456789123.12345)); // 输出1.2345678912312345E11解决方案NumberFormat format NumberFormat.getInstance(); format.setMinimumFractionDigits(2); format.setMaximumFractionDigits(2); format.setGroupingUsed(false); // 禁用千分位分隔符 System.out.println(format.format(new BigDecimal(123456789123.1))); // 输出123456789123.106. 性能优化技巧在交易风控系统中我们发现BigDecimal的这三个优化点最有效对象复用对高频使用的值如0、1、10使用静态常量private static final BigDecimal HUNDRED new BigDecimal(100);控制精度非必要不保留过多小数位// 仅在做除法时指定精度 BigDecimal rate amount.divide(total, 4, RoundingMode.HALF_UP);避免链式构造// 反例产生中间对象 BigDecimal bad new BigDecimal(new BigDecimal(123).add(new BigDecimal(456)).toString()); // 正例 BigDecimal good new BigDecimal(123).add(new BigDecimal(456));实测下来百万次运算能节省约200ms。虽然单次差异微小但在高频交易场景下很关键。