
com.ibm.icu.impl.number.RoundingUtils Maven / Gradle / Ivy
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
/** @author sffc */
public class RoundingUtils {
public static final int SECTION_LOWER = 1;
public static final int SECTION_MIDPOINT = 2;
public static final int SECTION_UPPER = 3;
/**
* The default rounding mode.
*/
public static final RoundingMode DEFAULT_ROUNDING_MODE = RoundingMode.HALF_EVEN;
/**
* The maximum number of fraction places, integer numerals, or significant digits.
* TODO: This does not feel like the best home for this value.
*/
public static final int MAX_INT_FRAC_SIG = 100;
/**
* Converts a rounding mode and metadata about the quantity being rounded to a boolean determining
* whether the value should be rounded toward infinity or toward zero.
*
* The parameters are of type int because benchmarks on an x86-64 processor against OpenJDK
* showed that ints were demonstrably faster than enums in switch statements.
*
* @param isEven Whether the digit immediately before the rounding magnitude is even.
* @param isNegative Whether the quantity is negative.
* @param section Whether the part of the quantity to the right of the rounding magnitude is
* exactly halfway between two digits, whether it is in the lower part (closer to zero), or
* whether it is in the upper part (closer to infinity). See {@link #SECTION_LOWER}, {@link
* #SECTION_MIDPOINT}, and {@link #SECTION_UPPER}.
* @param roundingMode The integer version of the {@link RoundingMode}, which you can get via
* {@link RoundingMode#ordinal}.
* @param reference A reference object to be used when throwing an ArithmeticException.
* @return true if the number should be rounded toward zero; false if it should be rounded toward
* infinity.
*/
public static boolean getRoundingDirection(
boolean isEven, boolean isNegative, int section, int roundingMode, Object reference) {
switch (roundingMode) {
case BigDecimal.ROUND_UP:
// round away from zero
return false;
case BigDecimal.ROUND_DOWN:
// round toward zero
return true;
case BigDecimal.ROUND_CEILING:
// round toward positive infinity
return isNegative;
case BigDecimal.ROUND_FLOOR:
// round toward negative infinity
return !isNegative;
case BigDecimal.ROUND_HALF_UP:
switch (section) {
case SECTION_MIDPOINT:
return false;
case SECTION_LOWER:
return true;
case SECTION_UPPER:
return false;
}
break;
case BigDecimal.ROUND_HALF_DOWN:
switch (section) {
case SECTION_MIDPOINT:
return true;
case SECTION_LOWER:
return true;
case SECTION_UPPER:
return false;
}
break;
case BigDecimal.ROUND_HALF_EVEN:
switch (section) {
case SECTION_MIDPOINT:
return isEven;
case SECTION_LOWER:
return true;
case SECTION_UPPER:
return false;
}
break;
}
// Rounding mode UNNECESSARY
throw new ArithmeticException("Rounding is required on " + reference.toString());
}
/**
* Gets whether the given rounding mode's rounding boundary is at the midpoint. The rounding
* boundary is the point at which a number switches from being rounded down to being rounded up.
* For example, with rounding mode HALF_EVEN, HALF_UP, or HALF_DOWN, the rounding boundary is at
* the midpoint, and this function would return true. However, for UP, DOWN, CEILING, and FLOOR,
* the rounding boundary is at the "edge", and this function would return false.
*
* @param roundingMode The integer version of the {@link RoundingMode}.
* @return true if rounding mode is HALF_EVEN, HALF_UP, or HALF_DOWN; false otherwise.
*/
public static boolean roundsAtMidpoint(int roundingMode) {
switch (roundingMode) {
case BigDecimal.ROUND_UP:
case BigDecimal.ROUND_DOWN:
case BigDecimal.ROUND_CEILING:
case BigDecimal.ROUND_FLOOR:
return false;
default:
return true;
}
}
private static final MathContext[] MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED =
new MathContext[RoundingMode.values().length];
private static final MathContext[] MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS =
new MathContext[RoundingMode.values().length];
static {
for (int i = 0; i < MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS.length; i++) {
MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[i] = new MathContext(0, RoundingMode.valueOf(i));
MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS[i] = new MathContext(34);
}
}
/**
* Gets the user-specified math context out of the property bag. If there is none, falls back to a
* math context with unlimited precision and the user-specified rounding mode, which defaults to
* HALF_EVEN (the IEEE 754R default).
*
* @param properties The property bag.
* @return A {@link MathContext}. Never null.
*/
public static MathContext getMathContextOrUnlimited(DecimalFormatProperties properties) {
MathContext mathContext = properties.getMathContext();
if (mathContext == null) {
RoundingMode roundingMode = properties.getRoundingMode();
if (roundingMode == null) roundingMode = RoundingMode.HALF_EVEN;
mathContext = MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[roundingMode.ordinal()];
}
return mathContext;
}
/**
* Gets the user-specified math context out of the property bag. If there is none, falls back to a
* math context with 34 digits of precision (the 128-bit IEEE 754R default) and the user-specified
* rounding mode, which defaults to HALF_EVEN (the IEEE 754R default).
*
* @param properties The property bag.
* @return A {@link MathContext}. Never null.
*/
public static MathContext getMathContextOr34Digits(DecimalFormatProperties properties) {
MathContext mathContext = properties.getMathContext();
if (mathContext == null) {
RoundingMode roundingMode = properties.getRoundingMode();
if (roundingMode == null) roundingMode = RoundingMode.HALF_EVEN;
mathContext = MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS[roundingMode.ordinal()];
}
return mathContext;
}
/**
* Gets a MathContext with unlimited precision and the specified RoundingMode. Equivalent to "new
* MathContext(0, roundingMode)", but pulls from a singleton to prevent object thrashing.
*
* @param roundingMode The {@link RoundingMode} to use.
* @return The corresponding {@link MathContext}.
*/
public static MathContext mathContextUnlimited(RoundingMode roundingMode) {
return MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[roundingMode.ordinal()];
}
}