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