blob: ff86fd52f5f2557d416211b975c0eba225f9154d [file] [log] [blame]
/*
* 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();
}
}