1 /* 2 * Copyright (C) 2020 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 15 package com.google.common.math; 16 17 import static com.google.common.truth.Truth.assertThat; 18 import static com.google.common.truth.Truth.assertWithMessage; 19 import static java.math.RoundingMode.CEILING; 20 import static java.math.RoundingMode.DOWN; 21 import static java.math.RoundingMode.FLOOR; 22 import static java.math.RoundingMode.HALF_DOWN; 23 import static java.math.RoundingMode.HALF_EVEN; 24 import static java.math.RoundingMode.HALF_UP; 25 import static java.math.RoundingMode.UNNECESSARY; 26 import static java.math.RoundingMode.UP; 27 import static java.math.RoundingMode.values; 28 import static org.junit.Assert.assertThrows; 29 30 import com.google.common.annotations.GwtIncompatible; 31 import java.math.BigDecimal; 32 import java.math.MathContext; 33 import java.math.RoundingMode; 34 import java.util.EnumMap; 35 import java.util.EnumSet; 36 import java.util.Map; 37 import junit.framework.TestCase; 38 39 @GwtIncompatible 40 public class BigDecimalMathTest extends TestCase { 41 private static final class RoundToDoubleTester { 42 private final BigDecimal input; 43 private final Map<RoundingMode, Double> expectedValues = new EnumMap<>(RoundingMode.class); 44 private boolean unnecessaryShouldThrow = false; 45 RoundToDoubleTester(BigDecimal input)46 RoundToDoubleTester(BigDecimal input) { 47 this.input = input; 48 } 49 setExpectation(double expectedValue, RoundingMode... modes)50 RoundToDoubleTester setExpectation(double expectedValue, RoundingMode... modes) { 51 for (RoundingMode mode : modes) { 52 Double previous = expectedValues.put(mode, expectedValue); 53 if (previous != null) { 54 throw new AssertionError(); 55 } 56 } 57 return this; 58 } 59 roundUnnecessaryShouldThrow()60 public RoundToDoubleTester roundUnnecessaryShouldThrow() { 61 unnecessaryShouldThrow = true; 62 return this; 63 } 64 test()65 public void test() { 66 assertThat(expectedValues.keySet()) 67 .containsAtLeastElementsIn(EnumSet.complementOf(EnumSet.of(UNNECESSARY))); 68 for (Map.Entry<RoundingMode, Double> entry : expectedValues.entrySet()) { 69 RoundingMode mode = entry.getKey(); 70 Double expectation = entry.getValue(); 71 assertWithMessage("roundToDouble(" + input + ", " + mode + ")") 72 .that(BigDecimalMath.roundToDouble(input, mode)) 73 .isEqualTo(expectation); 74 } 75 76 if (!expectedValues.containsKey(UNNECESSARY)) { 77 assertWithMessage("Expected roundUnnecessaryShouldThrow call") 78 .that(unnecessaryShouldThrow) 79 .isTrue(); 80 assertThrows( 81 "Expected ArithmeticException for roundToDouble(" + input + ", UNNECESSARY)", 82 ArithmeticException.class, 83 () -> BigDecimalMath.roundToDouble(input, UNNECESSARY)); 84 } 85 } 86 } 87 testRoundToDouble_zero()88 public void testRoundToDouble_zero() { 89 new RoundToDoubleTester(BigDecimal.ZERO).setExpectation(0.0, values()).test(); 90 } 91 testRoundToDouble_oneThird()92 public void testRoundToDouble_oneThird() { 93 new RoundToDoubleTester( 94 BigDecimal.ONE.divide(BigDecimal.valueOf(3), new MathContext(50, HALF_EVEN))) 95 .roundUnnecessaryShouldThrow() 96 .setExpectation(0.33333333333333337, UP, CEILING) 97 .setExpectation(0.3333333333333333, HALF_EVEN, FLOOR, DOWN, HALF_UP, HALF_DOWN) 98 .test(); 99 } 100 testRoundToDouble_halfMinDouble()101 public void testRoundToDouble_halfMinDouble() { 102 BigDecimal minDouble = new BigDecimal(Double.MIN_VALUE); 103 BigDecimal halfMinDouble = minDouble.divide(BigDecimal.valueOf(2)); 104 new RoundToDoubleTester(halfMinDouble) 105 .roundUnnecessaryShouldThrow() 106 .setExpectation(Double.MIN_VALUE, UP, CEILING, HALF_UP) 107 .setExpectation(0.0, HALF_EVEN, FLOOR, DOWN, HALF_DOWN) 108 .test(); 109 } 110 testRoundToDouble_halfNegativeMinDouble()111 public void testRoundToDouble_halfNegativeMinDouble() { 112 BigDecimal minDouble = new BigDecimal(-Double.MIN_VALUE); 113 BigDecimal halfMinDouble = minDouble.divide(BigDecimal.valueOf(2)); 114 new RoundToDoubleTester(halfMinDouble) 115 .roundUnnecessaryShouldThrow() 116 .setExpectation(-Double.MIN_VALUE, UP, FLOOR, HALF_UP) 117 .setExpectation(-0.0, HALF_EVEN, CEILING, DOWN, HALF_DOWN) 118 .test(); 119 } 120 testRoundToDouble_smallPositive()121 public void testRoundToDouble_smallPositive() { 122 new RoundToDoubleTester(BigDecimal.valueOf(16)).setExpectation(16.0, values()).test(); 123 } 124 testRoundToDouble_maxPreciselyRepresentable()125 public void testRoundToDouble_maxPreciselyRepresentable() { 126 new RoundToDoubleTester(BigDecimal.valueOf(1L << 53)) 127 .setExpectation(Math.pow(2, 53), values()) 128 .test(); 129 } 130 testRoundToDouble_maxPreciselyRepresentablePlusOne()131 public void testRoundToDouble_maxPreciselyRepresentablePlusOne() { 132 double twoToThe53 = Math.pow(2, 53); 133 // the representable doubles are 2^53 and 2^53 + 2. 134 // 2^53+1 is halfway between, so HALF_UP will go up and HALF_DOWN will go down. 135 new RoundToDoubleTester(BigDecimal.valueOf((1L << 53) + 1)) 136 .setExpectation(twoToThe53, DOWN, FLOOR, HALF_DOWN, HALF_EVEN) 137 .setExpectation(Math.nextUp(twoToThe53), CEILING, UP, HALF_UP) 138 .roundUnnecessaryShouldThrow() 139 .test(); 140 } 141 testRoundToDouble_twoToThe54PlusOne()142 public void testRoundToDouble_twoToThe54PlusOne() { 143 double twoToThe54 = Math.pow(2, 54); 144 // the representable doubles are 2^54 and 2^54 + 4 145 // 2^54+1 is less than halfway between, so HALF_DOWN and HALF_UP will both go down. 146 new RoundToDoubleTester(BigDecimal.valueOf((1L << 54) + 1)) 147 .setExpectation(twoToThe54, DOWN, FLOOR, HALF_DOWN, HALF_UP, HALF_EVEN) 148 .setExpectation(Math.nextUp(twoToThe54), CEILING, UP) 149 .roundUnnecessaryShouldThrow() 150 .test(); 151 } 152 testRoundToDouble_twoToThe54PlusOneHalf()153 public void testRoundToDouble_twoToThe54PlusOneHalf() { 154 double twoToThe54 = Math.pow(2, 54); 155 // the representable doubles are 2^54 and 2^54 + 4 156 // 2^54+1 is less than halfway between, so HALF_DOWN and HALF_UP will both go down. 157 new RoundToDoubleTester(BigDecimal.valueOf(1L << 54).add(new BigDecimal(0.5))) 158 .setExpectation(twoToThe54, DOWN, FLOOR, HALF_DOWN, HALF_UP, HALF_EVEN) 159 .setExpectation(Math.nextUp(twoToThe54), CEILING, UP) 160 .roundUnnecessaryShouldThrow() 161 .test(); 162 } 163 testRoundToDouble_twoToThe54PlusThree()164 public void testRoundToDouble_twoToThe54PlusThree() { 165 double twoToThe54 = Math.pow(2, 54); 166 // the representable doubles are 2^54 and 2^54 + 4 167 // 2^54+3 is more than halfway between, so HALF_DOWN and HALF_UP will both go up. 168 new RoundToDoubleTester(BigDecimal.valueOf((1L << 54) + 3)) 169 .setExpectation(twoToThe54, DOWN, FLOOR) 170 .setExpectation(Math.nextUp(twoToThe54), CEILING, UP, HALF_DOWN, HALF_UP, HALF_EVEN) 171 .roundUnnecessaryShouldThrow() 172 .test(); 173 } 174 testRoundToDouble_twoToThe54PlusFour()175 public void testRoundToDouble_twoToThe54PlusFour() { 176 new RoundToDoubleTester(BigDecimal.valueOf((1L << 54) + 4)) 177 .setExpectation(Math.pow(2, 54) + 4, values()) 178 .test(); 179 } 180 testRoundToDouble_maxDouble()181 public void testRoundToDouble_maxDouble() { 182 BigDecimal maxDoubleAsBD = new BigDecimal(Double.MAX_VALUE); 183 new RoundToDoubleTester(maxDoubleAsBD).setExpectation(Double.MAX_VALUE, values()).test(); 184 } 185 testRoundToDouble_maxDoublePlusOne()186 public void testRoundToDouble_maxDoublePlusOne() { 187 BigDecimal maxDoubleAsBD = new BigDecimal(Double.MAX_VALUE).add(BigDecimal.ONE); 188 new RoundToDoubleTester(maxDoubleAsBD) 189 .setExpectation(Double.MAX_VALUE, DOWN, FLOOR, HALF_EVEN, HALF_UP, HALF_DOWN) 190 .setExpectation(Double.POSITIVE_INFINITY, UP, CEILING) 191 .roundUnnecessaryShouldThrow() 192 .test(); 193 } 194 testRoundToDouble_wayTooBig()195 public void testRoundToDouble_wayTooBig() { 196 BigDecimal bi = BigDecimal.valueOf(2).pow(2 * Double.MAX_EXPONENT); 197 new RoundToDoubleTester(bi) 198 .setExpectation(Double.MAX_VALUE, DOWN, FLOOR, HALF_EVEN, HALF_UP, HALF_DOWN) 199 .setExpectation(Double.POSITIVE_INFINITY, UP, CEILING) 200 .roundUnnecessaryShouldThrow() 201 .test(); 202 } 203 testRoundToDouble_smallNegative()204 public void testRoundToDouble_smallNegative() { 205 new RoundToDoubleTester(BigDecimal.valueOf(-16)).setExpectation(-16.0, values()).test(); 206 } 207 testRoundToDouble_minPreciselyRepresentable()208 public void testRoundToDouble_minPreciselyRepresentable() { 209 new RoundToDoubleTester(BigDecimal.valueOf(-1L << 53)) 210 .setExpectation(-Math.pow(2, 53), values()) 211 .test(); 212 } 213 testRoundToDouble_minPreciselyRepresentableMinusOne()214 public void testRoundToDouble_minPreciselyRepresentableMinusOne() { 215 // the representable doubles are -2^53 and -2^53 - 2. 216 // -2^53-1 is halfway between, so HALF_UP will go up and HALF_DOWN will go down. 217 new RoundToDoubleTester(BigDecimal.valueOf((-1L << 53) - 1)) 218 .setExpectation(-Math.pow(2, 53), DOWN, CEILING, HALF_DOWN, HALF_EVEN) 219 .setExpectation(DoubleUtils.nextDown(-Math.pow(2, 53)), FLOOR, UP, HALF_UP) 220 .roundUnnecessaryShouldThrow() 221 .test(); 222 } 223 testRoundToDouble_negativeTwoToThe54MinusOne()224 public void testRoundToDouble_negativeTwoToThe54MinusOne() { 225 new RoundToDoubleTester(BigDecimal.valueOf((-1L << 54) - 1)) 226 .setExpectation(-Math.pow(2, 54), DOWN, CEILING, HALF_DOWN, HALF_UP, HALF_EVEN) 227 .setExpectation(DoubleUtils.nextDown(-Math.pow(2, 54)), FLOOR, UP) 228 .roundUnnecessaryShouldThrow() 229 .test(); 230 } 231 testRoundToDouble_negativeTwoToThe54MinusThree()232 public void testRoundToDouble_negativeTwoToThe54MinusThree() { 233 new RoundToDoubleTester(BigDecimal.valueOf((-1L << 54) - 3)) 234 .setExpectation(-Math.pow(2, 54), DOWN, CEILING) 235 .setExpectation( 236 DoubleUtils.nextDown(-Math.pow(2, 54)), FLOOR, UP, HALF_DOWN, HALF_UP, HALF_EVEN) 237 .roundUnnecessaryShouldThrow() 238 .test(); 239 } 240 testRoundToDouble_negativeTwoToThe54MinusFour()241 public void testRoundToDouble_negativeTwoToThe54MinusFour() { 242 new RoundToDoubleTester(BigDecimal.valueOf((-1L << 54) - 4)) 243 .setExpectation(-Math.pow(2, 54) - 4, values()) 244 .test(); 245 } 246 testRoundToDouble_minDouble()247 public void testRoundToDouble_minDouble() { 248 BigDecimal minDoubleAsBD = new BigDecimal(-Double.MAX_VALUE); 249 new RoundToDoubleTester(minDoubleAsBD).setExpectation(-Double.MAX_VALUE, values()).test(); 250 } 251 testRoundToDouble_minDoubleMinusOne()252 public void testRoundToDouble_minDoubleMinusOne() { 253 BigDecimal minDoubleAsBD = new BigDecimal(-Double.MAX_VALUE).subtract(BigDecimal.ONE); 254 new RoundToDoubleTester(minDoubleAsBD) 255 .setExpectation(-Double.MAX_VALUE, DOWN, CEILING, HALF_EVEN, HALF_UP, HALF_DOWN) 256 .setExpectation(Double.NEGATIVE_INFINITY, UP, FLOOR) 257 .roundUnnecessaryShouldThrow() 258 .test(); 259 } 260 testRoundToDouble_negativeWayTooBig()261 public void testRoundToDouble_negativeWayTooBig() { 262 BigDecimal bi = BigDecimal.valueOf(2).pow(2 * Double.MAX_EXPONENT).negate(); 263 new RoundToDoubleTester(bi) 264 .setExpectation(-Double.MAX_VALUE, DOWN, CEILING, HALF_EVEN, HALF_UP, HALF_DOWN) 265 .setExpectation(Double.NEGATIVE_INFINITY, UP, FLOOR) 266 .roundUnnecessaryShouldThrow() 267 .test(); 268 } 269 } 270