/* * 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_INTEGER_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.EXPONENTS; import static com.google.common.math.MathTesting.NEGATIVE_INTEGER_CANDIDATES; import static com.google.common.math.MathTesting.NONZERO_INTEGER_CANDIDATES; import static com.google.common.math.MathTesting.POSITIVE_INTEGER_CANDIDATES; import static com.google.common.math.TestPlatform.intsCanGoOutOfRange; import static java.math.BigInteger.valueOf; import static java.math.RoundingMode.UNNECESSARY; import com.google.common.annotations.GwtCompatible; import junit.framework.TestCase; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; /** * Tests for {@link IntMath}. * * @author Louis Wasserman */ @GwtCompatible(emulated = true) public class IntMathTest extends TestCase { public void testLessThanBranchFree() { for (int x : ALL_INTEGER_CANDIDATES) { for (int y : ALL_INTEGER_CANDIDATES) { if (LongMath.fitsInInt((long) x - y)) { int expected = (x < y) ? 1 : 0; int actual = IntMath.lessThanBranchFree(x, y); assertEquals(expected, actual); } } } } public void testLog2ZeroAlwaysThrows() { for (RoundingMode mode : ALL_ROUNDING_MODES) { try { IntMath.log2(0, mode); fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException expected) {} } } public void testLog2NegativeAlwaysThrows() { for (int x : NEGATIVE_INTEGER_CANDIDATES) { for (RoundingMode mode : ALL_ROUNDING_MODES) { try { IntMath.log2(x, mode); fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException expected) {} } } } // Relies on the correctness of BigIntegrerMath.log2 for all modes except UNNECESSARY. public void testLog2MatchesBigInteger() { for (int x : POSITIVE_INTEGER_CANDIDATES) { for (RoundingMode mode : ALL_SAFE_ROUNDING_MODES) { assertEquals(BigIntegerMath.log2(valueOf(x), mode), IntMath.log2(x, mode)); } } } // Relies on the correctness of isPowerOfTwo(int). public void testLog2Exact() { for (int x : POSITIVE_INTEGER_CANDIDATES) { // We only expect an exception if x was not a power of 2. boolean isPowerOf2 = IntMath.isPowerOfTwo(x); try { assertEquals(x, 1 << IntMath.log2(x, UNNECESSARY)); assertTrue(isPowerOf2); } catch (ArithmeticException e) { assertFalse(isPowerOf2); } } } // Relies on the correctness of BigIntegerMath.log10 for all modes except UNNECESSARY. // Relies on the correctness of log10(int, FLOOR) and of pow(int, int). // Simple test to cover sqrt(0) for all types and all modes. /* Relies on the correctness of BigIntegerMath.sqrt for all modes except UNNECESSARY. */ /* Relies on the correctness of sqrt(int, FLOOR). */ public void testDivNonZero() { for (int p : NONZERO_INTEGER_CANDIDATES) { for (int q : NONZERO_INTEGER_CANDIDATES) { for (RoundingMode mode : ALL_SAFE_ROUNDING_MODES) { // Skip some tests that fail due to GWT's non-compliant int implementation. // TODO(cpovirk): does this test fail for only some rounding modes or for all? if (p == -2147483648 && q == -1 && intsCanGoOutOfRange()) { continue; } int expected = new BigDecimal(valueOf(p)).divide(new BigDecimal(valueOf(q)), 0, mode).intValue(); assertEquals(p + "/" + q, force32(expected), IntMath.divide(p, q, mode)); } } } } public void testDivNonZeroExact() { for (int p : NONZERO_INTEGER_CANDIDATES) { for (int q : NONZERO_INTEGER_CANDIDATES) { // Skip some tests that fail due to GWT's non-compliant int implementation. if (p == -2147483648 && q == -1 && intsCanGoOutOfRange()) { continue; } boolean dividesEvenly = (p % q) == 0; try { assertEquals(p + "/" + q, p, IntMath.divide(p, q, UNNECESSARY) * q); assertTrue(p + "/" + q + " not expected to divide evenly", dividesEvenly); } catch (ArithmeticException e) { assertFalse(p + "/" + q + " expected to divide evenly", dividesEvenly); } } } } public void testZeroDivIsAlwaysZero() { for (int q : NONZERO_INTEGER_CANDIDATES) { for (RoundingMode mode : ALL_ROUNDING_MODES) { assertEquals(0, IntMath.divide(0, q, mode)); } } } public void testDivByZeroAlwaysFails() { for (int p : ALL_INTEGER_CANDIDATES) { for (RoundingMode mode : ALL_ROUNDING_MODES) { try { IntMath.divide(p, 0, mode); fail("Expected ArithmeticException"); } catch (ArithmeticException expected) {} } } } public void testMod() { for (int x : ALL_INTEGER_CANDIDATES) { for (int m : POSITIVE_INTEGER_CANDIDATES) { assertEquals(valueOf(x).mod(valueOf(m)).intValue(), IntMath.mod(x, m)); } } } public void testModNegativeModulusFails() { for (int x : POSITIVE_INTEGER_CANDIDATES) { for (int m : NEGATIVE_INTEGER_CANDIDATES) { try { IntMath.mod(x, m); fail("Expected ArithmeticException"); } catch (ArithmeticException expected) {} } } } public void testModZeroModulusFails() { for (int x : ALL_INTEGER_CANDIDATES) { try { IntMath.mod(x, 0); fail("Expected ArithmeticException"); } catch (ArithmeticException expected) {} } } public void testGCD() { for (int a : POSITIVE_INTEGER_CANDIDATES) { for (int b : POSITIVE_INTEGER_CANDIDATES) { assertEquals(valueOf(a).gcd(valueOf(b)), valueOf(IntMath.gcd(a, b))); } } } public void testGCDZero() { for (int a : POSITIVE_INTEGER_CANDIDATES) { assertEquals(a, IntMath.gcd(a, 0)); assertEquals(a, IntMath.gcd(0, a)); } assertEquals(0, IntMath.gcd(0, 0)); } public void testGCDNegativePositiveThrows() { for (int a : NEGATIVE_INTEGER_CANDIDATES) { try { IntMath.gcd(a, 3); fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException expected) {} try { IntMath.gcd(3, a); fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException expected) {} } } public void testGCDNegativeZeroThrows() { for (int a : NEGATIVE_INTEGER_CANDIDATES) { try { IntMath.gcd(a, 0); fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException expected) {} try { IntMath.gcd(0, a); fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException expected) {} } } public void testCheckedAdd() { for (int a : ALL_INTEGER_CANDIDATES) { for (int b : ALL_INTEGER_CANDIDATES) { BigInteger expectedResult = valueOf(a).add(valueOf(b)); boolean expectedSuccess = fitsInInt(expectedResult); try { assertEquals(a + b, IntMath.checkedAdd(a, b)); assertTrue(expectedSuccess); } catch (ArithmeticException e) { assertFalse(expectedSuccess); } } } } public void testCheckedSubtract() { for (int a : ALL_INTEGER_CANDIDATES) { for (int b : ALL_INTEGER_CANDIDATES) { BigInteger expectedResult = valueOf(a).subtract(valueOf(b)); boolean expectedSuccess = fitsInInt(expectedResult); try { assertEquals(a - b, IntMath.checkedSubtract(a, b)); assertTrue(expectedSuccess); } catch (ArithmeticException e) { assertFalse(expectedSuccess); } } } } public void testCheckedMultiply() { for (int a : ALL_INTEGER_CANDIDATES) { for (int b : ALL_INTEGER_CANDIDATES) { BigInteger expectedResult = valueOf(a).multiply(valueOf(b)); boolean expectedSuccess = fitsInInt(expectedResult); try { assertEquals(a * b, IntMath.checkedMultiply(a, b)); assertTrue(expectedSuccess); } catch (ArithmeticException e) { assertFalse(expectedSuccess); } } } } public void testCheckedPow() { for (int b : ALL_INTEGER_CANDIDATES) { for (int k : EXPONENTS) { BigInteger expectedResult = valueOf(b).pow(k); boolean expectedSuccess = fitsInInt(expectedResult); try { assertEquals(b + "^" + k, force32(expectedResult.intValue()), IntMath.checkedPow(b, k)); assertTrue(b + "^" + k + " should have succeeded", expectedSuccess); } catch (ArithmeticException e) { assertFalse(b + "^" + k + " should have failed", expectedSuccess); } } } } // Depends on the correctness of BigIntegerMath.factorial. public void testFactorial() { for (int n = 0; n <= 50; n++) { BigInteger expectedBig = BigIntegerMath.factorial(n); int expectedInt = fitsInInt(expectedBig) ? expectedBig.intValue() : Integer.MAX_VALUE; assertEquals(expectedInt, IntMath.factorial(n)); } } public void testFactorialNegative() { for (int n : NEGATIVE_INTEGER_CANDIDATES) { try { IntMath.factorial(n); fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException expected) {} } } // Depends on the correctness of BigIntegerMath.binomial. /** * Helper method that asserts the arithmetic mean of x and y is equal * to the expectedMean. */ private static void assertMean(int expectedMean, int x, int y) { assertEquals("The expectedMean should be the same as computeMeanSafely", expectedMean, computeMeanSafely(x, y)); assertMean(x, y); } /** * Helper method that asserts the arithmetic mean of x and y is equal * to the result of computeMeanSafely. */ private static void assertMean(int x, int y) { int expectedMean = computeMeanSafely(x, y); assertEquals(expectedMean, IntMath.mean(x, y)); assertEquals("The mean of x and y should equal the mean of y and x", expectedMean, IntMath.mean(y, x)); } /** * Computes the mean in a way that is obvious and resilient to * overflow by using BigInteger arithmetic. */ private static int computeMeanSafely(int x, int y) { BigInteger bigX = BigInteger.valueOf(x); BigInteger bigY = BigInteger.valueOf(y); BigDecimal bigMean = new BigDecimal(bigX.add(bigY)) .divide(BigDecimal.valueOf(2), BigDecimal.ROUND_FLOOR); // parseInt blows up on overflow as opposed to intValue() which does not. return Integer.parseInt(bigMean.toString()); } private static boolean fitsInInt(BigInteger big) { return big.bitLength() <= 31; } private static int force32(int value) { // GWT doesn't consistently overflow values to make them 32-bit, so we need to force it. return value & 0xffffffff; } }