xref: /aosp_15_r20/external/icu/libicu/cts_headers/number_roundingutils.h (revision 0e209d3975ff4a8c132096b14b0e9364a753506e)
1 // © 2017 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3 
4 #include "unicode/utypes.h"
5 
6 #if !UCONFIG_NO_FORMATTING
7 #ifndef __NUMBER_ROUNDINGUTILS_H__
8 #define __NUMBER_ROUNDINGUTILS_H__
9 
10 #include "number_types.h"
11 #include "string_segment.h"
12 
13 U_NAMESPACE_BEGIN
14 namespace number::impl {
15 namespace roundingutils {
16 
17 enum Section {
18     SECTION_LOWER_EDGE = -1,
19     SECTION_UPPER_EDGE = -2,
20     SECTION_LOWER = 1,
21     SECTION_MIDPOINT = 2,
22     SECTION_UPPER = 3
23 };
24 
25 /**
26  * Converts a rounding mode and metadata about the quantity being rounded to a boolean determining
27  * whether the value should be rounded toward infinity or toward zero.
28  *
29  * <p>The parameters are of type int because benchmarks on an x86-64 processor against OpenJDK
30  * showed that ints were demonstrably faster than enums in switch statements.
31  *
32  * @param isEven Whether the digit immediately before the rounding magnitude is even.
33  * @param isNegative Whether the quantity is negative.
34  * @param section Whether the part of the quantity to the right of the rounding magnitude is
35  *     exactly halfway between two digits, whether it is in the lower part (closer to zero), or
36  *     whether it is in the upper part (closer to infinity). See {@link #SECTION_LOWER}, {@link
37  *     #SECTION_MIDPOINT}, and {@link #SECTION_UPPER}.
38  * @param roundingMode The integer version of the {@link RoundingMode}, which you can get via
39  *     {@link RoundingMode#ordinal}.
40  * @param status Error code, set to U_FORMAT_INEXACT_ERROR if the rounding mode is kRoundUnnecessary.
41  * @return true if the number should be rounded toward zero; false if it should be rounded toward
42  *     infinity.
43  */
44 inline bool
getRoundingDirection(bool isEven,bool isNegative,Section section,RoundingMode roundingMode,UErrorCode & status)45 getRoundingDirection(bool isEven, bool isNegative, Section section, RoundingMode roundingMode,
46                      UErrorCode &status) {
47     if (U_FAILURE(status)) {
48         return false;
49     }
50     switch (roundingMode) {
51         case RoundingMode::UNUM_ROUND_UP:
52             // round away from zero
53             return false;
54 
55         case RoundingMode::UNUM_ROUND_DOWN:
56             // round toward zero
57             return true;
58 
59         case RoundingMode::UNUM_ROUND_CEILING:
60             // round toward positive infinity
61             return isNegative;
62 
63         case RoundingMode::UNUM_ROUND_FLOOR:
64             // round toward negative infinity
65             return !isNegative;
66 
67         case RoundingMode::UNUM_ROUND_HALFUP:
68             switch (section) {
69                 case SECTION_MIDPOINT:
70                     return false;
71                 case SECTION_LOWER:
72                     return true;
73                 case SECTION_UPPER:
74                     return false;
75                 default:
76                     break;
77             }
78             break;
79 
80         case RoundingMode::UNUM_ROUND_HALFDOWN:
81             switch (section) {
82                 case SECTION_MIDPOINT:
83                     return true;
84                 case SECTION_LOWER:
85                     return true;
86                 case SECTION_UPPER:
87                     return false;
88                 default:
89                     break;
90             }
91             break;
92 
93         case RoundingMode::UNUM_ROUND_HALFEVEN:
94             switch (section) {
95                 case SECTION_MIDPOINT:
96                     return isEven;
97                 case SECTION_LOWER:
98                     return true;
99                 case SECTION_UPPER:
100                     return false;
101                 default:
102                     break;
103             }
104             break;
105 
106         case RoundingMode::UNUM_ROUND_HALF_ODD:
107             switch (section) {
108                 case SECTION_MIDPOINT:
109                     return !isEven;
110                 case SECTION_LOWER:
111                     return true;
112                 case SECTION_UPPER:
113                     return false;
114                 default:
115                     break;
116             }
117             break;
118 
119         case RoundingMode::UNUM_ROUND_HALF_CEILING:
120             switch (section) {
121                 case SECTION_MIDPOINT:
122                     return isNegative;
123                 case SECTION_LOWER:
124                     return true;
125                 case SECTION_UPPER:
126                     return false;
127                 default:
128                     break;
129             }
130             break;
131 
132         case RoundingMode::UNUM_ROUND_HALF_FLOOR:
133             switch (section) {
134                 case SECTION_MIDPOINT:
135                     return !isNegative;
136                 case SECTION_LOWER:
137                     return true;
138                 case SECTION_UPPER:
139                     return false;
140                 default:
141                     break;
142             }
143             break;
144 
145         default:
146             break;
147     }
148 
149     status = U_FORMAT_INEXACT_ERROR;
150     return false;
151 }
152 
153 /**
154  * Gets whether the given rounding mode's rounding boundary is at the midpoint. The rounding
155  * boundary is the point at which a number switches from being rounded down to being rounded up.
156  * For example, with rounding mode HALF_EVEN, HALF_UP, or HALF_DOWN, the rounding boundary is at
157  * the midpoint, and this function would return true. However, for UP, DOWN, CEILING, and FLOOR,
158  * the rounding boundary is at the "edge", and this function would return false.
159  *
160  * @param roundingMode The integer version of the {@link RoundingMode}.
161  * @return true if rounding mode is HALF_EVEN, HALF_UP, or HALF_DOWN; false otherwise.
162  */
roundsAtMidpoint(int roundingMode)163 inline bool roundsAtMidpoint(int roundingMode) {
164     switch (roundingMode) {
165         case RoundingMode::UNUM_ROUND_UP:
166         case RoundingMode::UNUM_ROUND_DOWN:
167         case RoundingMode::UNUM_ROUND_CEILING:
168         case RoundingMode::UNUM_ROUND_FLOOR:
169             return false;
170 
171         default:
172             return true;
173     }
174 }
175 
176 } // namespace roundingutils
177 
178 
179 /**
180  * Encapsulates a Precision and a RoundingMode and performs rounding on a DecimalQuantity.
181  *
182  * This class does not exist in Java: instead, the base Precision class is used.
183  */
184 class RoundingImpl {
185   public:
186     RoundingImpl() = default;  // defaults to pass-through rounder
187 
188     RoundingImpl(const Precision& precision, UNumberFormatRoundingMode roundingMode,
189                  const CurrencyUnit& currency, UErrorCode& status);
190 
191     static RoundingImpl passThrough();
192 
193     /** Required for ScientificFormatter */
194     bool isSignificantDigits() const;
195 
196     /**
197      * Rounding endpoint used by Engineering and Compact notation. Chooses the most appropriate multiplier (magnitude
198      * adjustment), applies the adjustment, rounds, and returns the chosen multiplier.
199      *
200      * <p>
201      * In most cases, this is simple. However, when rounding the number causes it to cross a multiplier boundary, we
202      * need to re-do the rounding. For example, to display 999,999 in Engineering notation with 2 sigfigs, first you
203      * guess the multiplier to be -3. However, then you end up getting 1000E3, which is not the correct output. You then
204      * change your multiplier to be -6, and you get 1.0E6, which is correct.
205      *
206      * @param input The quantity to process.
207      * @param producer Function to call to return a multiplier based on a magnitude.
208      * @return The number of orders of magnitude the input was adjusted by this method.
209      */
210     int32_t
211     chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::MultiplierProducer &producer,
212                              UErrorCode &status);
213 
214     void apply(impl::DecimalQuantity &value, UErrorCode &status) const;
215 
216     /** Version of {@link #apply} that obeys minInt constraints. Used for scientific notation compatibility mode. */
217     void apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode status);
218 
219   private:
220     Precision fPrecision;
221     UNumberFormatRoundingMode fRoundingMode;
222     bool fPassThrough = true;  // default value
223 
224     // Permits access to fPrecision.
225     friend class units::UnitsRouter;
226 
227     // Permits access to fPrecision.
228     friend class UnitConversionHandler;
229 };
230 
231 /**
232  * Parses Precision-related skeleton strings without knowledge of MacroProps
233  * - see blueprint_helpers::parseIncrementOption().
234  *
235  * Referencing MacroProps means needing to pull in the .o files that have the
236  * destructors for the SymbolsWrapper, StringProp, and Scale classes.
237  */
238 void parseIncrementOption(const StringSegment &segment, Precision &outPrecision, UErrorCode &status);
239 
240 } // namespace number::impl
241 U_NAMESPACE_END
242 
243 #endif //__NUMBER_ROUNDINGUTILS_H__
244 
245 #endif /* #if !UCONFIG_NO_FORMATTING */
246