diff --git a/classlib/src/main/java/org/teavm/classlib/java/math/TBigInteger.java b/classlib/src/main/java/org/teavm/classlib/java/math/TBigInteger.java index 9ceaabf4b..2f1686cb1 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/math/TBigInteger.java +++ b/classlib/src/main/java/org/teavm/classlib/java/math/TBigInteger.java @@ -836,6 +836,20 @@ public class TBigInteger extends Number implements Comparable, Seri return TLogical.andNot(this, val); } + public byte byteValueExact() { + if (numberLength > 1 || bitLength() > 7) { + throw new ArithmeticException("BigInteger out of byte range"); + } + return byteValue(); + } + + public short shortValueExact() { + if (numberLength > 1 || bitLength() > 15) { + throw new ArithmeticException("BigInteger out of short range"); + } + return shortValue(); + } + /** * Returns this {@code BigInteger} as an int value. If {@code this} is too * big to be represented as an int, then {@code this} % 2^32 is returned. @@ -847,6 +861,21 @@ public class TBigInteger extends Number implements Comparable, Seri return sign * digits[0]; } + /** + * Returns this {@code BigInter} as an int value. + * + * @return this {@code BigInteger} as an int value. + * @see #intValue + * @throws ArithmeticException + * if {@code this} is too big to be represented as an int. + */ + public int intValueExact() { + if (numberLength > 1 || bitLength() > 31) { + throw new ArithmeticException("BigInteger out of int range"); + } + return intValue(); + } + /** * Returns this {@code BigInteger} as an long value. If {@code this} is too * big to be represented as an long, then {@code this} % 2^64 is returned. @@ -860,6 +889,21 @@ public class TBigInteger extends Number implements Comparable, Seri return sign * value; } + /** + * Returns this {@code BigInter} as an long value. + * + * @return this {@code BigInteger} as a long value. + * @throws ArithmeticException + * if {@code this} is too big to be represented as a long. + * @see #longValue + */ + public long longValueExact() { + if (numberLength > 2 || bitLength() > 63) { + throw new ArithmeticException("BigInteger out of long range"); + } + return longValue(); + } + /** * Returns this {@code BigInteger} as an float value. If {@code this} is too * big to be represented as an float, then {@code Float.POSITIVE_INFINITY} @@ -1102,6 +1146,76 @@ public class TBigInteger extends Number implements Comparable, Seri return TMultiplication.pow(this, exp); } + /** + * Returns a new {@code BigInteger} whose value is the biggest integer + * {@code n} such that {@code n * n <= this}. + * + * @implNote This implementation follows the ideas in Henry S. Warren, Jr., + * Hacker's Delight (2nd ed.) (Addison Wesley, 2013), 279-282. + * + * @return {@code floor(sqrt(this))} + * @throws ArithmeticException if {@code this} is negative. + */ + public TBigInteger sqrt() { + if (sign < 0) { + throw new ArithmeticException("Negative BigInteger"); + } + + // Trivial cases + if (equals(ZERO)) { + return ZERO; + } else if (compareTo(valueOf(4)) < 0) { + return ONE; + } + + // BigInteger fits into long, so do calculation directly + if (bitLength() < 64) { + // Estimate using existing sqrt implementation for double + long val = longValueExact(); + long candidate = (long) Math.floor(Math.sqrt(val)); + + // Improve estimate using Newton's method + do { + long next = (candidate + val / candidate) >> 1; + if (next >= candidate) { + // found convergence candidate if stopped to decrease + return valueOf(candidate); + } + + candidate = next; + } while (true); + } + + // Shift BigInteger into long range to use existing sqrt implementation + // and then shift back into the initial range for a rough estimate + + long shiftCount = bitLength() - 63; + if (shiftCount % 2 == 1) { + shiftCount += 1; + } + + if ((shiftCount & 0xFFFFFFFF00000000L) > 0) { + throw new ArithmeticException("integer overflow"); + } + + double shiftedVal = shiftRight((int) shiftCount).doubleValue(); + TBigInteger candidate = valueOf((long) Math.ceil(Math.sqrt(shiftedVal))); + + candidate = candidate.shiftLeft((int) shiftCount >> 1); + + // Improve estimate using Newton's method + do { + // next = (candidate + this/candidate) >> 1; + TBigInteger next = candidate.add(this.divide(candidate)).shiftRight(1); + if (next.compareTo(candidate) >= 0) { + // found convergence candidate if stopped to decrease + return candidate; + } + + candidate = next; + } while (true); + } + /** * Returns a {@code BigInteger} array which contains {@code this / divisor} * at index 0 and {@code this % divisor} at index 1. diff --git a/tests/src/test/java/org/teavm/classlib/java/math/BigIntegerSquareRootTest.java b/tests/src/test/java/org/teavm/classlib/java/math/BigIntegerSquareRootTest.java new file mode 100644 index 000000000..755f5e17e --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/math/BigIntegerSquareRootTest.java @@ -0,0 +1,93 @@ +/* + * Copyright 2023 Bernd Busse. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.math; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.math.BigInteger; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.junit.SkipPlatform; +import org.teavm.junit.TeaVMTestRunner; +import org.teavm.junit.TestPlatform; + +@RunWith(TeaVMTestRunner.class) +@SkipPlatform(TestPlatform.WASI) +public class BigIntegerSquareRootTest { + /** + * sqrt: negative value + */ + @Test + public void testSqrtException() { + BigInteger aNumber = new BigInteger("-8"); + try { + aNumber.sqrt(); + fail("ArithmeticException has not been caught"); + } catch (ArithmeticException e) { + assertEquals("Improper exception message", "Negative BigInteger", e.getMessage()); + } + } + + /** + * sqrt: special cases + */ + @Test + public void testSpecialCases() { + BigInteger aNumber = new BigInteger("3"); + + assertEquals(BigInteger.ZERO, BigInteger.ZERO.sqrt()); + assertEquals(BigInteger.ONE, BigInteger.ONE.sqrt()); + assertEquals(BigInteger.ONE, aNumber.sqrt()); + } + + /** + * sqrt of small number + */ + @Test + public void testSmallNumbers() { + byte[] aBytes = {39, -128, 127}; + int aSign = 1; + byte[] rBytes = {6, 72}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.sqrt(); + byte[] resBytes = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for (int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * sqrt of large number + */ + @Test + public void testBigNumbers() { + byte[] aBytes = {1, 100, 56, 7, 98, -1, 39, -128, 127, 5, 6, 7, 8, 9}; + int aSign = 1; + byte[] rBytes = {18, -33, -82, -48, -58, 93, 37}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.sqrt(); + byte[] resBytes = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for (int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } +}