问题描述
在使用BigDecimal进行精确计算时,特别是进行除法运算时,可能会遇到以下异常:
java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.异常原因
BigDecimal是不可变的、任意精度的有符号十进制数,专为精确计算设计。但在除法运算中,当除不尽时(商是无限循环小数,如1除以3),如果没有指定舍入规则,BigDecimal无法精确表示结果,因此抛出此异常。
解决方案
方案1:使用divide()方法时指定舍入模式(推荐)
import java.math.BigDecimal; import java.math.RoundingMode; public class BigDecimalExample { public static void main(String[] args) { BigDecimal dividend = new BigDecimal("1"); BigDecimal divisor = new BigDecimal("3"); // 指定舍入模式(四舍五入,保留2位小数) BigDecimal result = dividend.divide(divisor, 2, RoundingMode.HALF_UP); System.out.println(result); // 输出: 0.33 // 其他舍入模式示例 // 向上取整 BigDecimal result2 = dividend.divide(divisor, 2, RoundingMode.UP); // 向下取整 BigDecimal result3 = dividend.divide(divisor, 2, RoundingMode.DOWN); // 银行家舍入法(四舍六入五成双) BigDecimal result4 = dividend.divide(divisor, 2, RoundingMode.HALF_EVEN); } }方案2:使用MathContext指定精度
import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; public class BigDecimalExample2 { public static void main(String[] args) { BigDecimal dividend = new BigDecimal("1"); BigDecimal divisor = new BigDecimal("3"); // 使用MathContext指定精度和舍入模式 MathContext mc = new MathContext(4, RoundingMode.HALF_UP); BigDecimal result = dividend.divide(divisor, mc); System.out.println(result); // 输出: 0.3333 } }常用舍入模式详解
| 舍入模式 | 描述 | 示例(保留0位小数) |
|---|---|---|
RoundingMode.UP | 远离零方向舍入 | 2.5 → 3, -2.5 → -3 |
RoundingMode.DOWN | 向零方向舍入 | 2.5 → 2, -2.5 → -2 |
RoundingMode.CEILING | 向正无穷方向舍入 | 2.5 → 3, -2.5 → -2 |
RoundingMode.FLOOR | 向负无穷方向舍入 | 2.5 → 2, -2.5 → -3 |
RoundingMode.HALF_UP | 四舍五入 | 2.5 → 3, -2.5 → -3 |
RoundingMode.HALF_DOWN | 五舍六入 | 2.5 → 2, 2.6 → 3 |
RoundingMode.HALF_EVEN | 银行家舍入法 | 2.5 → 2, 3.5 → 4 |
实际业务场景选择建议
1. 金融计算(金额处理)
// 金额计算通常需要保留2位小数,使用四舍五入 BigDecimal amount = price.divide(quantity, 2, RoundingMode.HALF_UP);2. 科学计算
// 科学计算可能需要更高精度 BigDecimal result = value1.divide(value2, 10, RoundingMode.HALF_UP);3. 比例计算
// 百分比计算,保留4位小数 BigDecimal percentage = part.divide(total, 4, RoundingMode.HALF_UP) .multiply(new BigDecimal("100"));最佳实践
始终在除法运算中指定舍入模式,避免潜在的异常
根据业务需求选择合适的精度和舍入模式
金额计算统一使用2位小数精度
使用字符串构造函数创建BigDecimal,避免精度丢失
// 推荐 BigDecimal d1 = new BigDecimal("0.1"); // 不推荐(可能丢失精度) BigDecimal d2 = new BigDecimal(0.1);工具类示例
import java.math.BigDecimal; import java.math.RoundingMode; public class BigDecimalUtils { // 默认金融计算精度(2位小数) private static final int DEFAULT_SCALE = 2; /** * 安全的除法运算(使用默认精度和四舍五入) */ public static BigDecimal safeDivide(BigDecimal dividend, BigDecimal divisor) { if (divisor.compareTo(BigDecimal.ZERO) == 0) { throw new ArithmeticException("除数不能为零"); } return dividend.divide(divisor, DEFAULT_SCALE, RoundingMode.HALF_UP); } /** * 安全的除法运算(自定义精度和舍入模式) */ public static BigDecimal safeDivide(BigDecimal dividend, BigDecimal divisor, int scale, RoundingMode roundingMode) { if (divisor.compareTo(BigDecimal.ZERO) == 0) { throw new ArithmeticException("除数不能为零"); } return dividend.divide(divisor, scale, roundingMode); } /** * 百分比计算 */ public static BigDecimal calculatePercentage(BigDecimal part, BigDecimal total) { BigDecimal ratio = safeDivide(part, total, 4, RoundingMode.HALF_UP); return ratio.multiply(new BigDecimal("100")); } }总结
Non-terminating decimal expansion异常的根本原因是BigDecimal除法运算中无限小数的存在。解决此问题的关键在于:
必须在调用
divide()方法时指定精度和舍入模式根据业务场景选择合适的舍入策略
金融计算通常使用
HALF_UP(四舍五入)模式建议封装工具类统一处理BigDecimal运算
遵循这些原则,可以确保BigDecimal计算的精确性和稳定性,避免运行时异常的发生。