• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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