StackTraces
Java已验证

BigDecimal 除法报 Non-terminating decimal expansion

@debug_master32 天前更新阅读 6 min00

这个异常不是 bug,是 BigDecimal 的「精确优先」设计:除法要返回精确结果,但 1 除以 3 这类结果小数位无限延伸,根本表示不出来,于是直接抛异常而不是悄悄给你一个近似值。修法不是换掉 BigDecimal,而是每次除法都把「保留几位 + 怎么舍入」说清楚。

Issue & Env(案发现场与环境)

  • 现象:金额、计价等用到 BigDecimal 的代码,运行到某次除法突然抛异常,接口 500。
  • 环境:Java 任何版本,java.math.BigDecimal,常见于财务/计费/库存计算里除不尽的情况。

StackTrace(堆栈日志)

java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
    at java.math.BigDecimal.divide(BigDecimal.java:1736)
    at com.divudi.ejb.PharmacyCalculation.saveItemBatchWithCosting(PharmacyCalculation.java:786)
    at com.divudi.ejb.PharmacyCalculation.processBill(PharmacyCalculation.java:312)
    at com.divudi.jsf.PharmacyBillController.save(PharmacyBillController.java:124)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.lang.reflect.Method.invoke(Method.java:498)

一句「no exact representable decimal result」点明了本质:结果无法精确表示。

Reproduce(复现代码)

用单参数的 divide 做除法,结果除不尽:

BigDecimal a = new BigDecimal("1");
BigDecimal b = new BigDecimal("3");
System.out.println(a.divide(b)); // 抛异常

甚至 40 / 60 这种直觉上「除得尽」的例子也会炸第一步,因为 divide 要返回一个精确的十进制商,而 40 / 60 的十进制表示是循环小数 0.6666…,没法用有限位精确写下。

Root Cause(根因分析)

BigDecimal 用「无标度整数 + 标度(scale)」表示一个精确的十进制数。divide(divisor) 这个重载要求返回一个精确商,用的标度是「被除数标度减去除数标度」,且不允许舍入。一旦商的十进制展开是无限循环小数(1/3、10/7 之类),它无法在有限位里精确表示,就只能抛 ArithmeticException。

所以报错与否,取决于除法的结果是否「有限小数」,而不在于代码写得对不对。add、subtract、multiply 都没这个问题,唯独除法会因为除不尽而踩坑。很多人在开发环境的数据恰好除得尽、没炸,上线后换了一批订单数据就崩了。

另一个易错点是「只传 RoundingMode 不传 scale」:即便你指定了舍入模式,Java 仍需要一个明确的标度来决定保留几位小数,缺少它同样可能对除不尽的结果抛异常。

The Fix(解决方案)

除法统一写成「三参数」:被除数、保留位数、舍入模式:

BigDecimal a = new BigDecimal("1");
BigDecimal b = new BigDecimal("3");
// 保留 2 位小数,四舍五入
BigDecimal result = a.divide(b, 2, RoundingMode.HALF_UP);

金额类计算建议把精度提取成常量统一管理,避免各写各的造成舍入不一致:

private static final int MONEY_SCALE = 6;

BigDecimal perUnit = price.divide(units, MONEY_SCALE, RoundingMode.HALF_EVEN);

RoundingMode.HALF_EVEN(银行家舍入)对金额累加比 HALF_UP 更不容易产生累积偏差,适合财务场景。如果只是要个整数结果,也仍然要传 RoundingMode.HALF_UP,而不是依赖 divide 的默认行为。

需要精确到「无尽精度」的数学运算(不是货币)时,改用 MathContext.DECIMAL128 这类带精度上限的上下文;但那已经是另一套语义,业务金额别这么用。

References(参考来源)

最后更新于 2026-09-09

这篇帮到你了吗?

刚解决了一个棘手的报错?花两分钟记录下来,帮助下一个遇到同样问题的开发者。

贡献一条解法