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