Coverage Report

Created: 2025-06-24 06:43

/src/icu/source/i18n/number_roundingutils.h
Line
Count
Source (jump to first uncovered line)
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
46
getRoundingDirection(bool isEven, bool isNegative, Section section, RoundingMode roundingMode,
47
0
                     UErrorCode &status) {
48
0
    if (U_FAILURE(status)) {
49
0
        return false;
50
0
    }
51
0
    switch (roundingMode) {
52
0
        case RoundingMode::UNUM_ROUND_UP:
53
            // round away from zero
54
0
            return false;
55
56
0
        case RoundingMode::UNUM_ROUND_DOWN:
57
            // round toward zero
58
0
            return true;
59
60
0
        case RoundingMode::UNUM_ROUND_CEILING:
61
            // round toward positive infinity
62
0
            return isNegative;
63
64
0
        case RoundingMode::UNUM_ROUND_FLOOR:
65
            // round toward negative infinity
66
0
            return !isNegative;
67
68
0
        case RoundingMode::UNUM_ROUND_HALFUP:
69
0
            switch (section) {
70
0
                case SECTION_MIDPOINT:
71
0
                    return false;
72
0
                case SECTION_LOWER:
73
0
                    return true;
74
0
                case SECTION_UPPER:
75
0
                    return false;
76
0
                default:
77
0
                    break;
78
0
            }
79
0
            break;
80
81
0
        case RoundingMode::UNUM_ROUND_HALFDOWN:
82
0
            switch (section) {
83
0
                case SECTION_MIDPOINT:
84
0
                    return true;
85
0
                case SECTION_LOWER:
86
0
                    return true;
87
0
                case SECTION_UPPER:
88
0
                    return false;
89
0
                default:
90
0
                    break;
91
0
            }
92
0
            break;
93
94
0
        case RoundingMode::UNUM_ROUND_HALFEVEN:
95
0
            switch (section) {
96
0
                case SECTION_MIDPOINT:
97
0
                    return isEven;
98
0
                case SECTION_LOWER:
99
0
                    return true;
100
0
                case SECTION_UPPER:
101
0
                    return false;
102
0
                default:
103
0
                    break;
104
0
            }
105
0
            break;
106
107
0
        case RoundingMode::UNUM_ROUND_HALF_ODD:
108
0
            switch (section) {
109
0
                case SECTION_MIDPOINT:
110
0
                    return !isEven;
111
0
                case SECTION_LOWER:
112
0
                    return true;
113
0
                case SECTION_UPPER:
114
0
                    return false;
115
0
                default:
116
0
                    break;
117
0
            }
118
0
            break;
119
120
0
        case RoundingMode::UNUM_ROUND_HALF_CEILING:
121
0
            switch (section) {
122
0
                case SECTION_MIDPOINT:
123
0
                    return isNegative;
124
0
                case SECTION_LOWER:
125
0
                    return true;
126
0
                case SECTION_UPPER:
127
0
                    return false;
128
0
                default:
129
0
                    break;
130
0
            }
131
0
            break;
132
133
0
        case RoundingMode::UNUM_ROUND_HALF_FLOOR:
134
0
            switch (section) {
135
0
                case SECTION_MIDPOINT:
136
0
                    return !isNegative;
137
0
                case SECTION_LOWER:
138
0
                    return true;
139
0
                case SECTION_UPPER:
140
0
                    return false;
141
0
                default:
142
0
                    break;
143
0
            }
144
0
            break;
145
146
0
        default:
147
0
            break;
148
0
    }
149
150
0
    status = U_FORMAT_INEXACT_ERROR;
151
0
    return false;
152
0
}
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
 */
164
0
inline bool roundsAtMidpoint(int roundingMode) {
165
0
    switch (roundingMode) {
166
0
        case RoundingMode::UNUM_ROUND_UP:
167
0
        case RoundingMode::UNUM_ROUND_DOWN:
168
0
        case RoundingMode::UNUM_ROUND_CEILING:
169
0
        case RoundingMode::UNUM_ROUND_FLOOR:
170
0
            return false;
171
172
0
        default:
173
0
            return true;
174
0
    }
175
0
}
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
0
    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 */