问题描述
两个BigDecimal相除, 抛了异常
原因分析:
Java 中使用 BigDecimal 做除法运算的时候,值有可能是无限循环的小数,结果是无限循环的小数,就会抛出上面这个异常。
来看看源码:
public BigDecimal divide(BigDecimal divisor) {
// 省略N行
BigDecimal quotient;
MathContext mc = new MathContext( (int)Math.min(this.precision() + (long)Math.ceil(10.0*divisor.precision()/3.0), Integer.MAX_VALUE), RoundingMode.UNNECESSARY);
try {
quotient = this.divide(divisor, mc);
} catch (ArithmeticException e) {
throw new ArithmeticException("Non-terminating decimal expansion; " +
"no exact representable decimal result.");
}
}
是不是抛出来的异常跟我们堆栈里的一样,我们接着进入divide方法看看
private static BigDecimal divide(final long xs, int xscale, final long ys, int yscale, long preferredScale, MathContext mc) {
......
quotient = divideAndRound(scaledXs, ys, scl, roundingMode, checkScaleNonZero(preferredScale));
......
// doRound, here, only affects 1000000000 case.
return doRound(quotient,mc);
}
接着进入divideAndRound方法
private static BigDecimal divideAndRound(long ldividend, long ldivisor, int scale, int roundingMode,
int preferredScale) {
int qsign; // quotient sign
long q = ldividend / ldivisor; // store quotient in long
if (roundingMode == ROUND_DOWN && scale == preferredScale)
return valueOf(q, scale);
long r = ldividend % ldivisor; // store remainder in long
qsign = ((ldividend < 0) == (ldivisor < 0)) ? 1 : -1;
if (r != 0) {
// 重点
boolean increment = needIncrement(ldivisor, roundingMode, qsign, q, r);
return valueOf((increment ? q + qsign : q), scale);
} else {
if (preferredScale != scale)
return createAndStripZerosToMatchScale(q, scale, preferredScale);
else
return valueOf(q, scale);
}
}
进入needIncrement方法
private static boolean needIncrement(long ldivisor, int roundingMode,
int qsign, long q, long r) {
assert r != 0L;
int cmpFracHalf;
if (r <= HALF_LONG_MIN_VALUE || r > HALF_LONG_MAX_VALUE) {
cmpFracHalf = 1; // 2 * r can't fit into long
} else {
cmpFracHalf = longCompareMagnitude(2 * r, ldivisor);
}
return commonNeedIncrement(roundingMode, qsign, cmpFracHalf, (q & 1L) != 0L);
}
进入commonNeedIncrement方法
private static boolean commonNeedIncrement(int roundingMode, int qsign,
int cmpFracHalf, boolean oddQuot) {
switch(roundingMode) {
case ROUND_UNNECESSARY:
throw new ArithmeticException("Rounding necessary");
......
}
}
最终进入了case第一个,此时roundingMode是7
倒着往上找,看看这个roundingMode从哪儿来的。
int roundingMode = mc.roundingMode.oldMode;
MathContext mc = new MathContext( (int)Math.min(this.precision() + (long)Math.ceil(10.0*divisor.precision()/3.0), Integer.MAX_VALUE), RoundingMode.UNNECESSARY);
终于找到源头了吧,就是在我们使用单个参数的divide方法时,默认给的就是7,如果除不尽,有这个限制。如果用1200除以12,可以除尽,就不会抛这个异常了。
解决方案:
JDK为我们提供了带精度的divide方法,当遇到这种除不尽的场景,可以很好的处理,综上,建议在用BigDecimal进行除法运算的时候,优先采用有精度控制的方法。
例如:
new BigDecimal("1200").divide(new BigDecimal("12"), 2, BigDecimal.ROUND_HALF_UP)