classlib: extend BigInteger implementation with xValueExact() and sqrt()

This commit is contained in:
Bernd Busse 2023-07-27 21:13:03 +02:00 committed by Alexey Andreev
parent 4e076a65ee
commit 83e3306071
2 changed files with 207 additions and 0 deletions

View File

@ -836,6 +836,20 @@ public class TBigInteger extends Number implements Comparable<TBigInteger>, 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<TBigInteger>, 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<TBigInteger>, 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<TBigInteger>, 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.

View File

@ -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());
}
}