Skip to content

Commit

Permalink
classlib: extend BigInteger implementation with xValueExact() and sqrt()
Browse files Browse the repository at this point in the history
  • Loading branch information
tryone144 committed Jan 24, 2024
1 parent 6027f33 commit 95e0e3a
Show file tree
Hide file tree
Showing 2 changed files with 207 additions and 0 deletions.
114 changes: 114 additions & 0 deletions classlib/src/main/java/org/teavm/classlib/java/math/TBigInteger.java
Original file line number Diff line number Diff line change
Expand Up @@ -836,6 +836,20 @@ public TBigInteger andNot(TBigInteger val) {
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.
Expand All @@ -847,6 +861,21 @@ public int intValue() {
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.
Expand All @@ -860,6 +889,21 @@ public long longValue() {
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}
Expand Down Expand Up @@ -1102,6 +1146,76 @@ public TBigInteger pow(int exp) {
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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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());
}
}

0 comments on commit 95e0e3a

Please sign in to comment.