/* * Copyright (C) 2020 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.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; 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 com.google.common.annotations.GwtIncompatible; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; import java.util.EnumMap; import java.util.EnumSet; import java.util.Map; import junit.framework.TestCase; @GwtIncompatible public class BigDecimalMathTest extends TestCase { private static final class RoundToDoubleTester { private final BigDecimal input; private final Map expectedValues = new EnumMap<>(RoundingMode.class); private boolean unnecessaryShouldThrow = false; RoundToDoubleTester(BigDecimal 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 entry : expectedValues.entrySet()) { RoundingMode mode = entry.getKey(); Double expectation = entry.getValue(); assertWithMessage("roundToDouble(" + input + ", " + mode + ")") .that(BigDecimalMath.roundToDouble(input, mode)) .isEqualTo(expectation); } if (!expectedValues.containsKey(UNNECESSARY)) { assertWithMessage("Expected roundUnnecessaryShouldThrow call") .that(unnecessaryShouldThrow) .isTrue(); try { BigDecimalMath.roundToDouble(input, UNNECESSARY); fail("Expected ArithmeticException for roundToDouble(" + input + ", UNNECESSARY)"); } catch (ArithmeticException expected) { // expected } } } } public void testRoundToDouble_zero() { new RoundToDoubleTester(BigDecimal.ZERO).setExpectation(0.0, values()).test(); } public void testRoundToDouble_oneThird() { new RoundToDoubleTester( BigDecimal.ONE.divide(BigDecimal.valueOf(3), new MathContext(50, HALF_EVEN))) .roundUnnecessaryShouldThrow() .setExpectation(0.33333333333333337, UP, CEILING) .setExpectation(0.3333333333333333, HALF_EVEN, FLOOR, DOWN, HALF_UP, HALF_DOWN) .test(); } public void testRoundToDouble_halfMinDouble() { BigDecimal minDouble = new BigDecimal(Double.MIN_VALUE); BigDecimal halfMinDouble = minDouble.divide(BigDecimal.valueOf(2)); new RoundToDoubleTester(halfMinDouble) .roundUnnecessaryShouldThrow() .setExpectation(Double.MIN_VALUE, UP, CEILING, HALF_UP) .setExpectation(0.0, HALF_EVEN, FLOOR, DOWN, HALF_DOWN) .test(); } public void testRoundToDouble_halfNegativeMinDouble() { BigDecimal minDouble = new BigDecimal(-Double.MIN_VALUE); BigDecimal halfMinDouble = minDouble.divide(BigDecimal.valueOf(2)); new RoundToDoubleTester(halfMinDouble) .roundUnnecessaryShouldThrow() .setExpectation(-Double.MIN_VALUE, UP, FLOOR, HALF_UP) .setExpectation(-0.0, HALF_EVEN, CEILING, DOWN, HALF_DOWN) .test(); } public void testRoundToDouble_smallPositive() { new RoundToDoubleTester(BigDecimal.valueOf(16)).setExpectation(16.0, values()).test(); } public void testRoundToDouble_maxPreciselyRepresentable() { new RoundToDoubleTester(BigDecimal.valueOf(1L << 53)) .setExpectation(Math.pow(2, 53), values()) .test(); } 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(BigDecimal.valueOf((1L << 53) + 1)) .setExpectation(twoToThe53, DOWN, FLOOR, HALF_DOWN, HALF_EVEN) .setExpectation(Math.nextUp(twoToThe53), CEILING, UP, HALF_UP) .roundUnnecessaryShouldThrow() .test(); } 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(BigDecimal.valueOf((1L << 54) + 1)) .setExpectation(twoToThe54, DOWN, FLOOR, HALF_DOWN, HALF_UP, HALF_EVEN) .setExpectation(Math.nextUp(twoToThe54), CEILING, UP) .roundUnnecessaryShouldThrow() .test(); } public void testRoundToDouble_twoToThe54PlusOneHalf() { 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(BigDecimal.valueOf(1L << 54).add(new BigDecimal(0.5))) .setExpectation(twoToThe54, DOWN, FLOOR, HALF_DOWN, HALF_UP, HALF_EVEN) .setExpectation(Math.nextUp(twoToThe54), CEILING, UP) .roundUnnecessaryShouldThrow() .test(); } 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(BigDecimal.valueOf((1L << 54) + 3)) .setExpectation(twoToThe54, DOWN, FLOOR) .setExpectation(Math.nextUp(twoToThe54), CEILING, UP, HALF_DOWN, HALF_UP, HALF_EVEN) .roundUnnecessaryShouldThrow() .test(); } public void testRoundToDouble_twoToThe54PlusFour() { new RoundToDoubleTester(BigDecimal.valueOf((1L << 54) + 4)) .setExpectation(Math.pow(2, 54) + 4, values()) .test(); } public void testRoundToDouble_maxDouble() { BigDecimal maxDoubleAsBD = new BigDecimal(Double.MAX_VALUE); new RoundToDoubleTester(maxDoubleAsBD).setExpectation(Double.MAX_VALUE, values()).test(); } public void testRoundToDouble_maxDoublePlusOne() { BigDecimal maxDoubleAsBD = new BigDecimal(Double.MAX_VALUE).add(BigDecimal.ONE); new RoundToDoubleTester(maxDoubleAsBD) .setExpectation(Double.MAX_VALUE, DOWN, FLOOR, HALF_EVEN, HALF_UP, HALF_DOWN) .setExpectation(Double.POSITIVE_INFINITY, UP, CEILING) .roundUnnecessaryShouldThrow() .test(); } public void testRoundToDouble_wayTooBig() { BigDecimal bi = BigDecimal.valueOf(2).pow(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(); } public void testRoundToDouble_smallNegative() { new RoundToDoubleTester(BigDecimal.valueOf(-16)).setExpectation(-16.0, values()).test(); } public void testRoundToDouble_minPreciselyRepresentable() { new RoundToDoubleTester(BigDecimal.valueOf(-1L << 53)) .setExpectation(-Math.pow(2, 53), values()) .test(); } 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(BigDecimal.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(); } public void testRoundToDouble_negativeTwoToThe54MinusOne() { new RoundToDoubleTester(BigDecimal.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(); } public void testRoundToDouble_negativeTwoToThe54MinusThree() { new RoundToDoubleTester(BigDecimal.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(); } public void testRoundToDouble_negativeTwoToThe54MinusFour() { new RoundToDoubleTester(BigDecimal.valueOf((-1L << 54) - 4)) .setExpectation(-Math.pow(2, 54) - 4, values()) .test(); } public void testRoundToDouble_minDouble() { BigDecimal minDoubleAsBD = new BigDecimal(-Double.MAX_VALUE); new RoundToDoubleTester(minDoubleAsBD).setExpectation(-Double.MAX_VALUE, values()).test(); } public void testRoundToDouble_minDoubleMinusOne() { BigDecimal minDoubleAsBD = new BigDecimal(-Double.MAX_VALUE).subtract(BigDecimal.ONE); new RoundToDoubleTester(minDoubleAsBD) .setExpectation(-Double.MAX_VALUE, DOWN, CEILING, HALF_EVEN, HALF_UP, HALF_DOWN) .setExpectation(Double.NEGATIVE_INFINITY, UP, FLOOR) .roundUnnecessaryShouldThrow() .test(); } public void testRoundToDouble_negativeWayTooBig() { BigDecimal bi = BigDecimal.valueOf(2).pow(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(); } }