com.google.common.math.ToDoubleRounder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of guava Show documentation
Show all versions of guava Show documentation
Guava is a suite of core and expanded libraries that include
utility classes, google's collections, io classes, and much
much more.
/*
* 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> {
/**
* 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");
}
}