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