BigDecimal除法必须显式指定scale和RoundingMode,否则无限小数会抛ArithmeticException;推荐使用三参数divide(divisor, scale, roundingMode),避免两参数或单参数版本的隐晦行为。

BigDecimal除法必须显式指定scale和RoundingMode
不设scale和RoundingMode直接调用divide(),只要结果是无限小数(比如1除以3),就会抛ArithmeticException: Non-terminating decimal expansion。这不是bug,是设计——BigDecimal默认拒绝模糊精度。
常见错误现象:本地测试用整除(如10.divide(2))没问题,一到线上遇到1.0.divide(3.0)就崩;或者误以为divide(BigDecimal.valueOf(3))能自动取整。
- 必须传两个参数:
scale(保留几位小数)和RoundingMode(怎么舍入) -
scale不能为负,否则抛IllegalArgumentException - 如果被除数为0,无论
scale和RoundingMode怎么设,结果都是0,不会报错
RoundingMode.HALF_UP是最接近“四舍五入”的选项
很多人直觉选RoundingMode.HALF_UP,它确实对应小学数学里的四舍五入逻辑:0–4舍、5–9入。但要注意,它只在scale已明确的前提下生效;如果scale设太小(比如算税率时只留0位小数),会丢失业务精度。
使用场景:金额计算、报表展示、前端透出值。别用在中间计算——中间过程建议多留2位(比如钱用4位小数),最后再按需截断。
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
RoundingMode.HALF_EVEN(银行家舍入)更抗统计偏差,但业务方难理解,慎用 -
RoundingMode.DOWN和RoundingMode.UP是纯截断/进一,适合计费类“向上取整”场景(如按分钟计费) - 绝对不要用
RoundingMode.UNNECESSARY——它要求必须整除,稍有余数就炸,基本只用于校验
divide()的三个重载版本别混用
容易踩的坑是看到divide(BigDecimal divisor, RoundingMode roundingMode)这个两参数版本,以为scale会自动推导——其实它默认用被除数的scale,极不可靠。例如new BigDecimal("1").divide(new BigDecimal("3"), RoundingMode.HALF_UP)结果是0(因为"1"的scale是0)。
真正可控的是三参数版本:divide(divisor, scale, roundingMode)。所有生产代码里,只要涉及除法,必须用这个。
- 两参数版
divide(divisor, roundingMode):依赖操作数自身scale,行为隐晦,回避 - 单参数版
divide(divisor):仅适用于确定整除的场景,如单位换算(100分=1元),否则必抛异常 - 推荐写法:
amount.divide(rate, 6, RoundingMode.HALF_UP),6位小数保底,避免中间计算失真
scale设多少?看业务规则,不是看UI显示位数
有人把scale设成和页面显示一致(比如金额显示2位,就设2),这会导致中间步骤精度污染。比如计算含税价:不含税价 × (1 + 税率),若税率是13%,先用2位小数算100 × 1.13 = 113.00,再除以1.13反推,已经回不去了。
性能影响几乎可忽略,但兼容性要注意:老JDK(如Java 8u20)对超大scale(如100+)做除法略慢,不过日常用10以内完全无感。
- 金融核心系统:至少6位(支持万分之一费率)
- 电商价格:建议4位(应付优惠券叠加、满减折算)
- 日志或调试输出:可用
toString()或toPlainString(),别用doubleValue()——那会丢精度
最麻烦的其实是上下游约定:如果调第三方接口返回的是double,那从一开始精度就没了,BigDecimal再怎么设也救不回来。这种地方得倒逼协议改用字符串传数字。

















