| /* |
| * 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<RoundingMode, Double> 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<RoundingMode, Double> 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(); |
| } |
| } |