BigDecimal 报错处理 - java.lang.ArithmeticException: Non-terminating decimal expansion; no exact represe

 

问题记录:计算一个回收率的值时抛出异常 java.lang.ArithmeticException: Non-terminating decimal expansion; no exact represe

测试时,前几次都没有问题,但在某一次测试中,改变了一个可变的数值时,抛出异常。

问题定位:调用 BigDecimal 的 divide 方法时抛出异常,调用的方法是重载方法中,只有一个参数的 divide(BigDecimal divisor);
问题分析
       1.异常信息翻译:非终止十进制扩展;没有精确的可表示的十进制结果
       2.测试结果分析:前几次测试,divide的结果刚好是已除尽的数据,即非无限循环小数;异常的测试,结果是除不尽的小数(无限循环小数),即异常所表示的没有精确的可表示的十进制结果。
       3.方法参数解析:divide(BigDecimal divisor,  int scale, int roudingMode);
       (1)divisor,分母
       (2)scale,精确到小数点后几位;
                divide(divisor, roudingMode)时,若没有提前设置调用者的scale了,scale默认为0,即取整
       (3)roudingMode,约数规则,用BigDecimal的常量表示
             (如:BigDecimal.ROUND_HALF_UP:超过一半进一,即四舍五入)
解决方法:用其他两个方法计算商,注意自定义scale和roudingMode,之前是先计算后设置scale导致的问题,应在发生无限循环小数前处理。

深入分析处理

1.第二日想了一想,divide三个方法最终调用的底层方法是相同的,原则不变的还是参数的处理

2.且昨日分析到抛出异常的地方并不是真正抛出异常的地方;

直观异常处

BigDecimal 报错处理 - java.lang.ArithmeticException: Non-terminating decimal expansion; no exact represe_第1张图片
发现这里调用了另一个divide的双参方法,且参数MathContext 上面的注释说明:

/*
             * 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.
             */
如果商this/除数有一个终止的十进制扩展,
则扩展的位数不能超过(a.precision()+ceil(10*b.precision)/3)位数。
因此,创建一个具有此精度的MathContext对象,并使用不必要的舍入模式进行除法。

这里注意两个地方:(1)有终止的十进制扩展-可以除尽;(2)UNNECESSARY不必要的舍入模式;

(1)若是不可除尽的数,怎么办;

(2)ROUND_UNNECESSARY:舍入模式用于断言请求的操作具有精确的结果,因此不需要舍入。如果在生成不精确结果的操作上指定了此舍入模式,则抛出{@code arithmetricationexception}。

在第二点上是否就看出来了,然后我就一直 breakpoint 跟下去,基本都是对参数的各种判定处理,尽量返回一个精确的数据,最后到达抛出异常的地方
BigDecimal 报错处理 - java.lang.ArithmeticException: Non-terminating decimal expansion; no exact represe_第2张图片
完全对应(2),ROUND_UNNECESSARY 模式用于操作具有不精确结果时,将直接抛出异常。
当然我们实际处理中,很难用到 ROUND_UNNECESSARY 模式,除非用于一些特殊结果的判定。

另外,divide(BigDecimal divisor, int roudingMode); 和 divide(BigDecimal divisor); 最终抛出的异常信息也不一样;

原因:divide(BigDecimal divisor);在初始调用时,进行了异常捕获处理,并抛出了一个新的 ArithmeticException

 

测试:

public static void main(String[] args) {
		BigDecimal numerator = new BigDecimal(1);
		BigDecimal denominator = new BigDecimal(3);
		BigDecimal divide = numerator.divide(denominator, BigDecimal.ROUND_HALF_UP);
		System.out.println(divide);
		try {
			divide = numerator.divide(denominator);
			System.out.println(divide);
		}catch (Exception e) {
			System.out.println("divide(one)");
			e.printStackTrace();
		}
		try {
			divide = numerator.divide(denominator, BigDecimal.ROUND_UNNECESSARY);
			System.out.println(divide);
		}catch (Exception e) {
			System.out.println("divide(two, UNNECESSARY)");
			e.printStackTrace();
		}
	}

下面贴出相关源码:

divide(BigDecimal divisor):有捕获异常处理

/**
     * 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;
        }
    }

divide(BigDecimal divisor, int roundMode):未捕获异常再次处理

/**
     * Returns a {@code BigDecimal} whose value is {@code (this /
     * divisor)}, and whose scale is {@code this.scale()}.  If
     * rounding must be performed to generate a result with the given
     * scale, the specified rounding mode is applied.
     *
     * 

The new {@link #divide(BigDecimal, RoundingMode)} method * should be used in preference to this legacy method. * * @param divisor value by which this {@code BigDecimal} is to be divided. * @param roundingMode rounding mode to apply. * @return {@code this / divisor} * @throws ArithmeticException if {@code divisor==0}, or * {@code roundingMode==ROUND_UNNECESSARY} and * {@code this.scale()} is insufficient to represent the result * of the division exactly. * @throws IllegalArgumentException if {@code roundingMode} does not * represent a valid rounding mode. * @see #ROUND_UP * @see #ROUND_DOWN * @see #ROUND_CEILING * @see #ROUND_FLOOR * @see #ROUND_HALF_UP * @see #ROUND_HALF_DOWN * @see #ROUND_HALF_EVEN * @see #ROUND_UNNECESSARY */ public BigDecimal divide(BigDecimal divisor, int roundingMode) { return this.divide(divisor, scale, roundingMode); } public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) { if (roundingMode < ROUND_UP || roundingMode > ROUND_UNNECESSARY) throw new IllegalArgumentException("Invalid rounding mode"); if (this.intCompact != INFLATED) { if ((divisor.intCompact != INFLATED)) { return divide(this.intCompact, this.scale, divisor.intCompact, divisor.scale, scale, roundingMode); } else { return divide(this.intCompact, this.scale, divisor.intVal, divisor.scale, scale, roundingMode); } } else { if ((divisor.intCompact != INFLATED)) { return divide(this.intVal, this.scale, divisor.intCompact, divisor.scale, scale, roundingMode); } else { return divide(this.intVal, this.scale, divisor.intVal, divisor.scale, scale, roundingMode); } } }

实际抛出异常

/**
     * Shared logic of need increment computation.
     */
    private static boolean commonNeedIncrement(int roundingMode, int qsign,
                                        int cmpFracHalf, boolean oddQuot) {
        switch(roundingMode) {
        case ROUND_UNNECESSARY:
            throw new ArithmeticException("Rounding necessary");

        case ROUND_UP: // Away from zero
            return true;

        case ROUND_DOWN: // Towards zero
            return false;

        case ROUND_CEILING: // Towards +infinity
            return qsign > 0;

        case ROUND_FLOOR: // Towards -infinity
            return qsign < 0;

        default: // Some kind of half-way rounding
            assert roundingMode >= ROUND_HALF_UP &&
                roundingMode <= ROUND_HALF_EVEN: "Unexpected rounding mode" + RoundingMode.valueOf(roundingMode);

            if (cmpFracHalf < 0 ) // We're closer to higher digit
                return false;
            else if (cmpFracHalf > 0 ) // We're closer to lower digit
                return true;
            else { // half-way
                assert cmpFracHalf == 0;

                switch(roundingMode) {
                case ROUND_HALF_DOWN:
                    return false;

                case ROUND_HALF_UP:
                    return true;

                case ROUND_HALF_EVEN:
                    return oddQuot;

                default:
                    throw new AssertionError("Unexpected rounding mode" + roundingMode);
                }
            }
        }
    }




                  

你可能感兴趣的:(Java)