778 lines
28 KiB
Java
778 lines
28 KiB
Java
/*
|
|
* Copyright (C) 2011 The Guava Authors
|
|
*
|
|
* 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 com.google.common.math;
|
|
|
|
import static com.google.common.math.MathTesting.ALL_BIGINTEGER_CANDIDATES;
|
|
import static com.google.common.math.MathTesting.ALL_ROUNDING_MODES;
|
|
import static com.google.common.math.MathTesting.ALL_SAFE_ROUNDING_MODES;
|
|
import static com.google.common.math.MathTesting.NEGATIVE_BIGINTEGER_CANDIDATES;
|
|
import static com.google.common.math.MathTesting.NONZERO_BIGINTEGER_CANDIDATES;
|
|
import static com.google.common.math.MathTesting.POSITIVE_BIGINTEGER_CANDIDATES;
|
|
import static com.google.common.truth.Truth.assertThat;
|
|
import static com.google.common.truth.Truth.assertWithMessage;
|
|
import static java.math.BigInteger.ONE;
|
|
import static java.math.BigInteger.TEN;
|
|
import static java.math.BigInteger.ZERO;
|
|
import static java.math.RoundingMode.CEILING;
|
|
import static java.math.RoundingMode.DOWN;
|
|
import static java.math.RoundingMode.FLOOR;
|
|
import static java.math.RoundingMode.HALF_DOWN;
|
|
import static java.math.RoundingMode.HALF_EVEN;
|
|
import static java.math.RoundingMode.HALF_UP;
|
|
import static java.math.RoundingMode.UNNECESSARY;
|
|
import static java.math.RoundingMode.UP;
|
|
import static java.math.RoundingMode.values;
|
|
import static java.util.Arrays.asList;
|
|
|
|
import com.google.common.annotations.GwtCompatible;
|
|
import com.google.common.annotations.GwtIncompatible;
|
|
import com.google.common.testing.NullPointerTester;
|
|
import java.math.BigDecimal;
|
|
import java.math.BigInteger;
|
|
import java.math.RoundingMode;
|
|
import java.util.EnumMap;
|
|
import java.util.EnumSet;
|
|
import java.util.Map;
|
|
import junit.framework.TestCase;
|
|
|
|
/**
|
|
* Tests for BigIntegerMath.
|
|
*
|
|
* @author Louis Wasserman
|
|
*/
|
|
@GwtCompatible(emulated = true)
|
|
public class BigIntegerMathTest extends TestCase {
|
|
public void testCeilingPowerOfTwo() {
|
|
for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) {
|
|
BigInteger result = BigIntegerMath.ceilingPowerOfTwo(x);
|
|
assertTrue(BigIntegerMath.isPowerOfTwo(result));
|
|
assertTrue(result.compareTo(x) >= 0);
|
|
assertTrue(result.compareTo(x.add(x)) < 0);
|
|
}
|
|
}
|
|
|
|
public void testFloorPowerOfTwo() {
|
|
for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) {
|
|
BigInteger result = BigIntegerMath.floorPowerOfTwo(x);
|
|
assertTrue(BigIntegerMath.isPowerOfTwo(result));
|
|
assertTrue(result.compareTo(x) <= 0);
|
|
assertTrue(result.add(result).compareTo(x) > 0);
|
|
}
|
|
}
|
|
|
|
public void testCeilingPowerOfTwoNegative() {
|
|
for (BigInteger x : NEGATIVE_BIGINTEGER_CANDIDATES) {
|
|
try {
|
|
BigIntegerMath.ceilingPowerOfTwo(x);
|
|
fail("Expected IllegalArgumentException");
|
|
} catch (IllegalArgumentException expected) {
|
|
}
|
|
}
|
|
}
|
|
|
|
public void testFloorPowerOfTwoNegative() {
|
|
for (BigInteger x : NEGATIVE_BIGINTEGER_CANDIDATES) {
|
|
try {
|
|
BigIntegerMath.floorPowerOfTwo(x);
|
|
fail("Expected IllegalArgumentException");
|
|
} catch (IllegalArgumentException expected) {
|
|
}
|
|
}
|
|
}
|
|
|
|
public void testCeilingPowerOfTwoZero() {
|
|
try {
|
|
BigIntegerMath.ceilingPowerOfTwo(BigInteger.ZERO);
|
|
fail("Expected IllegalArgumentException");
|
|
} catch (IllegalArgumentException expected) {
|
|
}
|
|
}
|
|
|
|
public void testFloorPowerOfTwoZero() {
|
|
try {
|
|
BigIntegerMath.floorPowerOfTwo(BigInteger.ZERO);
|
|
fail("Expected IllegalArgumentException");
|
|
} catch (IllegalArgumentException expected) {
|
|
}
|
|
}
|
|
|
|
@GwtIncompatible // TODO
|
|
public void testConstantSqrt2PrecomputedBits() {
|
|
assertEquals(
|
|
BigIntegerMath.sqrt(
|
|
BigInteger.ZERO.setBit(2 * BigIntegerMath.SQRT2_PRECOMPUTE_THRESHOLD + 1), FLOOR),
|
|
BigIntegerMath.SQRT2_PRECOMPUTED_BITS);
|
|
}
|
|
|
|
public void testIsPowerOfTwo() {
|
|
for (BigInteger x : ALL_BIGINTEGER_CANDIDATES) {
|
|
// Checks for a single bit set.
|
|
boolean expected = x.signum() > 0 & x.and(x.subtract(ONE)).equals(ZERO);
|
|
assertEquals(expected, BigIntegerMath.isPowerOfTwo(x));
|
|
}
|
|
}
|
|
|
|
public void testLog2ZeroAlwaysThrows() {
|
|
for (RoundingMode mode : ALL_ROUNDING_MODES) {
|
|
try {
|
|
BigIntegerMath.log2(ZERO, mode);
|
|
fail("Expected IllegalArgumentException");
|
|
} catch (IllegalArgumentException expected) {
|
|
}
|
|
}
|
|
}
|
|
|
|
public void testLog2NegativeAlwaysThrows() {
|
|
for (RoundingMode mode : ALL_ROUNDING_MODES) {
|
|
try {
|
|
BigIntegerMath.log2(BigInteger.valueOf(-1), mode);
|
|
fail("Expected IllegalArgumentException");
|
|
} catch (IllegalArgumentException expected) {
|
|
}
|
|
}
|
|
}
|
|
|
|
public void testLog2Floor() {
|
|
for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) {
|
|
for (RoundingMode mode : asList(FLOOR, DOWN)) {
|
|
int result = BigIntegerMath.log2(x, mode);
|
|
assertTrue(ZERO.setBit(result).compareTo(x) <= 0);
|
|
assertTrue(ZERO.setBit(result + 1).compareTo(x) > 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void testLog2Ceiling() {
|
|
for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) {
|
|
for (RoundingMode mode : asList(CEILING, UP)) {
|
|
int result = BigIntegerMath.log2(x, mode);
|
|
assertTrue(ZERO.setBit(result).compareTo(x) >= 0);
|
|
assertTrue(result == 0 || ZERO.setBit(result - 1).compareTo(x) < 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Relies on the correctness of isPowerOfTwo(BigInteger).
|
|
public void testLog2Exact() {
|
|
for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) {
|
|
// We only expect an exception if x was not a power of 2.
|
|
boolean isPowerOf2 = BigIntegerMath.isPowerOfTwo(x);
|
|
try {
|
|
assertEquals(x, ZERO.setBit(BigIntegerMath.log2(x, UNNECESSARY)));
|
|
assertTrue(isPowerOf2);
|
|
} catch (ArithmeticException e) {
|
|
assertFalse(isPowerOf2);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void testLog2HalfUp() {
|
|
for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) {
|
|
int result = BigIntegerMath.log2(x, HALF_UP);
|
|
BigInteger x2 = x.pow(2);
|
|
// x^2 < 2^(2 * result + 1), or else we would have rounded up
|
|
assertTrue(ZERO.setBit(2 * result + 1).compareTo(x2) > 0);
|
|
// x^2 >= 2^(2 * result - 1), or else we would have rounded down
|
|
assertTrue(result == 0 || ZERO.setBit(2 * result - 1).compareTo(x2) <= 0);
|
|
}
|
|
}
|
|
|
|
public void testLog2HalfDown() {
|
|
for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) {
|
|
int result = BigIntegerMath.log2(x, HALF_DOWN);
|
|
BigInteger x2 = x.pow(2);
|
|
// x^2 <= 2^(2 * result + 1), or else we would have rounded up
|
|
assertTrue(ZERO.setBit(2 * result + 1).compareTo(x2) >= 0);
|
|
// x^2 > 2^(2 * result - 1), or else we would have rounded down
|
|
assertTrue(result == 0 || ZERO.setBit(2 * result - 1).compareTo(x2) < 0);
|
|
}
|
|
}
|
|
|
|
// Relies on the correctness of log2(BigInteger, {HALF_UP,HALF_DOWN}).
|
|
public void testLog2HalfEven() {
|
|
for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) {
|
|
int halfEven = BigIntegerMath.log2(x, HALF_EVEN);
|
|
// Now figure out what rounding mode we should behave like (it depends if FLOOR was
|
|
// odd/even).
|
|
boolean floorWasEven = (BigIntegerMath.log2(x, FLOOR) & 1) == 0;
|
|
assertEquals(BigIntegerMath.log2(x, floorWasEven ? HALF_DOWN : HALF_UP), halfEven);
|
|
}
|
|
}
|
|
|
|
@GwtIncompatible // TODO
|
|
public void testLog10ZeroAlwaysThrows() {
|
|
for (RoundingMode mode : ALL_ROUNDING_MODES) {
|
|
try {
|
|
BigIntegerMath.log10(ZERO, mode);
|
|
fail("Expected IllegalArgumentException");
|
|
} catch (IllegalArgumentException expected) {
|
|
}
|
|
}
|
|
}
|
|
|
|
@GwtIncompatible // TODO
|
|
public void testLog10NegativeAlwaysThrows() {
|
|
for (RoundingMode mode : ALL_ROUNDING_MODES) {
|
|
try {
|
|
BigIntegerMath.log10(BigInteger.valueOf(-1), mode);
|
|
fail("Expected IllegalArgumentException");
|
|
} catch (IllegalArgumentException expected) {
|
|
}
|
|
}
|
|
}
|
|
|
|
@GwtIncompatible // TODO
|
|
public void testLog10Floor() {
|
|
for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) {
|
|
for (RoundingMode mode : asList(FLOOR, DOWN)) {
|
|
int result = BigIntegerMath.log10(x, mode);
|
|
assertTrue(TEN.pow(result).compareTo(x) <= 0);
|
|
assertTrue(TEN.pow(result + 1).compareTo(x) > 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
@GwtIncompatible // TODO
|
|
public void testLog10Ceiling() {
|
|
for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) {
|
|
for (RoundingMode mode : asList(CEILING, UP)) {
|
|
int result = BigIntegerMath.log10(x, mode);
|
|
assertTrue(TEN.pow(result).compareTo(x) >= 0);
|
|
assertTrue(result == 0 || TEN.pow(result - 1).compareTo(x) < 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Relies on the correctness of log10(BigInteger, FLOOR).
|
|
@GwtIncompatible // TODO
|
|
public void testLog10Exact() {
|
|
for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) {
|
|
int logFloor = BigIntegerMath.log10(x, FLOOR);
|
|
boolean expectSuccess = TEN.pow(logFloor).equals(x);
|
|
try {
|
|
assertEquals(logFloor, BigIntegerMath.log10(x, UNNECESSARY));
|
|
assertTrue(expectSuccess);
|
|
} catch (ArithmeticException e) {
|
|
assertFalse(expectSuccess);
|
|
}
|
|
}
|
|
}
|
|
|
|
@GwtIncompatible // TODO
|
|
public void testLog10HalfUp() {
|
|
for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) {
|
|
int result = BigIntegerMath.log10(x, HALF_UP);
|
|
BigInteger x2 = x.pow(2);
|
|
// x^2 < 10^(2 * result + 1), or else we would have rounded up
|
|
assertTrue(TEN.pow(2 * result + 1).compareTo(x2) > 0);
|
|
// x^2 >= 10^(2 * result - 1), or else we would have rounded down
|
|
assertTrue(result == 0 || TEN.pow(2 * result - 1).compareTo(x2) <= 0);
|
|
}
|
|
}
|
|
|
|
@GwtIncompatible // TODO
|
|
public void testLog10HalfDown() {
|
|
for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) {
|
|
int result = BigIntegerMath.log10(x, HALF_DOWN);
|
|
BigInteger x2 = x.pow(2);
|
|
// x^2 <= 10^(2 * result + 1), or else we would have rounded up
|
|
assertTrue(TEN.pow(2 * result + 1).compareTo(x2) >= 0);
|
|
// x^2 > 10^(2 * result - 1), or else we would have rounded down
|
|
assertTrue(result == 0 || TEN.pow(2 * result - 1).compareTo(x2) < 0);
|
|
}
|
|
}
|
|
|
|
// Relies on the correctness of log10(BigInteger, {HALF_UP,HALF_DOWN}).
|
|
@GwtIncompatible // TODO
|
|
public void testLog10HalfEven() {
|
|
for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) {
|
|
int halfEven = BigIntegerMath.log10(x, HALF_EVEN);
|
|
// Now figure out what rounding mode we should behave like (it depends if FLOOR was
|
|
// odd/even).
|
|
boolean floorWasEven = (BigIntegerMath.log10(x, FLOOR) & 1) == 0;
|
|
assertEquals(BigIntegerMath.log10(x, floorWasEven ? HALF_DOWN : HALF_UP), halfEven);
|
|
}
|
|
}
|
|
|
|
@GwtIncompatible // TODO
|
|
public void testLog10TrivialOnPowerOf10() {
|
|
BigInteger x = BigInteger.TEN.pow(100);
|
|
for (RoundingMode mode : ALL_ROUNDING_MODES) {
|
|
assertEquals(100, BigIntegerMath.log10(x, mode));
|
|
}
|
|
}
|
|
|
|
@GwtIncompatible // TODO
|
|
public void testSqrtZeroAlwaysZero() {
|
|
for (RoundingMode mode : ALL_ROUNDING_MODES) {
|
|
assertEquals(ZERO, BigIntegerMath.sqrt(ZERO, mode));
|
|
}
|
|
}
|
|
|
|
@GwtIncompatible // TODO
|
|
public void testSqrtNegativeAlwaysThrows() {
|
|
for (RoundingMode mode : ALL_ROUNDING_MODES) {
|
|
try {
|
|
BigIntegerMath.sqrt(BigInteger.valueOf(-1), mode);
|
|
fail("Expected IllegalArgumentException");
|
|
} catch (IllegalArgumentException expected) {
|
|
}
|
|
}
|
|
}
|
|
|
|
@GwtIncompatible // TODO
|
|
public void testSqrtFloor() {
|
|
for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) {
|
|
for (RoundingMode mode : asList(FLOOR, DOWN)) {
|
|
BigInteger result = BigIntegerMath.sqrt(x, mode);
|
|
assertTrue(result.compareTo(ZERO) > 0);
|
|
assertTrue(result.pow(2).compareTo(x) <= 0);
|
|
assertTrue(result.add(ONE).pow(2).compareTo(x) > 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
@GwtIncompatible // TODO
|
|
public void testSqrtCeiling() {
|
|
for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) {
|
|
for (RoundingMode mode : asList(CEILING, UP)) {
|
|
BigInteger result = BigIntegerMath.sqrt(x, mode);
|
|
assertTrue(result.compareTo(ZERO) > 0);
|
|
assertTrue(result.pow(2).compareTo(x) >= 0);
|
|
assertTrue(result.signum() == 0 || result.subtract(ONE).pow(2).compareTo(x) < 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Relies on the correctness of sqrt(BigInteger, FLOOR).
|
|
@GwtIncompatible // TODO
|
|
public void testSqrtExact() {
|
|
for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) {
|
|
BigInteger floor = BigIntegerMath.sqrt(x, FLOOR);
|
|
// We only expect an exception if x was not a perfect square.
|
|
boolean isPerfectSquare = floor.pow(2).equals(x);
|
|
try {
|
|
assertEquals(floor, BigIntegerMath.sqrt(x, UNNECESSARY));
|
|
assertTrue(isPerfectSquare);
|
|
} catch (ArithmeticException e) {
|
|
assertFalse(isPerfectSquare);
|
|
}
|
|
}
|
|
}
|
|
|
|
@GwtIncompatible // TODO
|
|
public void testSqrtHalfUp() {
|
|
for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) {
|
|
BigInteger result = BigIntegerMath.sqrt(x, HALF_UP);
|
|
BigInteger plusHalfSquared = result.pow(2).add(result).shiftLeft(2).add(ONE);
|
|
BigInteger x4 = x.shiftLeft(2);
|
|
// sqrt(x) < result + 0.5, so 4 * x < (result + 0.5)^2 * 4
|
|
// (result + 0.5)^2 * 4 = (result^2 + result)*4 + 1
|
|
assertTrue(x4.compareTo(plusHalfSquared) < 0);
|
|
BigInteger minusHalfSquared = result.pow(2).subtract(result).shiftLeft(2).add(ONE);
|
|
// sqrt(x) > result - 0.5, so 4 * x > (result - 0.5)^2 * 4
|
|
// (result - 0.5)^2 * 4 = (result^2 - result)*4 + 1
|
|
assertTrue(result.equals(ZERO) || x4.compareTo(minusHalfSquared) >= 0);
|
|
}
|
|
}
|
|
|
|
@GwtIncompatible // TODO
|
|
public void testSqrtHalfDown() {
|
|
for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) {
|
|
BigInteger result = BigIntegerMath.sqrt(x, HALF_DOWN);
|
|
BigInteger plusHalfSquared = result.pow(2).add(result).shiftLeft(2).add(ONE);
|
|
BigInteger x4 = x.shiftLeft(2);
|
|
// sqrt(x) <= result + 0.5, so 4 * x <= (result + 0.5)^2 * 4
|
|
// (result + 0.5)^2 * 4 = (result^2 + result)*4 + 1
|
|
assertTrue(x4.compareTo(plusHalfSquared) <= 0);
|
|
BigInteger minusHalfSquared = result.pow(2).subtract(result).shiftLeft(2).add(ONE);
|
|
// sqrt(x) > result - 0.5, so 4 * x > (result - 0.5)^2 * 4
|
|
// (result - 0.5)^2 * 4 = (result^2 - result)*4 + 1
|
|
assertTrue(result.equals(ZERO) || x4.compareTo(minusHalfSquared) > 0);
|
|
}
|
|
}
|
|
|
|
// Relies on the correctness of sqrt(BigInteger, {HALF_UP,HALF_DOWN}).
|
|
@GwtIncompatible // TODO
|
|
public void testSqrtHalfEven() {
|
|
for (BigInteger x : POSITIVE_BIGINTEGER_CANDIDATES) {
|
|
BigInteger halfEven = BigIntegerMath.sqrt(x, HALF_EVEN);
|
|
// Now figure out what rounding mode we should behave like (it depends if FLOOR was
|
|
// odd/even).
|
|
boolean floorWasOdd = BigIntegerMath.sqrt(x, FLOOR).testBit(0);
|
|
assertEquals(BigIntegerMath.sqrt(x, floorWasOdd ? HALF_UP : HALF_DOWN), halfEven);
|
|
}
|
|
}
|
|
|
|
@GwtIncompatible // TODO
|
|
@AndroidIncompatible // slow
|
|
public void testDivNonZero() {
|
|
for (BigInteger p : NONZERO_BIGINTEGER_CANDIDATES) {
|
|
for (BigInteger q : NONZERO_BIGINTEGER_CANDIDATES) {
|
|
for (RoundingMode mode : ALL_SAFE_ROUNDING_MODES) {
|
|
BigInteger expected =
|
|
new BigDecimal(p).divide(new BigDecimal(q), 0, mode).toBigIntegerExact();
|
|
assertEquals(expected, BigIntegerMath.divide(p, q, mode));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static final BigInteger BAD_FOR_ANDROID_P = new BigInteger("-9223372036854775808");
|
|
private static final BigInteger BAD_FOR_ANDROID_Q = new BigInteger("-1");
|
|
|
|
private static final BigInteger BAD_FOR_GINGERBREAD_P = new BigInteger("-9223372036854775808");
|
|
private static final BigInteger BAD_FOR_GINGERBREAD_Q = new BigInteger("-4294967296");
|
|
|
|
@GwtIncompatible // TODO
|
|
@AndroidIncompatible // slow
|
|
public void testDivNonZeroExact() {
|
|
boolean isAndroid = System.getProperty("java.runtime.name").contains("Android");
|
|
for (BigInteger p : NONZERO_BIGINTEGER_CANDIDATES) {
|
|
for (BigInteger q : NONZERO_BIGINTEGER_CANDIDATES) {
|
|
if (isAndroid && p.equals(BAD_FOR_ANDROID_P) && q.equals(BAD_FOR_ANDROID_Q)) {
|
|
// https://code.google.com/p/android/issues/detail?id=196555
|
|
continue;
|
|
}
|
|
if (isAndroid && p.equals(BAD_FOR_GINGERBREAD_P) && q.equals(BAD_FOR_GINGERBREAD_Q)) {
|
|
// Works fine under Marshmallow, so I haven't filed a bug.
|
|
continue;
|
|
}
|
|
|
|
boolean dividesEvenly = p.remainder(q).equals(ZERO);
|
|
|
|
try {
|
|
BigInteger quotient = BigIntegerMath.divide(p, q, UNNECESSARY);
|
|
BigInteger undone = quotient.multiply(q);
|
|
if (!p.equals(undone)) {
|
|
failFormat("expected %s.multiply(%s) = %s; got %s", quotient, q, p, undone);
|
|
}
|
|
assertTrue(dividesEvenly);
|
|
} catch (ArithmeticException e) {
|
|
assertFalse(dividesEvenly);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@GwtIncompatible // TODO
|
|
public void testZeroDivIsAlwaysZero() {
|
|
for (BigInteger q : NONZERO_BIGINTEGER_CANDIDATES) {
|
|
for (RoundingMode mode : ALL_ROUNDING_MODES) {
|
|
assertEquals(ZERO, BigIntegerMath.divide(ZERO, q, mode));
|
|
}
|
|
}
|
|
}
|
|
|
|
@GwtIncompatible // TODO
|
|
public void testDivByZeroAlwaysFails() {
|
|
for (BigInteger p : ALL_BIGINTEGER_CANDIDATES) {
|
|
for (RoundingMode mode : ALL_ROUNDING_MODES) {
|
|
try {
|
|
BigIntegerMath.divide(p, ZERO, mode);
|
|
fail("Expected ArithmeticException");
|
|
} catch (ArithmeticException expected) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void testFactorial() {
|
|
BigInteger expected = BigInteger.ONE;
|
|
for (int i = 1; i <= 200; i++) {
|
|
expected = expected.multiply(BigInteger.valueOf(i));
|
|
assertEquals(expected, BigIntegerMath.factorial(i));
|
|
}
|
|
}
|
|
|
|
public void testFactorial0() {
|
|
assertEquals(BigInteger.ONE, BigIntegerMath.factorial(0));
|
|
}
|
|
|
|
public void testFactorialNegative() {
|
|
try {
|
|
BigIntegerMath.factorial(-1);
|
|
fail("Expected IllegalArgumentException");
|
|
} catch (IllegalArgumentException expected) {
|
|
}
|
|
}
|
|
|
|
public void testBinomialSmall() {
|
|
runBinomialTest(0, 30);
|
|
}
|
|
|
|
@GwtIncompatible // too slow
|
|
public void testBinomialLarge() {
|
|
runBinomialTest(31, 100);
|
|
}
|
|
|
|
// Depends on the correctness of BigIntegerMath.factorial
|
|
private static void runBinomialTest(int firstN, int lastN) {
|
|
for (int n = firstN; n <= lastN; n++) {
|
|
for (int k = 0; k <= n; k++) {
|
|
BigInteger expected =
|
|
BigIntegerMath.factorial(n)
|
|
.divide(BigIntegerMath.factorial(k))
|
|
.divide(BigIntegerMath.factorial(n - k));
|
|
assertEquals(expected, BigIntegerMath.binomial(n, k));
|
|
}
|
|
}
|
|
}
|
|
|
|
public void testBinomialOutside() {
|
|
for (int n = 0; n <= 50; n++) {
|
|
try {
|
|
BigIntegerMath.binomial(n, -1);
|
|
fail("Expected IllegalArgumentException");
|
|
} catch (IllegalArgumentException expected) {
|
|
}
|
|
try {
|
|
BigIntegerMath.binomial(n, n + 1);
|
|
fail("Expected IllegalArgumentException");
|
|
} catch (IllegalArgumentException expected) {
|
|
}
|
|
}
|
|
}
|
|
|
|
@GwtIncompatible
|
|
private static final class RoundToDoubleTester {
|
|
private final BigInteger input;
|
|
private final Map<RoundingMode, Double> expectedValues = new EnumMap<>(RoundingMode.class);
|
|
private boolean unnecessaryShouldThrow = false;
|
|
|
|
RoundToDoubleTester(BigInteger input) {
|
|
this.input = input;
|
|
}
|
|
|
|
RoundToDoubleTester setExpectation(double expectedValue, RoundingMode... modes) {
|
|
for (RoundingMode mode : modes) {
|
|
Double previous = expectedValues.put(mode, expectedValue);
|
|
if (previous != null) {
|
|
throw new AssertionError();
|
|
}
|
|
}
|
|
return this;
|
|
}
|
|
|
|
public RoundToDoubleTester roundUnnecessaryShouldThrow() {
|
|
unnecessaryShouldThrow = true;
|
|
return this;
|
|
}
|
|
|
|
public void test() {
|
|
assertThat(expectedValues.keySet())
|
|
.containsAtLeastElementsIn(EnumSet.complementOf(EnumSet.of(UNNECESSARY)));
|
|
for (Map.Entry<RoundingMode, Double> entry : expectedValues.entrySet()) {
|
|
RoundingMode mode = entry.getKey();
|
|
Double expectation = entry.getValue();
|
|
assertWithMessage("roundToDouble(" + input + ", " + mode + ")")
|
|
.that(BigIntegerMath.roundToDouble(input, mode))
|
|
.isEqualTo(expectation);
|
|
}
|
|
|
|
if (!expectedValues.containsKey(UNNECESSARY)) {
|
|
assertWithMessage("Expected roundUnnecessaryShouldThrow call")
|
|
.that(unnecessaryShouldThrow)
|
|
.isTrue();
|
|
try {
|
|
BigIntegerMath.roundToDouble(input, UNNECESSARY);
|
|
fail("Expected ArithmeticException for roundToDouble(" + input + ", UNNECESSARY)");
|
|
} catch (ArithmeticException expected) {
|
|
// expected
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@GwtIncompatible
|
|
public void testRoundToDouble_Zero() {
|
|
new RoundToDoubleTester(BigInteger.ZERO).setExpectation(0.0, values()).test();
|
|
}
|
|
|
|
@GwtIncompatible
|
|
public void testRoundToDouble_smallPositive() {
|
|
new RoundToDoubleTester(BigInteger.valueOf(16)).setExpectation(16.0, values()).test();
|
|
}
|
|
|
|
@GwtIncompatible
|
|
public void testRoundToDouble_maxPreciselyRepresentable() {
|
|
new RoundToDoubleTester(BigInteger.valueOf(1L << 53))
|
|
.setExpectation(Math.pow(2, 53), values())
|
|
.test();
|
|
}
|
|
|
|
@GwtIncompatible
|
|
public void testRoundToDouble_maxPreciselyRepresentablePlusOne() {
|
|
double twoToThe53 = Math.pow(2, 53);
|
|
// the representable doubles are 2^53 and 2^53 + 2.
|
|
// 2^53+1 is halfway between, so HALF_UP will go up and HALF_DOWN will go down.
|
|
new RoundToDoubleTester(BigInteger.valueOf((1L << 53) + 1))
|
|
.setExpectation(twoToThe53, DOWN, FLOOR, HALF_DOWN, HALF_EVEN)
|
|
.setExpectation(Math.nextUp(twoToThe53), CEILING, UP, HALF_UP)
|
|
.roundUnnecessaryShouldThrow()
|
|
.test();
|
|
}
|
|
|
|
@GwtIncompatible
|
|
public void testRoundToDouble_twoToThe54PlusOne() {
|
|
double twoToThe54 = Math.pow(2, 54);
|
|
// the representable doubles are 2^54 and 2^54 + 4
|
|
// 2^54+1 is less than halfway between, so HALF_DOWN and HALF_UP will both go down.
|
|
new RoundToDoubleTester(BigInteger.valueOf((1L << 54) + 1))
|
|
.setExpectation(twoToThe54, DOWN, FLOOR, HALF_DOWN, HALF_UP, HALF_EVEN)
|
|
.setExpectation(Math.nextUp(twoToThe54), CEILING, UP)
|
|
.roundUnnecessaryShouldThrow()
|
|
.test();
|
|
}
|
|
|
|
@GwtIncompatible
|
|
public void testRoundToDouble_twoToThe54PlusThree() {
|
|
double twoToThe54 = Math.pow(2, 54);
|
|
// the representable doubles are 2^54 and 2^54 + 4
|
|
// 2^54+3 is more than halfway between, so HALF_DOWN and HALF_UP will both go up.
|
|
new RoundToDoubleTester(BigInteger.valueOf((1L << 54) + 3))
|
|
.setExpectation(twoToThe54, DOWN, FLOOR)
|
|
.setExpectation(Math.nextUp(twoToThe54), CEILING, UP, HALF_DOWN, HALF_UP, HALF_EVEN)
|
|
.roundUnnecessaryShouldThrow()
|
|
.test();
|
|
}
|
|
|
|
@GwtIncompatible
|
|
public void testRoundToDouble_twoToThe54PlusFour() {
|
|
new RoundToDoubleTester(BigInteger.valueOf((1L << 54) + 4))
|
|
.setExpectation(Math.pow(2, 54) + 4, values())
|
|
.test();
|
|
}
|
|
|
|
@GwtIncompatible
|
|
public void testRoundToDouble_maxDouble() {
|
|
BigInteger maxDoubleAsBI = DoubleMath.roundToBigInteger(Double.MAX_VALUE, UNNECESSARY);
|
|
new RoundToDoubleTester(maxDoubleAsBI).setExpectation(Double.MAX_VALUE, values()).test();
|
|
}
|
|
|
|
@GwtIncompatible
|
|
public void testRoundToDouble_maxDoublePlusOne() {
|
|
BigInteger maxDoubleAsBI =
|
|
DoubleMath.roundToBigInteger(Double.MAX_VALUE, UNNECESSARY).add(BigInteger.ONE);
|
|
new RoundToDoubleTester(maxDoubleAsBI)
|
|
.setExpectation(Double.MAX_VALUE, DOWN, FLOOR, HALF_EVEN, HALF_UP, HALF_DOWN)
|
|
.setExpectation(Double.POSITIVE_INFINITY, UP, CEILING)
|
|
.roundUnnecessaryShouldThrow()
|
|
.test();
|
|
}
|
|
|
|
@GwtIncompatible
|
|
public void testRoundToDouble_wayTooBig() {
|
|
BigInteger bi = BigInteger.ONE.shiftLeft(2 * Double.MAX_EXPONENT);
|
|
new RoundToDoubleTester(bi)
|
|
.setExpectation(Double.MAX_VALUE, DOWN, FLOOR, HALF_EVEN, HALF_UP, HALF_DOWN)
|
|
.setExpectation(Double.POSITIVE_INFINITY, UP, CEILING)
|
|
.roundUnnecessaryShouldThrow()
|
|
.test();
|
|
}
|
|
|
|
@GwtIncompatible
|
|
public void testRoundToDouble_smallNegative() {
|
|
new RoundToDoubleTester(BigInteger.valueOf(-16)).setExpectation(-16.0, values()).test();
|
|
}
|
|
|
|
@GwtIncompatible
|
|
public void testRoundToDouble_minPreciselyRepresentable() {
|
|
new RoundToDoubleTester(BigInteger.valueOf(-1L << 53))
|
|
.setExpectation(-Math.pow(2, 53), values())
|
|
.test();
|
|
}
|
|
|
|
@GwtIncompatible
|
|
public void testRoundToDouble_minPreciselyRepresentableMinusOne() {
|
|
// the representable doubles are -2^53 and -2^53 - 2.
|
|
// -2^53-1 is halfway between, so HALF_UP will go up and HALF_DOWN will go down.
|
|
new RoundToDoubleTester(BigInteger.valueOf((-1L << 53) - 1))
|
|
.setExpectation(-Math.pow(2, 53), DOWN, CEILING, HALF_DOWN, HALF_EVEN)
|
|
.setExpectation(DoubleUtils.nextDown(-Math.pow(2, 53)), FLOOR, UP, HALF_UP)
|
|
.roundUnnecessaryShouldThrow()
|
|
.test();
|
|
}
|
|
|
|
@GwtIncompatible
|
|
public void testRoundToDouble_negativeTwoToThe54MinusOne() {
|
|
new RoundToDoubleTester(BigInteger.valueOf((-1L << 54) - 1))
|
|
.setExpectation(-Math.pow(2, 54), DOWN, CEILING, HALF_DOWN, HALF_UP, HALF_EVEN)
|
|
.setExpectation(DoubleUtils.nextDown(-Math.pow(2, 54)), FLOOR, UP)
|
|
.roundUnnecessaryShouldThrow()
|
|
.test();
|
|
}
|
|
|
|
@GwtIncompatible
|
|
public void testRoundToDouble_negativeTwoToThe54MinusThree() {
|
|
new RoundToDoubleTester(BigInteger.valueOf((-1L << 54) - 3))
|
|
.setExpectation(-Math.pow(2, 54), DOWN, CEILING)
|
|
.setExpectation(
|
|
DoubleUtils.nextDown(-Math.pow(2, 54)), FLOOR, UP, HALF_DOWN, HALF_UP, HALF_EVEN)
|
|
.roundUnnecessaryShouldThrow()
|
|
.test();
|
|
}
|
|
|
|
@GwtIncompatible
|
|
public void testRoundToDouble_negativeTwoToThe54MinusFour() {
|
|
new RoundToDoubleTester(BigInteger.valueOf((-1L << 54) - 4))
|
|
.setExpectation(-Math.pow(2, 54) - 4, values())
|
|
.test();
|
|
}
|
|
|
|
@GwtIncompatible
|
|
public void testRoundToDouble_minDouble() {
|
|
BigInteger minDoubleAsBI = DoubleMath.roundToBigInteger(-Double.MAX_VALUE, UNNECESSARY);
|
|
new RoundToDoubleTester(minDoubleAsBI).setExpectation(-Double.MAX_VALUE, values()).test();
|
|
}
|
|
|
|
@GwtIncompatible
|
|
public void testRoundToDouble_minDoubleMinusOne() {
|
|
BigInteger minDoubleAsBI =
|
|
DoubleMath.roundToBigInteger(-Double.MAX_VALUE, UNNECESSARY).subtract(BigInteger.ONE);
|
|
new RoundToDoubleTester(minDoubleAsBI)
|
|
.setExpectation(-Double.MAX_VALUE, DOWN, CEILING, HALF_EVEN, HALF_UP, HALF_DOWN)
|
|
.setExpectation(Double.NEGATIVE_INFINITY, UP, FLOOR)
|
|
.roundUnnecessaryShouldThrow()
|
|
.test();
|
|
}
|
|
|
|
@GwtIncompatible
|
|
public void testRoundToDouble_negativeWayTooBig() {
|
|
BigInteger bi = BigInteger.ONE.shiftLeft(2 * Double.MAX_EXPONENT).negate();
|
|
new RoundToDoubleTester(bi)
|
|
.setExpectation(-Double.MAX_VALUE, DOWN, CEILING, HALF_EVEN, HALF_UP, HALF_DOWN)
|
|
.setExpectation(Double.NEGATIVE_INFINITY, UP, FLOOR)
|
|
.roundUnnecessaryShouldThrow()
|
|
.test();
|
|
}
|
|
|
|
@GwtIncompatible // NullPointerTester
|
|
public void testNullPointers() {
|
|
NullPointerTester tester = new NullPointerTester();
|
|
tester.setDefault(BigInteger.class, ONE);
|
|
tester.setDefault(int.class, 1);
|
|
tester.setDefault(long.class, 1L);
|
|
tester.testAllPublicStaticMethods(BigIntegerMath.class);
|
|
}
|
|
|
|
@GwtIncompatible // String.format
|
|
private static void failFormat(String template, Object... args) {
|
|
fail(String.format(template, args));
|
|
}
|
|
}
|