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;
@GwtIncompatible
abstract class ToDoubleRounder<X extends Number & Comparable<X>> {
abstract double roundToDoubleArbitrarily(X x);
abstract int sign(X x);
abstract X toX(double d, RoundingMode mode);
abstract X minus(X a, X b);
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) {
return roundFloorAsDouble;
} else if (diff > 0) {
return roundCeilingAsDouble;
}
switch (mode) {
case HALF_EVEN:
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");
}
}