1 // © 2017 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 package com.ibm.icu.impl.number; 4 5 import java.math.BigDecimal; 6 import java.math.MathContext; 7 import java.math.RoundingMode; 8 9 import com.ibm.icu.impl.StandardPlural; 10 import com.ibm.icu.number.Precision; 11 import com.ibm.icu.number.Scale; 12 import com.ibm.icu.text.PluralRules; 13 14 /** @author sffc */ 15 public class RoundingUtils { 16 17 public static final int SECTION_LOWER = 1; 18 public static final int SECTION_MIDPOINT = 2; 19 public static final int SECTION_UPPER = 3; 20 21 /** 22 * The default rounding mode. 23 */ 24 public static final RoundingMode DEFAULT_ROUNDING_MODE = RoundingMode.HALF_EVEN; 25 26 /** 27 * The maximum number of fraction places, integer numerals, or significant digits. TODO: This does 28 * not feel like the best home for this value. 29 */ 30 public static final int MAX_INT_FRAC_SIG = 999; 31 32 /** 33 * Converts a rounding mode and metadata about the quantity being rounded to a boolean determining 34 * whether the value should be rounded toward infinity or toward zero. 35 * 36 * <p> 37 * The parameters are of type int because benchmarks on an x86-64 processor against OpenJDK showed 38 * that ints were demonstrably faster than enums in switch statements. 39 * 40 * @param isEven 41 * Whether the digit immediately before the rounding magnitude is even. 42 * @param isNegative 43 * Whether the quantity is negative. 44 * @param section 45 * Whether the part of the quantity to the right of the rounding magnitude is exactly 46 * halfway between two digits, whether it is in the lower part (closer to zero), or 47 * whether it is in the upper part (closer to infinity). See {@link #SECTION_LOWER}, 48 * {@link #SECTION_MIDPOINT}, and {@link #SECTION_UPPER}. 49 * @param roundingMode 50 * The integer version of the {@link RoundingMode}, which you can get via 51 * {@link RoundingMode#ordinal}. 52 * @param reference 53 * A reference object to be used when throwing an ArithmeticException. 54 * @return true if the number should be rounded toward zero; false if it should be rounded toward 55 * infinity. 56 */ getRoundingDirection( boolean isEven, boolean isNegative, int section, int roundingMode, Object reference)57 public static boolean getRoundingDirection( 58 boolean isEven, 59 boolean isNegative, 60 int section, 61 int roundingMode, 62 Object reference) { 63 switch (roundingMode) { 64 case BigDecimal.ROUND_UP: 65 // round away from zero 66 return false; 67 68 case BigDecimal.ROUND_DOWN: 69 // round toward zero 70 return true; 71 72 case BigDecimal.ROUND_CEILING: 73 // round toward positive infinity 74 return isNegative; 75 76 case BigDecimal.ROUND_FLOOR: 77 // round toward negative infinity 78 return !isNegative; 79 80 case BigDecimal.ROUND_HALF_UP: 81 switch (section) { 82 case SECTION_MIDPOINT: 83 return false; 84 case SECTION_LOWER: 85 return true; 86 case SECTION_UPPER: 87 return false; 88 } 89 break; 90 91 case BigDecimal.ROUND_HALF_DOWN: 92 switch (section) { 93 case SECTION_MIDPOINT: 94 return true; 95 case SECTION_LOWER: 96 return true; 97 case SECTION_UPPER: 98 return false; 99 } 100 break; 101 102 case BigDecimal.ROUND_HALF_EVEN: 103 switch (section) { 104 case SECTION_MIDPOINT: 105 return isEven; 106 case SECTION_LOWER: 107 return true; 108 case SECTION_UPPER: 109 return false; 110 } 111 break; 112 } 113 114 // Rounding mode UNNECESSARY 115 throw new ArithmeticException("Rounding is required on " + reference.toString()); 116 } 117 118 /** 119 * Gets whether the given rounding mode's rounding boundary is at the midpoint. The rounding boundary 120 * is the point at which a number switches from being rounded down to being rounded up. For example, 121 * with rounding mode HALF_EVEN, HALF_UP, or HALF_DOWN, the rounding boundary is at the midpoint, and 122 * this function would return true. However, for UP, DOWN, CEILING, and FLOOR, the rounding boundary 123 * is at the "edge", and this function would return false. 124 * 125 * @param roundingMode 126 * The integer version of the {@link RoundingMode}. 127 * @return true if rounding mode is HALF_EVEN, HALF_UP, or HALF_DOWN; false otherwise. 128 */ roundsAtMidpoint(int roundingMode)129 public static boolean roundsAtMidpoint(int roundingMode) { 130 switch (roundingMode) { 131 case BigDecimal.ROUND_UP: 132 case BigDecimal.ROUND_DOWN: 133 case BigDecimal.ROUND_CEILING: 134 case BigDecimal.ROUND_FLOOR: 135 return false; 136 137 default: 138 return true; 139 } 140 } 141 142 private static final MathContext[] MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED = new MathContext[RoundingMode 143 .values().length]; 144 145 private static final MathContext[] MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS = new MathContext[RoundingMode 146 .values().length]; 147 148 static { 149 for (int i = 0; i < MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS.length; i++) { 150 MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[i] = new MathContext(0, RoundingMode.valueOf(i)); 151 MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS[i] = new MathContext(34); 152 } 153 } 154 155 /** The default MathContext, unlimited-precision version. */ 156 public static final MathContext DEFAULT_MATH_CONTEXT_UNLIMITED 157 = MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[DEFAULT_ROUNDING_MODE.ordinal()]; 158 159 /** The default MathContext, 34-digit version. */ 160 public static final MathContext DEFAULT_MATH_CONTEXT_34_DIGITS 161 = MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS[DEFAULT_ROUNDING_MODE.ordinal()]; 162 163 /** 164 * Gets the user-specified math context out of the property bag. If there is none, falls back to a 165 * math context with unlimited precision and the user-specified rounding mode, which defaults to 166 * HALF_EVEN (the IEEE 754R default). 167 * 168 * @param properties 169 * The property bag. 170 * @return A {@link MathContext}. Never null. 171 */ getMathContextOrUnlimited(DecimalFormatProperties properties)172 public static MathContext getMathContextOrUnlimited(DecimalFormatProperties properties) { 173 MathContext mathContext = properties.getMathContext(); 174 if (mathContext == null) { 175 RoundingMode roundingMode = properties.getRoundingMode(); 176 if (roundingMode == null) 177 roundingMode = RoundingMode.HALF_EVEN; 178 mathContext = MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[roundingMode.ordinal()]; 179 } 180 return mathContext; 181 } 182 183 /** 184 * Gets the user-specified math context out of the property bag. If there is none, falls back to a 185 * math context with 34 digits of precision (the 128-bit IEEE 754R default) and the user-specified 186 * rounding mode, which defaults to HALF_EVEN (the IEEE 754R default). 187 * 188 * @param properties 189 * The property bag. 190 * @return A {@link MathContext}. Never null. 191 */ getMathContextOr34Digits(DecimalFormatProperties properties)192 public static MathContext getMathContextOr34Digits(DecimalFormatProperties properties) { 193 MathContext mathContext = properties.getMathContext(); 194 if (mathContext == null) { 195 RoundingMode roundingMode = properties.getRoundingMode(); 196 if (roundingMode == null) 197 roundingMode = RoundingMode.HALF_EVEN; 198 mathContext = MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS[roundingMode.ordinal()]; 199 } 200 return mathContext; 201 } 202 203 /** 204 * Gets a MathContext with unlimited precision and the specified RoundingMode. Equivalent to "new 205 * MathContext(0, roundingMode)", but pulls from a singleton to prevent object thrashing. 206 * 207 * @param roundingMode 208 * The {@link RoundingMode} to use. 209 * @return The corresponding {@link MathContext}. 210 */ mathContextUnlimited(RoundingMode roundingMode)211 public static MathContext mathContextUnlimited(RoundingMode roundingMode) { 212 return MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[roundingMode.ordinal()]; 213 } 214 scaleFromProperties(DecimalFormatProperties properties)215 public static Scale scaleFromProperties(DecimalFormatProperties properties) { 216 MathContext mc = getMathContextOr34Digits(properties); 217 if (properties.getMagnitudeMultiplier() != 0) { 218 return Scale.powerOfTen(properties.getMagnitudeMultiplier()).withMathContext(mc); 219 } else if (properties.getMultiplier() != null) { 220 return Scale.byBigDecimal(properties.getMultiplier()).withMathContext(mc); 221 } else { 222 return null; 223 } 224 } 225 226 /** 227 * Computes the plural form after copying the number and applying rounding rules. 228 */ getPluralSafe( Precision rounder, PluralRules rules, DecimalQuantity dq)229 public static StandardPlural getPluralSafe( 230 Precision rounder, PluralRules rules, DecimalQuantity dq) { 231 if (rounder == null) { 232 return dq.getStandardPlural(rules); 233 } 234 // TODO(ICU-20500): Avoid the copy? 235 DecimalQuantity copy = dq.createCopy(); 236 rounder.apply(copy); 237 return copy.getStandardPlural(rules); 238 } 239 } 240