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