• 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 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