/* * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * @test * @bug 4851777 8233452 * @summary Tests of BigDecimal.sqrt(). */ import java.math.*; import java.util.*; import static java.math.BigDecimal.TEN; import static java.math.BigDecimal.ZERO; import static java.math.BigDecimal.valueOf; public class SquareRootTests { private static BigDecimal TWO = new BigDecimal(2); public static void main(String... args) { int failures = 0; failures += negativeTests(); failures += zeroTests(); failures += evenPowersOfTenTests(); failures += squareRootTwoTests(); failures += lowPrecisionPerfectSquares(); failures += almostFourRoundingDown(); failures += almostFourRoundingUp(); if (failures > 0 ) { throw new RuntimeException("Incurred " + failures + " failures" + " testing BigDecimal.sqrt()."); } } private static int negativeTests() { int failures = 0; for (long i = -10; i < 0; i++) { for (int j = -5; j < 5; j++) { try { BigDecimal input = BigDecimal.valueOf(i, j); BigDecimal result = input.sqrt(MathContext.DECIMAL64); System.err.println("Unexpected sqrt of negative: (" + input + ").sqrt() = " + result ); failures += 1; } catch (ArithmeticException e) { ; // Expected } } } return failures; } private static int zeroTests() { int failures = 0; for (int i = -100; i < 100; i++) { BigDecimal expected = BigDecimal.valueOf(0L, i/2); // These results are independent of rounding mode failures += compare(BigDecimal.valueOf(0L, i).sqrt(MathContext.UNLIMITED), expected, true, "zeros"); failures += compare(BigDecimal.valueOf(0L, i).sqrt(MathContext.DECIMAL64), expected, true, "zeros"); } return failures; } /** * sqrt(10^2N) is 10^N * Both numerical value and representation should be verified */ private static int evenPowersOfTenTests() { int failures = 0; MathContext oneDigitExactly = new MathContext(1, RoundingMode.UNNECESSARY); for (int scale = -100; scale <= 100; scale++) { BigDecimal testValue = BigDecimal.valueOf(1, 2*scale); BigDecimal expectedNumericalResult = BigDecimal.valueOf(1, scale); BigDecimal result; failures += equalNumerically(expectedNumericalResult, result = testValue.sqrt(MathContext.DECIMAL64), "Even powers of 10, DECIMAL64"); // Can round to one digit of precision exactly failures += equalNumerically(expectedNumericalResult, result = testValue.sqrt(oneDigitExactly), "even powers of 10, 1 digit"); if (result.precision() > 1) { failures += 1; System.err.println("Excess precision for " + result); } // If rounding to more than one digit, do precision / scale checking... } return failures; } private static int squareRootTwoTests() { int failures = 0; // Square root of 2 truncated to 65 digits BigDecimal highPrecisionRoot2 = new BigDecimal("1.41421356237309504880168872420969807856967187537694807317667973799"); RoundingMode[] modes = { RoundingMode.UP, RoundingMode.DOWN, RoundingMode.CEILING, RoundingMode.FLOOR, RoundingMode.HALF_UP, RoundingMode.HALF_DOWN, RoundingMode.HALF_EVEN }; // For each interesting rounding mode, for precisions 1 to, say // 63 numerically compare TWO.sqrt(mc) to // highPrecisionRoot2.round(mc) and the alternative internal high-precision // implementation of square root. for (RoundingMode mode : modes) { for (int precision = 1; precision < 63; precision++) { MathContext mc = new MathContext(precision, mode); BigDecimal expected = highPrecisionRoot2.round(mc); BigDecimal computed = TWO.sqrt(mc); BigDecimal altComputed = BigSquareRoot.sqrt(TWO, mc); failures += equalNumerically(expected, computed, "sqrt(2)"); failures += equalNumerically(computed, altComputed, "computed & altComputed"); } } return failures; } private static int lowPrecisionPerfectSquares() { int failures = 0; // For 5^2 through 9^2, if the input is rounded to one digit // first before the root is computed, the wrong answer will // result. Verify results and scale for different rounding // modes and precisions. long[][] squaresWithOneDigitRoot = {{ 4, 2}, { 9, 3}, {25, 5}, {36, 6}, {49, 7}, {64, 8}, {81, 9}}; for (long[] squareAndRoot : squaresWithOneDigitRoot) { BigDecimal square = new BigDecimal(squareAndRoot[0]); BigDecimal expected = new BigDecimal(squareAndRoot[1]); for (int scale = 0; scale <= 4; scale++) { BigDecimal scaledSquare = square.setScale(scale, RoundingMode.UNNECESSARY); int expectedScale = scale/2; for (int precision = 0; precision <= 5; precision++) { for (RoundingMode rm : RoundingMode.values()) { MathContext mc = new MathContext(precision, rm); BigDecimal computedRoot = scaledSquare.sqrt(mc); failures += equalNumerically(expected, computedRoot, "simple squares"); int computedScale = computedRoot.scale(); if (precision >= expectedScale + 1 && computedScale != expectedScale) { System.err.printf("%s\tprecision=%d\trm=%s%n", computedRoot.toString(), precision, rm); failures++; System.err.printf("\t%s does not have expected scale of %d%n.", computedRoot, expectedScale); } } } } } return failures; } /** * Test around 3.9999 that the result doesn't not improperly * round-up to a numerical value of 2. */ private static int almostFourRoundingDown() { int failures = 0; BigDecimal nearFour = new BigDecimal("3.999999999999999999999999999999"); // Sqrt root is 1.9999... for (int i = 1; i < 64; i++) { MathContext mc = new MathContext(i, RoundingMode.FLOOR); BigDecimal result = nearFour.sqrt(mc); BigDecimal expected = BigSquareRoot.sqrt(nearFour, mc); failures += equalNumerically(expected, result, "near four rounding down"); failures += (result.compareTo(TWO) < 0) ? 0 : 1 ; } return failures; } /** * Test around 4.000...1 that the result doesn't not improperly * round-down to a numerical value of 2. */ private static int almostFourRoundingUp() { int failures = 0; BigDecimal nearFour = new BigDecimal("4.000000000000000000000000000001"); // Sqrt root is 2.0000.... for (int i = 1; i < 64; i++) { MathContext mc = new MathContext(i, RoundingMode.CEILING); BigDecimal result = nearFour.sqrt(mc); BigDecimal expected = BigSquareRoot.sqrt(nearFour, mc); failures += equalNumerically(expected, result, "near four rounding down"); failures += (result.compareTo(TWO) > 0) ? 0 : 1 ; } return failures; } private static int compare(BigDecimal a, BigDecimal b, boolean expected, String prefix) { boolean result = a.equals(b); int failed = (result==expected) ? 0 : 1; if (failed == 1) { System.err.println("Testing " + prefix + "(" + a + ").compareTo(" + b + ") => " + result + "\n\tExpected " + expected); } return failed; } private static int equalNumerically(BigDecimal a, BigDecimal b, String prefix) { return compareNumerically(a, b, 0, prefix); } private static int compareNumerically(BigDecimal a, BigDecimal b, int expected, String prefix) { int result = a.compareTo(b); int failed = (result==expected) ? 0 : 1; if (failed == 1) { System.err.println("Testing " + prefix + "(" + a + ").compareTo(" + b + ") => " + result + "\n\tExpected " + expected); } return failed; } /** * Alternative implementation of BigDecimal square root which uses * higher-precision for a simpler set of termination conditions * for the Newton iteration. */ private static class BigSquareRoot { /** * 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); private static boolean isPowerOfTen(BigDecimal bd) { return BigInteger.ONE.equals(bd.unscaledValue()); } public static BigDecimal sqrt(BigDecimal bd, MathContext mc) { int signum = bd.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 follows, * 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 = bd.scale()/2; BigDecimal zeroWithFinalPreferredScale = BigDecimal.valueOf(0L, preferredScale); // First phase of numerical normalization, strip trailing // zeros and check for even powers of 10. BigDecimal stripped = bd.stripTrailingZeros(); int strippedScale = stripped.scale(); // Numerically sqrt(10^2N) = 10^N if (isPowerOfTen(stripped) && strippedScale % 2 == 0) { BigDecimal result = BigDecimal.valueOf(1L, strippedScale/2); if (result.scale() != preferredScale) { // Adjust to requested precision and preferred // scale as appropriate. result = result.add(zeroWithFinalPreferredScale, mc); } return result; } // 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 so that // the significand portion is to right of the decimal // point by roughly (scale() - precision() +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; } BigDecimal working = stripped.scaleByPowerOfTen(scaleAdjust); 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 a // // 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 was // 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 // an N digit number by itself 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(); int loopPrecision = Math.max(Math.max(2 * targetPrecision + 2, workingPrecision), 34); // Force at least // two Netwon // iterations on the // Math.sqrt result. do { int tmpPrecision = Math.max(Math.max(guessPrecision, targetPrecision + 2), workingPrecision); MathContext mcTmp = new MathContext(loopPrecision, RoundingMode.HALF_EVEN); // approx = 0.5 * (approx + fraction / approx) approx = ONE_HALF.multiply(approx.add(working.divide(approx, mcTmp), mcTmp)); guessPrecision *= 2; } while (guessPrecision < loopPrecision); 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 (bd.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)); } return result; } else { switch (signum) { case -1: throw new ArithmeticException("Attempted square root " + "of negative BigDecimal"); case 0: return valueOf(0L, bd.scale()/2); default: throw new AssertionError("Bad value from signum"); } } } } }