1 /* 2 * Copyright (c) 2016, 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* 25 * @test 26 * @bug 4851777 8233452 27 * @summary Tests of BigDecimal.sqrt(). 28 */ 29 30 import java.math.*; 31 import java.util.*; 32 33 import static java.math.BigDecimal.TEN; 34 import static java.math.BigDecimal.ZERO; 35 import static java.math.BigDecimal.valueOf; 36 37 public class SquareRootTests { 38 private static BigDecimal TWO = new BigDecimal(2); 39 40 public static void main(String... args) { 41 int failures = 0; 42 43 failures += negativeTests(); 44 failures += zeroTests(); 45 failures += evenPowersOfTenTests(); 46 failures += squareRootTwoTests(); 47 failures += lowPrecisionPerfectSquares(); 48 failures += almostFourRoundingDown(); 49 failures += almostFourRoundingUp(); 50 failures += nearTen(); 51 failures += nearOne(); 52 failures += halfWay(); 53 54 if (failures > 0 ) { 55 throw new RuntimeException("Incurred " + failures + " failures" + 56 " testing BigDecimal.sqrt()."); 57 } 58 } 59 60 private static int negativeTests() { 61 int failures = 0; 62 63 for (long i = -10; i < 0; i++) { 64 for (int j = -5; j < 5; j++) { 65 try { 66 BigDecimal input = BigDecimal.valueOf(i, j); 67 BigDecimal result = input.sqrt(MathContext.DECIMAL64); 68 System.err.println("Unexpected sqrt of negative: (" + 69 input + ").sqrt() = " + result ); 70 failures += 1; 71 } catch (ArithmeticException e) { 72 ; // Expected 73 } 74 } 75 } 76 77 return failures; 78 } 79 80 private static int zeroTests() { 81 int failures = 0; 82 83 for (int i = -100; i < 100; i++) { 84 BigDecimal expected = BigDecimal.valueOf(0L, i/2); 85 // These results are independent of rounding mode 86 failures += compare(BigDecimal.valueOf(0L, i).sqrt(MathContext.UNLIMITED), 87 expected, true, "zeros"); 88 89 failures += compare(BigDecimal.valueOf(0L, i).sqrt(MathContext.DECIMAL64), 90 expected, true, "zeros"); 91 } 92 93 return failures; 94 } 95 96 /** 97 * sqrt(10^2N) is 10^N 98 * Both numerical value and representation should be verified 99 */ 100 private static int evenPowersOfTenTests() { 101 int failures = 0; 102 MathContext oneDigitExactly = new MathContext(1, RoundingMode.UNNECESSARY); 103 104 for (int scale = -100; scale <= 100; scale++) { 105 BigDecimal testValue = BigDecimal.valueOf(1, 2*scale); 106 BigDecimal expectedNumericalResult = BigDecimal.valueOf(1, scale); 107 108 BigDecimal result; 109 110 failures += equalNumerically(expectedNumericalResult, 111 result = testValue.sqrt(MathContext.DECIMAL64), 112 "Even powers of 10, DECIMAL64"); 113 114 // Can round to one digit of precision exactly 115 failures += equalNumerically(expectedNumericalResult, 116 result = testValue.sqrt(oneDigitExactly), 117 "even powers of 10, 1 digit"); 118 if (result.precision() > 1) { 119 failures += 1; 120 System.err.println("Excess precision for " + result); 121 } 122 123 // If rounding to more than one digit, do precision / scale checking... 124 } 125 126 return failures; 127 } 128 129 private static int squareRootTwoTests() { 130 int failures = 0; 131 132 // Square root of 2 truncated to 65 digits 133 BigDecimal highPrecisionRoot2 = 134 new BigDecimal("1.41421356237309504880168872420969807856967187537694807317667973799"); 135 136 RoundingMode[] modes = { 137 RoundingMode.UP, RoundingMode.DOWN, 138 RoundingMode.CEILING, RoundingMode.FLOOR, 139 RoundingMode.HALF_UP, RoundingMode.HALF_DOWN, RoundingMode.HALF_EVEN 140 }; 141 142 143 // For each interesting rounding mode, for precisions 1 to, say, 144 // 63 numerically compare TWO.sqrt(mc) to 145 // highPrecisionRoot2.round(mc) and the alternative internal high-precision 146 // implementation of square root. 147 for (RoundingMode mode : modes) { 148 for (int precision = 1; precision < 63; precision++) { 149 MathContext mc = new MathContext(precision, mode); 150 BigDecimal expected = highPrecisionRoot2.round(mc); 151 BigDecimal computed = TWO.sqrt(mc); 152 BigDecimal altComputed = BigSquareRoot.sqrt(TWO, mc); 153 154 failures += equalNumerically(expected, computed, "sqrt(2)"); 155 failures += equalNumerically(computed, altComputed, "computed & altComputed"); 156 } 157 } 158 159 return failures; 160 } 161 162 private static int lowPrecisionPerfectSquares() { 163 int failures = 0; 164 165 // For 5^2 through 9^2, if the input is rounded to one digit 166 // first before the root is computed, the wrong answer will 167 // result. Verify results and scale for different rounding 168 // modes and precisions. 169 long[][] squaresWithOneDigitRoot = {{ 4, 2}, 170 { 9, 3}, 171 {25, 5}, 172 {36, 6}, 173 {49, 7}, 174 {64, 8}, 175 {81, 9}}; 176 177 for (long[] squareAndRoot : squaresWithOneDigitRoot) { 178 BigDecimal square = new BigDecimal(squareAndRoot[0]); 179 BigDecimal expected = new BigDecimal(squareAndRoot[1]); 180 181 for (int scale = 0; scale <= 4; scale++) { 182 BigDecimal scaledSquare = square.setScale(scale, RoundingMode.UNNECESSARY); 183 int expectedScale = scale/2; 184 for (int precision = 0; precision <= 5; precision++) { 185 for (RoundingMode rm : RoundingMode.values()) { 186 MathContext mc = new MathContext(precision, rm); 187 BigDecimal computedRoot = scaledSquare.sqrt(mc); 188 failures += equalNumerically(expected, computedRoot, "simple squares"); 189 int computedScale = computedRoot.scale(); 190 if (precision >= expectedScale + 1 && 191 computedScale != expectedScale) { 192 System.err.printf("%s\tprecision=%d\trm=%s%n", 193 computedRoot.toString(), precision, rm); 194 failures++; 195 System.err.printf("\t%s does not have expected scale of %d%n.", 196 computedRoot, expectedScale); 197 } 198 } 199 } 200 } 201 } 202 203 return failures; 204 } 205 206 /** 207 * Test around 3.9999 that the result doesn't not improperly 208 * round-up to a numerical value of 2. 209 */ 210 private static int almostFourRoundingDown() { 211 int failures = 0; 212 BigDecimal nearFour = new BigDecimal("3.999999999999999999999999999999"); 213 214 // Sqrt root is 1.9999... 215 216 for (int i = 1; i < 64; i++) { 217 MathContext mc = new MathContext(i, RoundingMode.FLOOR); 218 BigDecimal result = nearFour.sqrt(mc); 219 BigDecimal expected = BigSquareRoot.sqrt(nearFour, mc); 220 failures += equalNumerically(expected, result, "near four rounding down"); 221 failures += (result.compareTo(TWO) < 0) ? 0 : 1 ; 222 } 223 224 return failures; 225 } 226 227 /** 228 * Test around 4.000...1 that the result doesn't not improperly 229 * round-down to a numerical value of 2. 230 */ 231 private static int almostFourRoundingUp() { 232 int failures = 0; 233 BigDecimal nearFour = new BigDecimal("4.000000000000000000000000000001"); 234 235 // Sqrt root is 2.0000....<non-zero digits> 236 237 for (int i = 1; i < 64; i++) { 238 MathContext mc = new MathContext(i, RoundingMode.CEILING); 239 BigDecimal result = nearFour.sqrt(mc); 240 BigDecimal expected = BigSquareRoot.sqrt(nearFour, mc); 241 failures += equalNumerically(expected, result, "near four rounding down"); 242 failures += (result.compareTo(TWO) > 0) ? 0 : 1 ; 243 } 244 245 return failures; 246 } 247 248 private static int nearTen() { 249 int failures = 0; 250 251 BigDecimal near10 = new BigDecimal("9.99999999999999999999"); 252 253 BigDecimal near10sq = near10.multiply(near10); 254 255 BigDecimal near10sq_ulp = near10sq.add(near10sq.ulp()); 256 257 for (int i = 10; i < 23; i++) { 258 MathContext mc = new MathContext(i, RoundingMode.HALF_EVEN); 259 260 failures += equalNumerically(BigSquareRoot.sqrt(near10sq_ulp, mc), 261 near10sq_ulp.sqrt(mc), 262 "near 10 rounding down"); 263 } 264 265 return failures; 266 } 267 268 269 /* 270 * Probe for rounding failures near a power of ten, 1 = 10^0, 271 * where an ulp has a different size above and below the value. 272 */ 273 private static int nearOne() { 274 int failures = 0; 275 276 BigDecimal near1 = new BigDecimal(".999999999999999999999"); 277 BigDecimal near1sq = near1.multiply(near1); 278 BigDecimal near1sq_ulp = near1sq.add(near1sq.ulp()); 279 280 for (int i = 10; i < 23; i++) { 281 for (RoundingMode rm : List.of(RoundingMode.HALF_EVEN, 282 RoundingMode.UP, 283 RoundingMode.DOWN )) { 284 MathContext mc = new MathContext(i, rm); 285 failures += equalNumerically(BigSquareRoot.sqrt(near1sq_ulp, mc), 286 near1sq_ulp.sqrt(mc), 287 "near 1 half even"); 288 } 289 } 290 291 return failures; 292 } 293 294 295 private static int halfWay() { 296 int failures = 0; 297 298 /* 299 * Use enough digits that the exact result cannot be computed 300 * from the sqrt of a double. 301 */ 302 BigDecimal[] halfWayCases = { 303 // Odd next digit, truncate on HALF_EVEN 304 new BigDecimal("123456789123456789.5"), 305 306 // Even next digit, round up on HALF_EVEN 307 new BigDecimal("123456789123456788.5"), 308 }; 309 310 for (BigDecimal halfWayCase : halfWayCases) { 311 // Round result to next-to-last place 312 int precision = halfWayCase.precision() - 1; 313 BigDecimal square = halfWayCase.multiply(halfWayCase); 314 315 for (RoundingMode rm : List.of(RoundingMode.HALF_EVEN, 316 RoundingMode.HALF_UP, 317 RoundingMode.HALF_DOWN)) { 318 MathContext mc = new MathContext(precision, rm); 319 320 System.out.println("\nRounding mode " + rm); 321 System.out.println("\t" + halfWayCase.round(mc) + "\t" + halfWayCase); 322 /*System.out.println("\t" + square.sqrt(mc));*/ 323 System.out.println("\t" + BigSquareRoot.sqrt(square, mc)); 324 325 failures += equalNumerically(/*square.sqrt(mc),*/ 326 BigSquareRoot.sqrt(square, mc), 327 halfWayCase.round(mc), 328 "Rounding halway " + rm); 329 } 330 } 331 332 return failures; 333 } 334 335 private static int compare(BigDecimal a, BigDecimal b, boolean expected, String prefix) { 336 boolean result = a.equals(b); 337 int failed = (result==expected) ? 0 : 1; 338 if (failed == 1) { 339 System.err.println("Testing " + prefix + 340 "(" + a + ").compareTo(" + b + ") => " + result + 341 "\n\tExpected " + expected); 342 } 343 return failed; 344 } 345 346 private static int equalNumerically(BigDecimal a, BigDecimal b, 347 String prefix) { 348 return compareNumerically(a, b, 0, prefix); 349 } 350 351 352 private static int compareNumerically(BigDecimal a, BigDecimal b, 353 int expected, String prefix) { 354 int result = a.compareTo(b); 355 int failed = (result==expected) ? 0 : 1; 356 if (failed == 1) { 357 System.err.println("Testing " + prefix + 358 "(" + a + ").compareTo(" + b + ") => " + result + 359 "\n\tExpected " + expected); 360 } 361 return failed; 362 } 363 364 /** 365 * Alternative implementation of BigDecimal square root which uses 366 * higher-precision for a simpler set of termination conditions 367 * for the Newton iteration. 368 */ 369 private static class BigSquareRoot { 370 /** 371 * The value 0.1, with a scale of 1. 372 */ 373 private static final BigDecimal ONE_TENTH = valueOf(1L, 1); 374 375 /** 376 * The value 0.5, with a scale of 1. 377 */ 378 private static final BigDecimal ONE_HALF = valueOf(5L, 1); 379 380 private static boolean isPowerOfTen(BigDecimal bd) { 381 return BigInteger.ONE.equals(bd.unscaledValue()); 382 } 383 384 public static BigDecimal sqrt(BigDecimal bd, MathContext mc) { 385 int signum = bd.signum(); 386 if (signum == 1) { 387 /* 388 * The following code draws on the algorithm presented in 389 * "Properly Rounded Variable Precision Square Root," Hull and 390 * Abrham, ACM Transactions on Mathematical Software, Vol 11, 391 * No. 3, September 1985, Pages 229-237. 392 * 393 * The BigDecimal computational model differs from the one 394 * presented in the paper in several ways: first BigDecimal 395 * numbers aren't necessarily normalized, second many more 396 * rounding modes are supported, including UNNECESSARY, and 397 * exact results can be requested. 398 * 399 * The main steps of the algorithm below are as follows, 400 * first argument reduce the value to the numerical range 401 * [1, 10) using the following relations: 402 * 403 * x = y * 10 ^ exp 404 * sqrt(x) = sqrt(y) * 10^(exp / 2) if exp is even 405 * sqrt(x) = sqrt(y/10) * 10 ^((exp+1)/2) is exp is odd 406 * 407 * Then use Newton's iteration on the reduced value to compute 408 * the numerical digits of the desired result. 409 * 410 * Finally, scale back to the desired exponent range and 411 * perform any adjustment to get the preferred scale in the 412 * representation. 413 */ 414 415 // The code below favors relative simplicity over checking 416 // for special cases that could run faster. 417 418 int preferredScale = bd.scale()/2; 419 BigDecimal zeroWithFinalPreferredScale = 420 BigDecimal.valueOf(0L, preferredScale); 421 422 // First phase of numerical normalization, strip trailing 423 // zeros and check for even powers of 10. 424 BigDecimal stripped = bd.stripTrailingZeros(); 425 int strippedScale = stripped.scale(); 426 427 // Numerically sqrt(10^2N) = 10^N 428 if (isPowerOfTen(stripped) && 429 strippedScale % 2 == 0) { 430 BigDecimal result = BigDecimal.valueOf(1L, strippedScale/2); 431 if (result.scale() != preferredScale) { 432 // Adjust to requested precision and preferred 433 // scale as appropriate. 434 result = result.add(zeroWithFinalPreferredScale, mc); 435 } 436 return result; 437 } 438 439 // After stripTrailingZeros, the representation is normalized as 440 // 441 // unscaledValue * 10^(-scale) 442 // 443 // where unscaledValue is an integer with the mimimum 444 // precision for the cohort of the numerical value. To 445 // allow binary floating-point hardware to be used to get 446 // approximately a 15 digit approximation to the square 447 // root, it is helpful to instead normalize this so that 448 // the significand portion is to right of the decimal 449 // point by roughly (scale() - precision() +1). 450 451 // Now the precision / scale adjustment 452 int scaleAdjust = 0; 453 int scale = stripped.scale() - stripped.precision() + 1; 454 if (scale % 2 == 0) { 455 scaleAdjust = scale; 456 } else { 457 scaleAdjust = scale - 1; 458 } 459 460 BigDecimal working = stripped.scaleByPowerOfTen(scaleAdjust); 461 462 assert // Verify 0.1 <= working < 10 463 ONE_TENTH.compareTo(working) <= 0 && working.compareTo(TEN) < 0; 464 465 // Use good ole' Math.sqrt to get the initial guess for 466 // the Newton iteration, good to at least 15 decimal 467 // digits. This approach does incur the cost of a 468 // 469 // BigDecimal -> double -> BigDecimal 470 // 471 // conversion cycle, but it avoids the need for several 472 // Newton iterations in BigDecimal arithmetic to get the 473 // working answer to 15 digits of precision. If many fewer 474 // than 15 digits were needed, it might be faster to do 475 // the loop entirely in BigDecimal arithmetic. 476 // 477 // (A double value might have as much many as 17 decimal 478 // digits of precision; it depends on the relative density 479 // of binary and decimal numbers at different regions of 480 // the number line.) 481 // 482 // (It would be possible to check for certain special 483 // cases to avoid doing any Newton iterations. For 484 // example, if the BigDecimal -> double conversion was 485 // known to be exact and the rounding mode had a 486 // low-enough precision, the post-Newton rounding logic 487 // could be applied directly.) 488 489 BigDecimal guess = new BigDecimal(Math.sqrt(working.doubleValue())); 490 int guessPrecision = 15; 491 int originalPrecision = mc.getPrecision(); 492 int targetPrecision; 493 494 // If an exact value is requested, it must only need about 495 // half of the input digits to represent since multiplying 496 // an N digit number by itself yield a 2N-1 digit or 2N 497 // digit result. 498 if (originalPrecision == 0) { 499 targetPrecision = stripped.precision()/2 + 1; 500 } else { 501 targetPrecision = originalPrecision; 502 } 503 504 // When setting the precision to use inside the Newton 505 // iteration loop, take care to avoid the case where the 506 // precision of the input exceeds the requested precision 507 // and rounding the input value too soon. 508 BigDecimal approx = guess; 509 int workingPrecision = working.precision(); 510 // Use "2p + 2" property to guarantee enough 511 // intermediate precision so that a double-rounding 512 // error does not occur when rounded to the final 513 // destination precision. 514 int loopPrecision = Math.max(Math.max(2 * targetPrecision + 2, 515 workingPrecision), 516 34); // Force at least 517 // two Netwon 518 // iterations on the 519 // Math.sqrt result. 520 do { 521 int tmpPrecision = Math.max(Math.max(guessPrecision, targetPrecision + 2), 522 workingPrecision); 523 MathContext mcTmp = new MathContext(loopPrecision, RoundingMode.HALF_EVEN); 524 // approx = 0.5 * (approx + fraction / approx) 525 approx = ONE_HALF.multiply(approx.add(working.divide(approx, mcTmp), mcTmp)); 526 guessPrecision *= 2; 527 } while (guessPrecision < loopPrecision); 528 529 BigDecimal result; 530 RoundingMode targetRm = mc.getRoundingMode(); 531 if (targetRm == RoundingMode.UNNECESSARY || originalPrecision == 0) { 532 RoundingMode tmpRm = 533 (targetRm == RoundingMode.UNNECESSARY) ? RoundingMode.DOWN : targetRm; 534 MathContext mcTmp = new MathContext(targetPrecision, tmpRm); 535 result = approx.scaleByPowerOfTen(-scaleAdjust/2).round(mcTmp); 536 537 // If result*result != this numerically, the square 538 // root isn't exact 539 if (bd.subtract(result.multiply(result)).compareTo(ZERO) != 0) { 540 throw new ArithmeticException("Computed square root not exact."); 541 } 542 } else { 543 result = approx.scaleByPowerOfTen(-scaleAdjust/2).round(mc); 544 } 545 546 if (result.scale() != preferredScale) { 547 // The preferred scale of an add is 548 // max(addend.scale(), augend.scale()). Therefore, if 549 // the scale of the result is first minimized using 550 // stripTrailingZeros(), adding a zero of the 551 // preferred scale rounding the correct precision will 552 // perform the proper scale vs precision tradeoffs. 553 result = result.stripTrailingZeros(). 554 add(zeroWithFinalPreferredScale, 555 new MathContext(originalPrecision, RoundingMode.UNNECESSARY)); 556 } 557 return result; 558 } else { 559 switch (signum) { 560 case -1: 561 throw new ArithmeticException("Attempted square root " + 562 "of negative BigDecimal"); 563 case 0: 564 return valueOf(0L, bd.scale()/2); 565 566 default: 567 throw new AssertionError("Bad value from signum"); 568 } 569 } 570 } 571 } 572 }