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