Coverage Report

Created: 2025-06-24 06:43

/src/icu/source/i18n/number_rounding.cpp
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
8
#include "charstr.h"
9
#include "uassert.h"
10
#include "unicode/numberformatter.h"
11
#include "number_types.h"
12
#include "number_decimalquantity.h"
13
#include "double-conversion.h"
14
#include "number_roundingutils.h"
15
#include "number_skeletons.h"
16
#include "putilimp.h"
17
#include "string_segment.h"
18
19
using namespace icu;
20
using namespace icu::number;
21
using namespace icu::number::impl;
22
23
24
using double_conversion::DoubleToStringConverter;
25
using icu::StringSegment;
26
27
void number::impl::parseIncrementOption(const StringSegment &segment,
28
                                        Precision &outPrecision,
29
0
                                        UErrorCode &status) {
30
    // Need to do char <-> UChar conversion...
31
0
    U_ASSERT(U_SUCCESS(status));
32
0
    CharString buffer;
33
0
    SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status);
34
35
    // Utilize DecimalQuantity/decNumber to parse this for us.
36
0
    DecimalQuantity dq;
37
0
    UErrorCode localStatus = U_ZERO_ERROR;
38
0
    dq.setToDecNumber({buffer.data(), buffer.length()}, localStatus);
39
0
    if (U_FAILURE(localStatus)) {
40
        // throw new SkeletonSyntaxException("Invalid rounding increment", segment, e);
41
0
        status = U_NUMBER_SKELETON_SYNTAX_ERROR;
42
0
        return;
43
0
    }
44
0
    double increment = dq.toDouble();
45
46
    // We also need to figure out how many digits. Do a brute force string operation.
47
0
    int decimalOffset = 0;
48
0
    while (decimalOffset < segment.length() && segment.charAt(decimalOffset) != '.') {
49
0
        decimalOffset++;
50
0
    }
51
0
    if (decimalOffset == segment.length()) {
52
0
        outPrecision = Precision::increment(increment);
53
0
    } else {
54
0
        int32_t fractionLength = segment.length() - decimalOffset - 1;
55
0
        outPrecision = Precision::increment(increment).withMinFraction(fractionLength);
56
0
    }
57
0
}
58
59
namespace {
60
61
0
int32_t getRoundingMagnitudeFraction(int maxFrac) {
62
0
    if (maxFrac == -1) {
63
0
        return INT32_MIN;
64
0
    }
65
0
    return -maxFrac;
66
0
}
67
68
0
int32_t getRoundingMagnitudeSignificant(const DecimalQuantity &value, int maxSig) {
69
0
    if (maxSig == -1) {
70
0
        return INT32_MIN;
71
0
    }
72
0
    int magnitude = value.isZeroish() ? 0 : value.getMagnitude();
73
0
    return magnitude - maxSig + 1;
74
0
}
75
76
0
int32_t getDisplayMagnitudeFraction(int minFrac) {
77
0
    if (minFrac == 0) {
78
0
        return INT32_MAX;
79
0
    }
80
0
    return -minFrac;
81
0
}
82
83
0
int32_t getDisplayMagnitudeSignificant(const DecimalQuantity &value, int minSig) {
84
0
    int magnitude = value.isZeroish() ? 0 : value.getMagnitude();
85
0
    return magnitude - minSig + 1;
86
0
}
87
88
}
89
90
91
0
MultiplierProducer::~MultiplierProducer() = default;
92
93
94
0
digits_t roundingutils::doubleFractionLength(double input, int8_t* singleDigit) {
95
0
    char buffer[DoubleToStringConverter::kBase10MaximalLength + 1];
96
0
    bool sign; // unused; always positive
97
0
    int32_t length;
98
0
    int32_t point;
99
0
    DoubleToStringConverter::DoubleToAscii(
100
0
            input,
101
0
            DoubleToStringConverter::DtoaMode::SHORTEST,
102
0
            0,
103
0
            buffer,
104
0
            sizeof(buffer),
105
0
            &sign,
106
0
            &length,
107
0
            &point
108
0
    );
109
110
0
    if (singleDigit == nullptr) {
111
        // no-op
112
0
    } else if (length == 1) {
113
0
        *singleDigit = buffer[0] - '0';
114
0
    } else {
115
0
        *singleDigit = -1;
116
0
    }
117
118
0
    return static_cast<digits_t>(length - point);
119
0
}
120
121
122
0
Precision Precision::unlimited() {
123
0
    return Precision(RND_NONE, {});
124
0
}
125
126
0
FractionPrecision Precision::integer() {
127
0
    return constructFraction(0, 0);
128
0
}
129
130
0
FractionPrecision Precision::fixedFraction(int32_t minMaxFractionPlaces) {
131
0
    if (minMaxFractionPlaces >= 0 && minMaxFractionPlaces <= kMaxIntFracSig) {
132
0
        return constructFraction(minMaxFractionPlaces, minMaxFractionPlaces);
133
0
    } else {
134
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
135
0
    }
136
0
}
137
138
0
FractionPrecision Precision::minFraction(int32_t minFractionPlaces) {
139
0
    if (minFractionPlaces >= 0 && minFractionPlaces <= kMaxIntFracSig) {
140
0
        return constructFraction(minFractionPlaces, -1);
141
0
    } else {
142
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
143
0
    }
144
0
}
145
146
0
FractionPrecision Precision::maxFraction(int32_t maxFractionPlaces) {
147
0
    if (maxFractionPlaces >= 0 && maxFractionPlaces <= kMaxIntFracSig) {
148
0
        return constructFraction(0, maxFractionPlaces);
149
0
    } else {
150
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
151
0
    }
152
0
}
153
154
0
FractionPrecision Precision::minMaxFraction(int32_t minFractionPlaces, int32_t maxFractionPlaces) {
155
0
    if (minFractionPlaces >= 0 && maxFractionPlaces <= kMaxIntFracSig &&
156
0
        minFractionPlaces <= maxFractionPlaces) {
157
0
        return constructFraction(minFractionPlaces, maxFractionPlaces);
158
0
    } else {
159
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
160
0
    }
161
0
}
162
163
0
Precision Precision::fixedSignificantDigits(int32_t minMaxSignificantDigits) {
164
0
    if (minMaxSignificantDigits >= 1 && minMaxSignificantDigits <= kMaxIntFracSig) {
165
0
        return constructSignificant(minMaxSignificantDigits, minMaxSignificantDigits);
166
0
    } else {
167
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
168
0
    }
169
0
}
170
171
0
Precision Precision::minSignificantDigits(int32_t minSignificantDigits) {
172
0
    if (minSignificantDigits >= 1 && minSignificantDigits <= kMaxIntFracSig) {
173
0
        return constructSignificant(minSignificantDigits, -1);
174
0
    } else {
175
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
176
0
    }
177
0
}
178
179
0
Precision Precision::maxSignificantDigits(int32_t maxSignificantDigits) {
180
0
    if (maxSignificantDigits >= 1 && maxSignificantDigits <= kMaxIntFracSig) {
181
0
        return constructSignificant(1, maxSignificantDigits);
182
0
    } else {
183
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
184
0
    }
185
0
}
186
187
0
Precision Precision::minMaxSignificantDigits(int32_t minSignificantDigits, int32_t maxSignificantDigits) {
188
0
    if (minSignificantDigits >= 1 && maxSignificantDigits <= kMaxIntFracSig &&
189
0
        minSignificantDigits <= maxSignificantDigits) {
190
0
        return constructSignificant(minSignificantDigits, maxSignificantDigits);
191
0
    } else {
192
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
193
0
    }
194
0
}
195
196
0
Precision Precision::trailingZeroDisplay(UNumberTrailingZeroDisplay trailingZeroDisplay) const {
197
0
    Precision result(*this); // copy constructor
198
0
    result.fTrailingZeroDisplay = trailingZeroDisplay;
199
0
    return result;
200
0
}
201
202
0
IncrementPrecision Precision::increment(double roundingIncrement) {
203
0
    if (roundingIncrement > 0.0) {
204
0
        return constructIncrement(roundingIncrement, 0);
205
0
    } else {
206
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
207
0
    }
208
0
}
209
210
0
CurrencyPrecision Precision::currency(UCurrencyUsage currencyUsage) {
211
0
    return constructCurrency(currencyUsage);
212
0
}
213
214
Precision FractionPrecision::withSignificantDigits(
215
        int32_t minSignificantDigits,
216
        int32_t maxSignificantDigits,
217
0
        UNumberRoundingPriority priority) const {
218
0
    if (fType == RND_ERROR) { return *this; } // no-op in error state
219
0
    if (minSignificantDigits >= 1 &&
220
0
            maxSignificantDigits >= minSignificantDigits &&
221
0
            maxSignificantDigits <= kMaxIntFracSig) {
222
0
        return constructFractionSignificant(
223
0
            *this,
224
0
            minSignificantDigits,
225
0
            maxSignificantDigits,
226
0
            priority);
227
0
    } else {
228
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
229
0
    }
230
0
}
231
232
0
Precision FractionPrecision::withMinDigits(int32_t minSignificantDigits) const {
233
0
    if (fType == RND_ERROR) { return *this; } // no-op in error state
234
0
    if (minSignificantDigits >= 1 && minSignificantDigits <= kMaxIntFracSig) {
235
0
        return constructFractionSignificant(
236
0
            *this,
237
0
            1,
238
0
            minSignificantDigits,
239
0
            UNUM_ROUNDING_PRIORITY_RELAXED);
240
0
    } else {
241
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
242
0
    }
243
0
}
244
245
0
Precision FractionPrecision::withMaxDigits(int32_t maxSignificantDigits) const {
246
0
    if (fType == RND_ERROR) { return *this; } // no-op in error state
247
0
    if (maxSignificantDigits >= 1 && maxSignificantDigits <= kMaxIntFracSig) {
248
0
        return constructFractionSignificant(*this,
249
0
            1,
250
0
            maxSignificantDigits,
251
0
            UNUM_ROUNDING_PRIORITY_STRICT);
252
0
    } else {
253
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
254
0
    }
255
0
}
256
257
// Private method on base class
258
0
Precision Precision::withCurrency(const CurrencyUnit &currency, UErrorCode &status) const {
259
0
    if (fType == RND_ERROR) { return *this; } // no-op in error state
260
0
    U_ASSERT(fType == RND_CURRENCY);
261
0
    const char16_t *isoCode = currency.getISOCurrency();
262
0
    double increment = ucurr_getRoundingIncrementForUsage(isoCode, fUnion.currencyUsage, &status);
263
0
    int32_t minMaxFrac = ucurr_getDefaultFractionDigitsForUsage(
264
0
            isoCode, fUnion.currencyUsage, &status);
265
0
    Precision retval = (increment != 0.0)
266
0
        ? static_cast<Precision>(constructIncrement(increment, minMaxFrac))
267
0
        : static_cast<Precision>(constructFraction(minMaxFrac, minMaxFrac));
268
0
    retval.fTrailingZeroDisplay = fTrailingZeroDisplay;
269
0
    return retval;
270
0
}
271
272
// Public method on CurrencyPrecision subclass
273
0
Precision CurrencyPrecision::withCurrency(const CurrencyUnit &currency) const {
274
0
    UErrorCode localStatus = U_ZERO_ERROR;
275
0
    Precision result = Precision::withCurrency(currency, localStatus);
276
0
    if (U_FAILURE(localStatus)) {
277
0
        return {localStatus};
278
0
    }
279
0
    return result;
280
0
}
281
282
0
Precision IncrementPrecision::withMinFraction(int32_t minFrac) const {
283
0
    if (fType == RND_ERROR) { return *this; } // no-op in error state
284
0
    if (minFrac >= 0 && minFrac <= kMaxIntFracSig) {
285
0
        return constructIncrement(fUnion.increment.fIncrement, minFrac);
286
0
    } else {
287
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
288
0
    }
289
0
}
290
291
0
FractionPrecision Precision::constructFraction(int32_t minFrac, int32_t maxFrac) {
292
0
    FractionSignificantSettings settings;
293
0
    settings.fMinFrac = static_cast<digits_t>(minFrac);
294
0
    settings.fMaxFrac = static_cast<digits_t>(maxFrac);
295
0
    settings.fMinSig = -1;
296
0
    settings.fMaxSig = -1;
297
0
    PrecisionUnion union_;
298
0
    union_.fracSig = settings;
299
0
    return {RND_FRACTION, union_};
300
0
}
301
302
0
Precision Precision::constructSignificant(int32_t minSig, int32_t maxSig) {
303
0
    FractionSignificantSettings settings;
304
0
    settings.fMinFrac = -1;
305
0
    settings.fMaxFrac = -1;
306
0
    settings.fMinSig = static_cast<digits_t>(minSig);
307
0
    settings.fMaxSig = static_cast<digits_t>(maxSig);
308
0
    PrecisionUnion union_;
309
0
    union_.fracSig = settings;
310
0
    return {RND_SIGNIFICANT, union_};
311
0
}
312
313
Precision
314
Precision::constructFractionSignificant(
315
        const FractionPrecision &base,
316
        int32_t minSig,
317
        int32_t maxSig,
318
0
        UNumberRoundingPriority priority) {
319
0
    FractionSignificantSettings settings = base.fUnion.fracSig;
320
0
    settings.fMinSig = static_cast<digits_t>(minSig);
321
0
    settings.fMaxSig = static_cast<digits_t>(maxSig);
322
0
    settings.fPriority = priority;
323
0
    PrecisionUnion union_;
324
0
    union_.fracSig = settings;
325
0
    return {RND_FRACTION_SIGNIFICANT, union_};
326
0
}
327
328
0
IncrementPrecision Precision::constructIncrement(double increment, int32_t minFrac) {
329
0
    IncrementSettings settings;
330
    // Note: For number formatting, fIncrement is used for RND_INCREMENT but not
331
    // RND_INCREMENT_ONE or RND_INCREMENT_FIVE. However, fIncrement is used in all
332
    // three when constructing a skeleton.
333
0
    settings.fIncrement = increment;
334
0
    settings.fMinFrac = static_cast<digits_t>(minFrac);
335
    // One of the few pre-computed quantities:
336
    // Note: it is possible for minFrac to be more than maxFrac... (misleading)
337
0
    int8_t singleDigit;
338
0
    settings.fMaxFrac = roundingutils::doubleFractionLength(increment, &singleDigit);
339
0
    PrecisionUnion union_;
340
0
    union_.increment = settings;
341
0
    if (singleDigit == 1) {
342
        // NOTE: In C++, we must return the correct value type with the correct union.
343
        // It would be invalid to return a RND_FRACTION here because the methods on the
344
        // IncrementPrecision type assume that the union is backed by increment data.
345
0
        return {RND_INCREMENT_ONE, union_};
346
0
    } else if (singleDigit == 5) {
347
0
        return {RND_INCREMENT_FIVE, union_};
348
0
    } else {
349
0
        return {RND_INCREMENT, union_};
350
0
    }
351
0
}
352
353
0
CurrencyPrecision Precision::constructCurrency(UCurrencyUsage usage) {
354
0
    PrecisionUnion union_;
355
0
    union_.currencyUsage = usage;
356
0
    return {RND_CURRENCY, union_};
357
0
}
358
359
360
RoundingImpl::RoundingImpl(const Precision& precision, UNumberFormatRoundingMode roundingMode,
361
                           const CurrencyUnit& currency, UErrorCode& status)
362
0
        : fPrecision(precision), fRoundingMode(roundingMode), fPassThrough(false) {
363
0
    if (precision.fType == Precision::RND_CURRENCY) {
364
0
        fPrecision = precision.withCurrency(currency, status);
365
0
    }
366
0
}
367
368
0
RoundingImpl RoundingImpl::passThrough() {
369
0
    return {};
370
0
}
371
372
0
bool RoundingImpl::isSignificantDigits() const {
373
0
    return fPrecision.fType == Precision::RND_SIGNIFICANT;
374
0
}
375
376
int32_t
377
RoundingImpl::chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::MultiplierProducer &producer,
378
0
                                  UErrorCode &status) {
379
    // Do not call this method with zero, NaN, or infinity.
380
0
    U_ASSERT(!input.isZeroish());
381
382
    // Perform the first attempt at rounding.
383
0
    int magnitude = input.getMagnitude();
384
0
    int multiplier = producer.getMultiplier(magnitude);
385
0
    input.adjustMagnitude(multiplier);
386
0
    apply(input, status);
387
388
    // If the number rounded to zero, exit.
389
0
    if (input.isZeroish() || U_FAILURE(status)) {
390
0
        return multiplier;
391
0
    }
392
393
    // If the new magnitude after rounding is the same as it was before rounding, then we are done.
394
    // This case applies to most numbers.
395
0
    if (input.getMagnitude() == magnitude + multiplier) {
396
0
        return multiplier;
397
0
    }
398
399
    // If the above case DIDN'T apply, then we have a case like 99.9 -> 100 or 999.9 -> 1000:
400
    // The number rounded up to the next magnitude. Check if the multiplier changes; if it doesn't,
401
    // we do not need to make any more adjustments.
402
0
    int _multiplier = producer.getMultiplier(magnitude + 1);
403
0
    if (multiplier == _multiplier) {
404
0
        return multiplier;
405
0
    }
406
407
    // We have a case like 999.9 -> 1000, where the correct output is "1K", not "1000".
408
    // Fix the magnitude and re-apply the rounding strategy.
409
0
    input.adjustMagnitude(_multiplier - multiplier);
410
0
    apply(input, status);
411
0
    return _multiplier;
412
0
}
413
414
/** This is the method that contains the actual rounding logic. */
415
0
void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const {
416
0
    if (U_FAILURE(status)) {
417
0
        return;
418
0
    }
419
0
    if (fPassThrough) {
420
0
        return;
421
0
    }
422
0
    int32_t resolvedMinFraction = 0;
423
0
    switch (fPrecision.fType) {
424
0
        case Precision::RND_BOGUS:
425
0
        case Precision::RND_ERROR:
426
            // Errors should be caught before the apply() method is called
427
0
            status = U_INTERNAL_PROGRAM_ERROR;
428
0
            break;
429
430
0
        case Precision::RND_NONE:
431
0
            value.roundToInfinity();
432
0
            break;
433
434
0
        case Precision::RND_FRACTION:
435
0
            value.roundToMagnitude(
436
0
                    getRoundingMagnitudeFraction(fPrecision.fUnion.fracSig.fMaxFrac),
437
0
                    fRoundingMode,
438
0
                    status);
439
0
            resolvedMinFraction =
440
0
                    uprv_max(0, -getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac));
441
0
            break;
442
443
0
        case Precision::RND_SIGNIFICANT:
444
0
            value.roundToMagnitude(
445
0
                    getRoundingMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMaxSig),
446
0
                    fRoundingMode,
447
0
                    status);
448
0
            resolvedMinFraction =
449
0
                    uprv_max(0, -getDisplayMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMinSig));
450
            // Make sure that digits are displayed on zero.
451
0
            if (value.isZeroish() && fPrecision.fUnion.fracSig.fMinSig > 0) {
452
0
                value.setMinInteger(1);
453
0
            }
454
0
            break;
455
456
0
        case Precision::RND_FRACTION_SIGNIFICANT: {
457
0
            int32_t roundingMag1 = getRoundingMagnitudeFraction(fPrecision.fUnion.fracSig.fMaxFrac);
458
0
            int32_t roundingMag2 = getRoundingMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMaxSig);
459
0
            int32_t roundingMag;
460
0
            if (fPrecision.fUnion.fracSig.fPriority == UNUM_ROUNDING_PRIORITY_RELAXED) {
461
0
                roundingMag = uprv_min(roundingMag1, roundingMag2);
462
0
            } else {
463
0
                roundingMag = uprv_max(roundingMag1, roundingMag2);
464
0
            }
465
0
            value.roundToMagnitude(roundingMag, fRoundingMode, status);
466
467
0
            int32_t displayMag1 = getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac);
468
0
            int32_t displayMag2 = getDisplayMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMinSig);
469
0
            int32_t displayMag = uprv_min(displayMag1, displayMag2);
470
0
            resolvedMinFraction = uprv_max(0, -displayMag);
471
472
0
            break;
473
0
        }
474
475
0
        case Precision::RND_INCREMENT:
476
0
            value.roundToIncrement(
477
0
                    fPrecision.fUnion.increment.fIncrement,
478
0
                    fRoundingMode,
479
0
                    status);
480
0
            resolvedMinFraction = fPrecision.fUnion.increment.fMinFrac;
481
0
            break;
482
483
0
        case Precision::RND_INCREMENT_ONE:
484
0
            value.roundToMagnitude(
485
0
                    -fPrecision.fUnion.increment.fMaxFrac,
486
0
                    fRoundingMode,
487
0
                    status);
488
0
            resolvedMinFraction = fPrecision.fUnion.increment.fMinFrac;
489
0
            break;
490
491
0
        case Precision::RND_INCREMENT_FIVE:
492
0
            value.roundToNickel(
493
0
                    -fPrecision.fUnion.increment.fMaxFrac,
494
0
                    fRoundingMode,
495
0
                    status);
496
0
            resolvedMinFraction = fPrecision.fUnion.increment.fMinFrac;
497
0
            break;
498
499
0
        case Precision::RND_CURRENCY:
500
            // Call .withCurrency() before .apply()!
501
0
            UPRV_UNREACHABLE;
502
503
0
        default:
504
0
            UPRV_UNREACHABLE;
505
0
    }
506
507
0
    if (fPrecision.fTrailingZeroDisplay == UNUM_TRAILING_ZERO_AUTO ||
508
            // PLURAL_OPERAND_T returns fraction digits as an integer
509
0
            value.getPluralOperand(PLURAL_OPERAND_T) != 0) {
510
0
        value.setMinFraction(resolvedMinFraction);
511
0
    }
512
0
}
513
514
0
void RoundingImpl::apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode /*status*/) {
515
    // This method is intended for the one specific purpose of helping print "00.000E0".
516
    // Question: Is it useful to look at trailingZeroDisplay here?
517
0
    U_ASSERT(isSignificantDigits());
518
0
    U_ASSERT(value.isZeroish());
519
0
    value.setMinFraction(fPrecision.fUnion.fracSig.fMinSig - minInt);
520
0
}
521
522
#endif /* #if !UCONFIG_NO_FORMATTING */