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