BigDecimal类 setScale()方法java.lang.ArithmeticException: Rounding necessary

        BigDecimal调用setScale方法时当未设置舍入模式时,系统会给个默认ROUND_UNNECESSARY(int 值为7)值,如果小数点后不为零,而且要保留的小数位数小于旧小数位数,那么此时会抛出异常java.lang.ArithmeticException: Rounding necessary。

BigDecimal的setScale方法提供了三种方式分别是:

    setScale(int newScale);//参数一欲保留的小数位数

    setScale(int newScale, RoundingMode roundingMode);//参数一欲保留的小数位数,参数二进位方式枚举

    setScale(int newScale, int roundingMode);//参数一欲保留的小数位数,参数二进位方式

且看源码:

public BigDecimal setScale(int newScale) {
        return setScale(newScale, ROUND_UNNECESSARY);

}

public BigDecimal setScale(int newScale, RoundingMode roundingMode) {
        return setScale(newScale, roundingMode.oldMode);
}

public BigDecimal setScale(int newScale, int roundingMode) {

        //roundingMode不能小于0、不能大于7

        if (roundingMode < ROUND_UP || roundingMode > ROUND_UNNECESSARY)
            throw new IllegalArgumentException("Invalid rounding mode");

        int oldScale = this.scale;
        if (newScale == oldScale)        // easy case
            return this;
        if (this.signum() == 0)            // zero can have any scale
            return BigDecimal.valueOf(0, newScale);


        long rs = this.intCompact;
        if (newScale > oldScale) {
            int raise = checkScale((long)newScale - oldScale);
            BigInteger rb = null;
            if (rs == INFLATED ||
                (rs = longMultiplyPowerTen(rs, raise)) == INFLATED)
                rb = bigMultiplyPowerTen(raise);
            return new BigDecimal(rb, rs, newScale,
                                  (precision > 0) ? precision + raise : 0);
        } else {
            // newScale < oldScale -- drop some digits
            // Can't predict the precision due to the effect of rounding.
            int drop = checkScale((long)oldScale - newScale);
            if (drop < LONG_TEN_POWERS_TABLE.length)
                return divideAndRound(rs, this.intVal,
                                      LONG_TEN_POWERS_TABLE[drop], null,
                                      newScale, roundingMode, newScale);
            else
                return divideAndRound(rs, this.intVal,
                                      INFLATED, bigTenToThe(drop),
                                      newScale, roundingMode, newScale);
        }

    };

private static BigDecimal divideAndRound(long ldividend, BigInteger bdividend,
                                             long ldivisor,  BigInteger bdivisor,
                                             int scale, int roundingMode,
                                             int preferredScale) {
        boolean isRemainderZero;       // record remainder is zero or not
        int qsign;                     // quotient sign
        long q = 0, r = 0;             // store quotient & remainder in long
        MutableBigInteger mq = null;   // store quotient
        MutableBigInteger mr = null;   // store remainder
        MutableBigInteger mdivisor = null;
        boolean isLongDivision = (ldividend != INFLATED && ldivisor != INFLATED);
        if (isLongDivision) {
            q = ldividend / ldivisor;
            if (roundingMode == ROUND_DOWN && scale == preferredScale)
                return new BigDecimal(null, q, scale, 0);
            r = ldividend % ldivisor;
            isRemainderZero = (r == 0);
            qsign = ((ldividend < 0) == (ldivisor < 0)) ? 1 : -1;
        } else {
            if (bdividend == null)
                bdividend = BigInteger.valueOf(ldividend);
            // Descend into mutables for faster remainder checks
            MutableBigInteger mdividend = new MutableBigInteger(bdividend.mag);
            mq = new MutableBigInteger();
            if (ldivisor != INFLATED) {
                r = mdividend.divide(ldivisor, mq);
                isRemainderZero = (r == 0);
                qsign = (ldivisor < 0) ? -bdividend.signum : bdividend.signum;
            } else {
                mdivisor = new MutableBigInteger(bdivisor.mag);
                mr = mdividend.divide(mdivisor, mq);
                isRemainderZero = mr.isZero();
                qsign = (bdividend.signum != bdivisor.signum) ? -1 : 1;
            }
        }
        boolean increment = false;
        if (!isRemainderZero) {
            int cmpFracHalf;
            /* Round as appropriate */
            if (roundingMode == ROUND_UNNECESSARY) {  // Rounding prohibited
                throw new ArithmeticException("Rounding necessary");

            } else if (roundingMode == ROUND_UP) {      // Away from zero
                increment = true;
            } else if (roundingMode == ROUND_DOWN) {    // Towards zero
                increment = false;
            } else if (roundingMode == ROUND_CEILING) { // Towards +infinity
                increment = (qsign > 0);
            } else if (roundingMode == ROUND_FLOOR) {   // Towards -infinity
                increment = (qsign < 0);
            } else {
                if (isLongDivision || ldivisor != INFLATED) {
                    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);
                    }
                } else {
                    cmpFracHalf = mr.compareHalf(mdivisor);
                }
                if (cmpFracHalf < 0)
                    increment = false;     // We're closer to higher digit
                else if (cmpFracHalf > 0)  // We're closer to lower digit
                    increment = true;
                else if (roundingMode == ROUND_HALF_UP)
                    increment = true;
                else if (roundingMode == ROUND_HALF_DOWN)
                    increment = false;
                else  // roundingMode == ROUND_HALF_EVEN, true iff quotient is odd
                    increment = isLongDivision ? (q & 1L) != 0L : mq.isOdd();
            }
        }
        BigDecimal res;
        if (isLongDivision)
            res = new BigDecimal(null, (increment ? q + qsign : q), scale, 0);
        else {
            if (increment)
                mq.add(MutableBigInteger.ONE);
            res = mq.toBigDecimal(qsign, scale);
        }
        if (isRemainderZero && preferredScale != scale)
            res.stripZerosToMatchScale(preferredScale);
        return res;

    }

分析源码得知:当未设置舍入模式时,系统会给个默认ROUND_UNNECESSARY(int 值为7)值,如果调用setScale()方法时小数点后不为零,而且要保留的小数位数小于旧小数位数,那么此时会抛出异常java.lang.ArithmeticException: Rounding necessary。

改进措施:

        调用setScale方法一定要指定进位方式,建议使用setScale(int newScale, RoundingMode roundingMode)方法。

 setScale(int newScale, int roundingMode)  容易出错引发异常roundingMode只能大于等0小于等于7。

另转载一篇对RoundingMode 的说明:

RoundingMode 是一个枚举类,有一下几个常量:UP(0),DOWN(1),CEILING(2),FLOOR(3),HALF_UP(4)学校讲的,HALF_DOWN(5),HALF_EVEN(6),UNNECESSARY(7).

UP :

        远离零方向舍入的舍入模式。始终对非零舍弃部分前面的数字加 1。注意,此舍入模式始终不会减少计算值的绝对值。

输入数字 使用 UP 舍入模式
将输入数字舍入为一位数
5.5 6
2.5 3
1.6 2
1.1 2
1.0 1
-1.0 -1
-1.1 -2
-1.6 -2
-2.5 -3
-5.5 -6

DOWN:

        向零方向舍入的舍入模式。从不对舍弃部分前面的数字加 1(即截尾)。注意,此舍入模式始终不会增加计算值的绝对值。

输入数字 使用 DOWN 舍入模式
将输入数字舍入为一位数
5.5 5
2.5 2
1.6 1
1.1 1
1.0 1
-1.0 -1
-1.1 -1
-1.6 -1
-2.5 -2
-5.5 -5

CEILING

    向正无限大方向舍入的舍入模式。如果结果为正,则舍入行为类似于 RoundingMode.UP;如果结果为负,则舍入行为类似于 RoundingMode.DOWN。注意,此舍入模式始终不会减少计算值。

输入数字 使用 CEILING 舍入模式
将输入数字舍入为一位数
5.5 6
2.5 3
1.6 2
1.1 2
1.0 1
-1.0 -1
-1.1 -1
-1.6 -1
-2.5 -2
-5.5 -5

FLOOR:

        向负无限大方向舍入的舍入模式。如果结果为正,则舍入行为类似于 RoundingMode.DOWN;如果结果为负,则舍入行为类似于RoundingMode.UP。注意,此舍入模式始终不会增加计算值。

输入数字 使用 FLOOR 舍入模式
将输入数字舍入为一位数
5.5 5
2.5 2
1.6 1
1.1 1
1.0 1
-1.0 -1
-1.1 -2
-1.6 -2
-2.5 -3
-5.5 -6

HALF_UP:

        向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向上舍入。如果被舍弃部分 >= 0.5,则舍入行为同 RoundingMode.UP;否则舍入行为同RoundingMode.DOWN。注意,此舍入模式就是通常学校里讲的四舍五入。

输入数字 使用 HALF_UP 舍入模式
将输入数字舍入为一位数
5.5 6
2.5 3
1.6 2
1.1 1
1.0 1
-1.0 -1
-1.1 -1
-1.6 -2
-2.5 -3
-5.5 -6

HALF_DOWN:

        向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向下舍入。如果被舍弃部分 > 0.5,则舍入行为同 RoundingMode.UP;否则舍入行为同RoundingMode.DOWN

输入数字 使用 HALF_DOWN 舍入模式
将输入数字舍入为一位数
5.5 5
2.5 2
1.6 2
1.1 1
1.0 1
-1.0 -1
-1.1 -1
-1.6 -2
-2.5 -2
-5.5 -5

HALF_EVEN:

            向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。如果舍弃部分左边的数字为奇数,则舍入行为同RoundingMode.HALF_UP;如果为偶数,则舍入行为同RoundingMode.HALF_DOWN。注意,在重复进行一系列计算时,此舍入模式可以在统计上将累加错误减到最小。此舍入模式也称为“银行家舍入法”,主要在美国使用。此舍入模式类似于 Java 中对float 和double 算法使用的舍入策略。

输入数字 使用 HALF_EVEN 舍入模式
将输入数字舍入为一位数
5.5 6
2.5 2
1.6 2
1.1 1
1.0 1
-1.0 -1
-1.1 -1
-1.6 -2
-2.5 -2
-5.5 -6

 UNNECESSARY:

        用于断言请求的操作具有精确结果的舍入模式,因此不需要舍入。如果对生成精确结果的操作指定此舍入模式,则抛出 ArithmeticException

输入数字 使用 UNNECESSARY 舍入模式
将输入数字舍入为一位数
5.5 抛出 ArithmeticException
2.5 抛出 ArithmeticException
1.6 抛出 ArithmeticException
1.1 抛出 ArithmeticException
1.0 1
-1.0 -1
-1.1 抛出 ArithmeticException
-1.6 抛出 ArithmeticException
-2.5 抛出 ArithmeticException
-5.5 抛出 ArithmeticException
常用取值四舍五入( HALF_UP) ;


猜你喜欢

转载自blog.csdn.net/yy455363056/article/details/79898241