blob: 7525e3f990f158016cbbf9c0c10636225036ca3a [file] [log] [blame]
/*
* 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 {@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.
*/
abstract double roundToDoubleArbitrarily(X x);
/** 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. */
abstract X toX(double d, RoundingMode mode);
/** Returns a - b, guaranteed that both arguments are nonnegative. */
abstract X minus(X a, X b);
/** 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");
}
}