< prev index next >

src/java.base/share/classes/java/math/BigDecimal.java

Print this page

        

*** 344,353 **** --- 344,363 ---- * @since 1.5 */ public static final BigDecimal TEN = ZERO_THROUGH_TEN[10]; + /** + * The value 0.1, with a scale of 1. + */ + private static final BigDecimal ONE_TENTH = valueOf(1L, 1); + + /** + * The value 0.5, with a scale of 1. + */ + private static final BigDecimal ONE_HALF = valueOf(5L, 1); + // Constructors /** * Trusted package private constructor. * Trusted simply means if val is INFLATED, intVal could not be null and
*** 1993,2002 **** --- 2003,2320 ---- result[0] = lhs.divideToIntegralValue(divisor, mc); result[1] = lhs.subtract(result[0].multiply(divisor)); return result; } + + /** + * Returns an approximation to the square root of {@code this} + * with rounding according to the context settings. + * + * <p>The preferred scale of the returned result is equal to + * {@code floor(this.scale()/2.0)}. The value of the returned + * result is always within one ulp of the exact decimal value for + * the precision in question. If the rounding mode is {@link + * RoundingMode#HALF_UP HALF_UP}, {@link RoundingMode#HALF_DOWN + * HALF_DOWN}, or {@link RoundingMode#HALF_EVEN HALF_EVEN}, the + * result is within one half an ulp of the exact decimal value. + * + * <p>Special case: + * <ul> + * <li> The square root of a number numerically equal to {@code + * ZERO} is equal to {@code ZERO}. + * </ul> + * + * @param mc the context to use. + * @return the square root of {@code this}. + * @throws ArithmeticException if {@code this} is less than zero. + * @throws ArithmeticException if an exact result is requested + * ({@code mc.getPrecision()==0}) and there is no finite decimal + * expansion of the exact result + * @throws ArithmeticException if + * {@code (mc.getRoundingMode()==RoundingMode.UNNECESSARY}) and + * the exact result cannot fit in {@code mc.getPrecision()} + * digits. + * @since 9 + */ + public BigDecimal sqrt(MathContext mc) { + int signum = signum(); + if (signum == 1) { + /* + * The following code draws on the algorithm presented in + * "Properly Rounded Variable Precision Square Root," Hull and + * Abrham, ACM Transactions on Mathematical Software, Vol 11, + * No. 3, September 1985, Pages 229-237. + * + * The BigDecimal computational model differs from the one + * presented in the paper in several ways: first BigDecimal + * numbers aren't necessarily normalized, second many more + * rounding modes are supported, including UNNECESSARY, and + * exact results can be requested. + * + * The main steps of the algorithm below are as follow, first + * argument reduce the value to the numerical range [1, 10) + * using the following relations: + * + * x = y * 10 ^ exp + * sqrt(x) = sqrt(y) * 10^(exp / 2) if exp is even + * sqrt(x) = sqrt(y/10) * 10 ^((exp+1)/2) is exp is odd + * + * Then use Newton's iteration on the reduced value to compute + * the numerical digits of the desired result. + * + * Finally, scale back to the desired exponent range and + * perform any adjustment to get the preferred scale in the + * representation. + */ + + // The code below favors relative simplicity over checking + // for special cases that could run faster. + + int preferredScale = this.scale()/2; + BigDecimal zeroWithFinalPreferredScale = valueOf(0L, preferredScale); + + // First phase of numerical normalization, strip trailing + // zeros and check for even powers of 10. + BigDecimal stripped = this.stripTrailingZeros(); + int strippedScale = stripped.scale(); + + // Numerically, sqrt(10^2N) = 10^N + if (stripped.isPowerOfTen() && + strippedScale % 2 == 0) { + BigDecimal result = valueOf(1L, strippedScale/2); + if (result.scale() != preferredScale) { + // Adjust to requested precision and preferred + // scale as appropriate. + result = result.add(zeroWithFinalPreferredScale, mc); + } + return result; + } + + System.out.println("\tStarting: " + this + "\tscale: " + this.scale()); // DEBUG + System.out.println("\tStripped: " + stripped + "\tscale: " + stripped.scale()); // DEBUG + + + // After stripTrailingZeros, the representation is normalized as + // + // unscaledValue * 10^(-scale) + // + // where unscaledValue is an integer with the mimimum + // precision for the cohort of the numerical value. To + // allow binary floating-point hardware to be used to get + // approximately a 15 digit approximation to the square + // root, it is helpful to instead normalize this as so + // that the significand portion is to right of the decimal + // point, roughly: + // + // (unscaledValue * (10^-precision) * 10^(-scale)) * (10^precision) + // + // so that + // + // sqrt(unscaledValue * (10^-precision) * 10^(-scale) * (10^precision)) = + // + // sqrt(unscaledValue * (10^-precision) * 10^(-scale)) * 10^(precision/2) + // + // Therefore, this adjustment occurs for by 10^-precision is precision is even or + // (adjust as needed, +/-1 + // + + // Now the precision / scale adjustment + int scaleAdjust = 0; + int scale = stripped.scale() - stripped.precision() + 1; + if (scale % 2 == 0) { + scaleAdjust = scale; + } else { + scaleAdjust = scale - 1; + // stripped = stripped.multiply(TEN); why not?? + } + + // For extreme values, large exponents with huge + // significands, this scale adjustment can overflow. (?) + BigDecimal working = stripped.scaleByPowerOfTen(scaleAdjust); + System.out.println("\tWorking2: " + working + "\tscale: " + working.scale() + + "\tadjust: " + scaleAdjust); // DEBUG + + assert // Verify 0.1 <= working < 10 + ONE_TENTH.compareTo(working) <= 0 && + working.compareTo(TEN) < 0; + + // Use good ole' Math.sqrt to get the initial guess for + // the Newton iteration, good to at least 15 decimal + // digits. This approach does incur the cost of + // + // BigDecimal -> double -> BigDecimal + // + // conversion cycle, but it avoids the need for several + // Newton iterations in BigDecimal arithmetic to get the + // working answer to 15 digits of precision. If many fewer + // than 15 digits were needed, it might be faster to do + // the loop entirely in BigDecimal arithmetic. + // + // (A double value might have as much many as 17 decimal + // digits of precision; it depends on the relative density + // of binary and decimal numbers at different regions of + // the number line.) + // + // (It would be possible to check for certain special + // cases to avoid doing any Newton iterations. For + // example, if the BigDecimal -> double conversion were + // known to be exact and the rounding mode had a + // low-enough precision, the post-Newton rounding logic + // could be applied directly.) + + BigDecimal guess = new BigDecimal(Math.sqrt(working.doubleValue())); + int guessPrecision = 15; + int originalPrecision = mc.getPrecision(); + int targetPrecision; + + // If an exact value is requested, it must only need about + // half of the input digits to represent since multiplying + // two N digit numbers yield a 2N-1 digit or 2N digit result. + if (originalPrecision == 0) { + targetPrecision = stripped.precision()/2 + 1; + } else { + targetPrecision = originalPrecision; + } + + // When setting the precision to use inside the Newton + // iteration loop, take care to avoid the case where the + // precision of the input exceeds the requested precision + // and rounding the input value too soon. + BigDecimal approx = guess; + int workingPrecision = working.precision(); + do { + // If did more work, could we make sure the + // approximation was uniformly above / below the root? + + // System.out.println(approx); // DEBUG + int tmpPrecision = Math.max(Math.max(guessPrecision, targetPrecision + 2), + workingPrecision); + MathContext mcTmp = new MathContext(tmpPrecision, RoundingMode.HALF_EVEN); + // approx = 0.5 * (approx + fraction / approx) + approx = ONE_HALF.multiply(approx.add(working.divide(approx, mcTmp), mcTmp)); + guessPrecision *= 2; + } while (guessPrecision < targetPrecision + 2); + + // Need additinal checking for HALF_EVEN since we are only + // using two extra precision digits and don't have a sticky bit? + BigDecimal result; + RoundingMode targetRm = mc.getRoundingMode(); + if (targetRm == RoundingMode.UNNECESSARY || originalPrecision == 0) { + RoundingMode tmpRm = + (targetRm == RoundingMode.UNNECESSARY) ? RoundingMode.DOWN : targetRm; + MathContext mcTmp = new MathContext(targetPrecision, tmpRm); + result = approx.scaleByPowerOfTen(-scaleAdjust/2).round(mcTmp); + + // If result*result != this numerically, the square + // root isn't exact + if (this.subtract(result.multiply(result)).compareTo(ZERO) != 0) { + throw new ArithmeticException("Computed square root not exact."); + } + } else { + result = approx.scaleByPowerOfTen(-scaleAdjust/2).round(mc); + } + + if (result.scale() != preferredScale) { + // The preferred scale of an add is + // max(addend.scale(), augend.scale()). Therefore, if + // the scale of the result is first minimized using + // stripTrailingZeros(), adding a zero of the + // preferred scale rounding the correct precision will + // perform the proper scale vs precision tradeoffs. + result = result.stripTrailingZeros(). + add(zeroWithFinalPreferredScale, + new MathContext(originalPrecision, RoundingMode.UNNECESSARY)); + } + assert squareRootResultAssertions(result, mc); + return result; + } else { + switch (signum) { + case -1: + throw new ArithmeticException("Attempted square root " + + "of negative BigDecimal"); + case 0: + return valueOf(0L, scale()/2); + + default: + throw new AssertionError("Bad value from signum"); + } + } + } + + private boolean isPowerOfTen() { + return BigInteger.ONE.equals(this.unscaledValue()); + } + + /** + * For nonzero values, check numerical correctness properties of + * the computed result for the chosen rounding mode . + * + * For the directed roundings, for DOWN and FLOOR, result^2 must + * be {@code <=} the input and (result+ulp)^2 must be {@code >} the + * input. Conversely, for UP and CEIL, result^2 must be {@code >=} the + * input and (result-ulp)^2 must be {@code <} the input. + */ + private boolean squareRootResultAssertions(BigDecimal result, MathContext mc) { + if (result.signum() == 0) { + return squareRootZeroResultAssertions(result, mc); + } else { + RoundingMode rm = mc.getRoundingMode(); + BigDecimal ulp = result.ulp(); + BigDecimal neighborUp = result.add(ulp); + // Make neighbor down accruate even for powers of ten + if (this.isPowerOfTen()) { + ulp = ulp.divide(TEN); + } + BigDecimal neighborDown = result.subtract(ulp); + + switch (rm) { + case DOWN: + case FLOOR: + return + result.multiply(result).compareTo(this) <= 0 && + neighborUp.multiply(neighborUp).compareTo(this) > 0; + + case UP: + case CEILING: + return + result.multiply(result).compareTo(this) >= 0 && + neighborDown.multiply(neighborDown).compareTo(this) < 0; + + case HALF_DOWN: + case HALF_EVEN: + case HALF_UP: + BigDecimal err = result.multiply(result).subtract(this).abs(); + BigDecimal errUp = neighborUp.multiply(neighborUp).subtract(this); + BigDecimal errDown = this.subtract(neighborDown.multiply(neighborDown)); + // All error values should be positive so don't need to + // compare absolute values. + + int err_comp_errUp = err.compareTo(errUp); + int err_comp_errDown = err.compareTo(errDown); + + return + errUp.signum() == 1 && + errDown.signum() == 1 && + + err_comp_errUp <= 0 && + err_comp_errDown <= 0 && + + ((err_comp_errUp == 0 ) ? err_comp_errDown < 0 : true) && + ((err_comp_errDown == 0 ) ? err_comp_errUp < 0 : true); + // && could check for digit conditions for ties too + + default: // Definition of UNNECESSARY already verified. + return true; + } + } + } + + private boolean squareRootZeroResultAssertions(BigDecimal result, MathContext mc) { + return true; // TBD + } + /** * Returns a {@code BigDecimal} whose value is * <code>(this<sup>n</sup>)</code>, The power is computed exactly, to * unlimited precision. *
< prev index next >