/*
 * Copyright (C) 2020 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.common.math;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.math.MathPreconditions.checkRoundingUnnecessary;

import com.google.common.annotations.GwtIncompatible;
import java.math.RoundingMode;

Helper type to implement rounding X to a representable double value according to a RoundingMode.
/** * Helper type to implement rounding {@code X} to a representable {@code double} value according to * a {@link RoundingMode}. */
@GwtIncompatible abstract class ToDoubleRounder<X extends Number & Comparable<X>> {
Returns x rounded to either the greatest double less than or equal to the precise value of x, or the least double greater than or equal to the precise value of x.
/** * Returns x rounded to either the greatest double less than or equal to the precise value of x, * or the least double greater than or equal to the precise value of x. */
abstract double roundToDoubleArbitrarily(X x);
Returns the sign of x: either -1, 0, or 1.
/** Returns the sign of x: either -1, 0, or 1. */
abstract int sign(X x);
Returns d's value as an X, rounded with the specified mode.
/** Returns d's value as an X, rounded with the specified mode. */
abstract X toX(double d, RoundingMode mode);
Returns a - b, guaranteed that both arguments are nonnegative.
/** Returns a - b, guaranteed that both arguments are nonnegative. */
abstract X minus(X a, X b);
Rounds x to a double.
/** Rounds {@code x} to a {@code double}. */
final double roundToDouble(X x, RoundingMode mode) { checkNotNull(x, "x"); checkNotNull(mode, "mode"); double roundArbitrarily = roundToDoubleArbitrarily(x); if (Double.isInfinite(roundArbitrarily)) { switch (mode) { case DOWN: case HALF_EVEN: case HALF_DOWN: case HALF_UP: return Double.MAX_VALUE * sign(x); case FLOOR: return (roundArbitrarily == Double.POSITIVE_INFINITY) ? Double.MAX_VALUE : Double.NEGATIVE_INFINITY; case CEILING: return (roundArbitrarily == Double.POSITIVE_INFINITY) ? Double.POSITIVE_INFINITY : -Double.MAX_VALUE; case UP: return roundArbitrarily; case UNNECESSARY: throw new ArithmeticException(x + " cannot be represented precisely as a double"); } } X roundArbitrarilyAsX = toX(roundArbitrarily, RoundingMode.UNNECESSARY); int cmpXToRoundArbitrarily = x.compareTo(roundArbitrarilyAsX); switch (mode) { case UNNECESSARY: checkRoundingUnnecessary(cmpXToRoundArbitrarily == 0); return roundArbitrarily; case FLOOR: return (cmpXToRoundArbitrarily >= 0) ? roundArbitrarily : DoubleUtils.nextDown(roundArbitrarily); case CEILING: return (cmpXToRoundArbitrarily <= 0) ? roundArbitrarily : Math.nextUp(roundArbitrarily); case DOWN: if (sign(x) >= 0) { return (cmpXToRoundArbitrarily >= 0) ? roundArbitrarily : DoubleUtils.nextDown(roundArbitrarily); } else { return (cmpXToRoundArbitrarily <= 0) ? roundArbitrarily : Math.nextUp(roundArbitrarily); } case UP: if (sign(x) >= 0) { return (cmpXToRoundArbitrarily <= 0) ? roundArbitrarily : Math.nextUp(roundArbitrarily); } else { return (cmpXToRoundArbitrarily >= 0) ? roundArbitrarily : DoubleUtils.nextDown(roundArbitrarily); } case HALF_DOWN: case HALF_UP: case HALF_EVEN: { X roundFloor; double roundFloorAsDouble; X roundCeiling; double roundCeilingAsDouble; if (cmpXToRoundArbitrarily >= 0) { roundFloorAsDouble = roundArbitrarily; roundFloor = roundArbitrarilyAsX; roundCeilingAsDouble = Math.nextUp(roundArbitrarily); if (roundCeilingAsDouble == Double.POSITIVE_INFINITY) { return roundFloorAsDouble; } roundCeiling = toX(roundCeilingAsDouble, RoundingMode.CEILING); } else { roundCeilingAsDouble = roundArbitrarily; roundCeiling = roundArbitrarilyAsX; roundFloorAsDouble = DoubleUtils.nextDown(roundArbitrarily); if (roundFloorAsDouble == Double.NEGATIVE_INFINITY) { return roundCeilingAsDouble; } roundFloor = toX(roundFloorAsDouble, RoundingMode.FLOOR); } X deltaToFloor = minus(x, roundFloor); X deltaToCeiling = minus(roundCeiling, x); int diff = deltaToFloor.compareTo(deltaToCeiling); if (diff < 0) { // closer to floor return roundFloorAsDouble; } else if (diff > 0) { // closer to ceiling return roundCeilingAsDouble; } // halfway between the representable values; do the half-whatever logic switch (mode) { case HALF_EVEN: // roundFloorAsDouble and roundCeilingAsDouble are neighbors, so precisely // one of them should have an even long representation return ((Double.doubleToRawLongBits(roundFloorAsDouble) & 1L) == 0) ? roundFloorAsDouble : roundCeilingAsDouble; case HALF_DOWN: return (sign(x) >= 0) ? roundFloorAsDouble : roundCeilingAsDouble; case HALF_UP: return (sign(x) >= 0) ? roundCeilingAsDouble : roundFloorAsDouble; default: throw new AssertionError("impossible"); } } } throw new AssertionError("impossible"); } }