BigDecimal使用小记:
- 1. 两数相除(divide),最好声明小数保留位。
1. 两数相除(divide),最好声明小数保留位。
用的最多的就是保留几位小数,使用方式为:A.divide(B, 4, BigDecimal.ROUND_HALF_UP)
其中,4位小数点后保留的小数位,也就是商的精度。当除不尽时,保留小数位,当除尽时,小数位补零占位。BigDecimal.ROUND_HALF_UP
表示最后一位小数四舍五入。
如果没有声明商的精度,当除数不能被整除时,即商为无限循环小数时,会抛出ArithmeticException
异常。
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
at java.math.BigDecimal.divide(BigDecimal.java:1693)
at com.lin.test.aboutBigDecimal.TestBigDecimal.main(TestBigDecimal.java:12)
碰到这个异常的时候,还是很懵逼的。看了下BigDecimal除法的源码才知道:
/**
* Returns a {@code BigDecimal} whose value is {@code (this /
* divisor)}, and whose preferred scale is {@code (this.scale() -
* divisor.scale())}; if the exact quotient cannot be
* represented (because it has a non-terminating decimal
* expansion) an {@code ArithmeticException} is thrown.
*
* @param divisor value by which this {@code BigDecimal} is to be divided.
* @throws ArithmeticException if the exact quotient does not have a
* terminating decimal expansion
* @return {@code this / divisor}
* @since 1.5
* @author Joseph D. Darcy
*/
public BigDecimal divide(BigDecimal divisor) {
/*
* Handle zero cases first.
*/
if (divisor.signum() == 0) { // x/0
if (this.signum() == 0) // 0/0
throw new ArithmeticException("Division undefined"); // NaN
throw new ArithmeticException("Division by zero");
}
// Calculate preferred scale
//处理商的精度:比较除数和被除数的精度,当
int preferredScale = saturateLong((long) this.scale - divisor.scale);
if (this.signum() == 0) // 0/y
return zeroValueOf(preferredScale);
else {
/*
* If the quotient this/divisor has a terminating decimal
* expansion, the expansion can have no more than
* (a.precision() + ceil(10*b.precision)/3) digits.
* Therefore, create a MathContext object with this
* precision and do a divide with the UNNECESSARY rounding
* mode.
*/
MathContext mc = new MathContext( (int)Math.min(this.precision() +
(long)Math.ceil(10.0*divisor.precision()/3.0),
Integer.MAX_VALUE),
RoundingMode.UNNECESSARY);
BigDecimal quotient;
try {
quotient = this.divide(divisor, mc);
} catch (ArithmeticException e) {
throw new ArithmeticException("Non-terminating decimal expansion; " +
"no exact representable decimal result.");
}
int quotientScale = quotient.scale();
// divide(BigDecimal, mc) tries to adjust the quotient to
// the desired one by removing trailing zeros; since the
// exact divide method does not have an explicit digit
// limit, we can add zeros too.
if (preferredScale > quotientScale)
return quotient.setScale(preferredScale, ROUND_UNNECESSARY);
return quotient;
}
}
可以看到:
if (divisor.signum() == 0) { // x/0
if (this.signum() == 0) // 0/0
throw new ArithmeticException("Division undefined"); // NaN
throw new ArithmeticException("Division by zero");
}
首先判断除数是否为零,为零则抛出除零异常,所以在使用 BigDecimal 的除法时,需手动判断除数是否为零。
接着是设置商的精度:
// Calculate preferred scale
int preferredScale = saturateLong((long) this.scale - divisor.scale);
private static int saturateLong(long s) {
int i = (int)s;
return (s == i) ? i : (s < 0 ? Integer.MIN_VALUE : Integer.MAX_VALUE);
}
当除数和被除数的精度一样时,返回0
,即商无小数位。
当除数的精度大于被除数的精度时,返回Integer.MIN_VALUE
当除数的精度小于被除数的精度时,返回Integer.MAX_VALUE
。
接着判断被除数是否为零,如果为零,则直接按照前面计算的精度返回零(可能带有小数位的零,如:0.000000
),可见这里在使用。时,无需对被除数是否为零进行判断。
然后后面new MathContext
是计算了一个值,封装了上下文设置,这些设置描述了数字操作符的某些规则,这点没看懂怎么回事。
后面接着调用了构造方法:divide(BigDecimal divisor, MathContext mc)
这里仍然是先对被除数和除数进行非零判断,然后使用除数和被除数的intValue和精度,以及商的精度去计算。
使用最多的还是:divide(BigDecimal divisor, int scale, int roundingMode)
这个方法的三个参数分别是:除数,精度,小数位规则。
首先roundingMode
的值为final值,范围为0-7
,表示各种舍弃小数位的规则。其中四舍五入为:ROUND_HALF_UP
。