• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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