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