• 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.base.Preconditions.checkNotNull;
18 import static com.google.common.math.MathPreconditions.checkRoundingUnnecessary;
19 
20 import com.google.common.annotations.GwtIncompatible;
21 import java.math.RoundingMode;
22 
23 /**
24  * Helper type to implement rounding {@code X} to a representable {@code double} value according to
25  * a {@link RoundingMode}.
26  */
27 @GwtIncompatible
28 @ElementTypesAreNonnullByDefault
29 abstract class ToDoubleRounder<X extends Number & Comparable<X>> {
30   /**
31    * Returns x rounded to either the greatest double less than or equal to the precise value of x,
32    * or the least double greater than or equal to the precise value of x.
33    */
roundToDoubleArbitrarily(X x)34   abstract double roundToDoubleArbitrarily(X x);
35 
36   /** Returns the sign of x: either -1, 0, or 1. */
sign(X x)37   abstract int sign(X x);
38 
39   /** Returns d's value as an X, rounded with the specified mode. */
toX(double d, RoundingMode mode)40   abstract X toX(double d, RoundingMode mode);
41 
42   /** Returns a - b, guaranteed that both arguments are nonnegative. */
minus(X a, X b)43   abstract X minus(X a, X b);
44 
45   /** Rounds {@code x} to a {@code double}. */
roundToDouble(X x, RoundingMode mode)46   final double roundToDouble(X x, RoundingMode mode) {
47     checkNotNull(x, "x");
48     checkNotNull(mode, "mode");
49     double roundArbitrarily = roundToDoubleArbitrarily(x);
50     if (Double.isInfinite(roundArbitrarily)) {
51       switch (mode) {
52         case DOWN:
53         case HALF_EVEN:
54         case HALF_DOWN:
55         case HALF_UP:
56           return Double.MAX_VALUE * sign(x);
57         case FLOOR:
58           return (roundArbitrarily == Double.POSITIVE_INFINITY)
59               ? Double.MAX_VALUE
60               : Double.NEGATIVE_INFINITY;
61         case CEILING:
62           return (roundArbitrarily == Double.POSITIVE_INFINITY)
63               ? Double.POSITIVE_INFINITY
64               : -Double.MAX_VALUE;
65         case UP:
66           return roundArbitrarily;
67         case UNNECESSARY:
68           throw new ArithmeticException(x + " cannot be represented precisely as a double");
69       }
70     }
71     X roundArbitrarilyAsX = toX(roundArbitrarily, RoundingMode.UNNECESSARY);
72     int cmpXToRoundArbitrarily = x.compareTo(roundArbitrarilyAsX);
73     switch (mode) {
74       case UNNECESSARY:
75         checkRoundingUnnecessary(cmpXToRoundArbitrarily == 0);
76         return roundArbitrarily;
77       case FLOOR:
78         return (cmpXToRoundArbitrarily >= 0)
79             ? roundArbitrarily
80             : DoubleUtils.nextDown(roundArbitrarily);
81       case CEILING:
82         return (cmpXToRoundArbitrarily <= 0) ? roundArbitrarily : Math.nextUp(roundArbitrarily);
83       case DOWN:
84         if (sign(x) >= 0) {
85           return (cmpXToRoundArbitrarily >= 0)
86               ? roundArbitrarily
87               : DoubleUtils.nextDown(roundArbitrarily);
88         } else {
89           return (cmpXToRoundArbitrarily <= 0) ? roundArbitrarily : Math.nextUp(roundArbitrarily);
90         }
91       case UP:
92         if (sign(x) >= 0) {
93           return (cmpXToRoundArbitrarily <= 0) ? roundArbitrarily : Math.nextUp(roundArbitrarily);
94         } else {
95           return (cmpXToRoundArbitrarily >= 0)
96               ? roundArbitrarily
97               : DoubleUtils.nextDown(roundArbitrarily);
98         }
99       case HALF_DOWN:
100       case HALF_UP:
101       case HALF_EVEN:
102         {
103           X roundFloor;
104           double roundFloorAsDouble;
105           X roundCeiling;
106           double roundCeilingAsDouble;
107 
108           if (cmpXToRoundArbitrarily >= 0) {
109             roundFloorAsDouble = roundArbitrarily;
110             roundFloor = roundArbitrarilyAsX;
111             roundCeilingAsDouble = Math.nextUp(roundArbitrarily);
112             if (roundCeilingAsDouble == Double.POSITIVE_INFINITY) {
113               return roundFloorAsDouble;
114             }
115             roundCeiling = toX(roundCeilingAsDouble, RoundingMode.CEILING);
116           } else {
117             roundCeilingAsDouble = roundArbitrarily;
118             roundCeiling = roundArbitrarilyAsX;
119             roundFloorAsDouble = DoubleUtils.nextDown(roundArbitrarily);
120             if (roundFloorAsDouble == Double.NEGATIVE_INFINITY) {
121               return roundCeilingAsDouble;
122             }
123             roundFloor = toX(roundFloorAsDouble, RoundingMode.FLOOR);
124           }
125 
126           X deltaToFloor = minus(x, roundFloor);
127           X deltaToCeiling = minus(roundCeiling, x);
128           int diff = deltaToFloor.compareTo(deltaToCeiling);
129           if (diff < 0) { // closer to floor
130             return roundFloorAsDouble;
131           } else if (diff > 0) { // closer to ceiling
132             return roundCeilingAsDouble;
133           }
134           // halfway between the representable values; do the half-whatever logic
135           switch (mode) {
136             case HALF_EVEN:
137               // roundFloorAsDouble and roundCeilingAsDouble are neighbors, so precisely
138               // one of them should have an even long representation
139               return ((Double.doubleToRawLongBits(roundFloorAsDouble) & 1L) == 0)
140                   ? roundFloorAsDouble
141                   : roundCeilingAsDouble;
142             case HALF_DOWN:
143               return (sign(x) >= 0) ? roundFloorAsDouble : roundCeilingAsDouble;
144             case HALF_UP:
145               return (sign(x) >= 0) ? roundCeilingAsDouble : roundFloorAsDouble;
146             default:
147               throw new AssertionError("impossible");
148           }
149         }
150     }
151     throw new AssertionError("impossible");
152   }
153 }
154