Coverage Report

Created: 2025-06-24 06:43

/src/icu/source/i18n/number_formatimpl.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 "cstring.h"
9
#include "unicode/ures.h"
10
#include "uresimp.h"
11
#include "charstr.h"
12
#include "number_formatimpl.h"
13
#include "unicode/numfmt.h"
14
#include "number_patternstring.h"
15
#include "number_utils.h"
16
#include "unicode/numberformatter.h"
17
#include "unicode/dcfmtsym.h"
18
#include "number_scientific.h"
19
#include "number_compact.h"
20
#include "uresimp.h"
21
#include "ureslocs.h"
22
23
using namespace icu;
24
using namespace icu::number;
25
using namespace icu::number::impl;
26
27
28
NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, UErrorCode& status)
29
0
    : NumberFormatterImpl(macros, true, status) {
30
0
}
31
32
int32_t NumberFormatterImpl::formatStatic(const MacroProps &macros, UFormattedNumberData *results,
33
0
                                          UErrorCode &status) {
34
0
    DecimalQuantity &inValue = results->quantity;
35
0
    FormattedStringBuilder &outString = results->getStringRef();
36
0
    NumberFormatterImpl impl(macros, false, status);
37
0
    MicroProps& micros = impl.preProcessUnsafe(inValue, status);
38
0
    if (U_FAILURE(status)) { return 0; }
39
0
    int32_t length = writeNumber(micros, inValue, outString, 0, status);
40
0
    length += writeAffixes(micros, outString, 0, length, status);
41
0
    results->outputUnit = std::move(micros.outputUnit);
42
0
    results->gender = micros.gender;
43
0
    return length;
44
0
}
45
46
int32_t NumberFormatterImpl::getPrefixSuffixStatic(const MacroProps& macros, Signum signum,
47
                                                   StandardPlural::Form plural,
48
0
                                                   FormattedStringBuilder& outString, UErrorCode& status) {
49
0
    NumberFormatterImpl impl(macros, false, status);
50
0
    return impl.getPrefixSuffixUnsafe(signum, plural, outString, status);
51
0
}
52
53
// NOTE: C++ SPECIFIC DIFFERENCE FROM JAVA:
54
// The "safe" apply method uses a new MicroProps. In the MicroPropsGenerator, fMicros is copied into the new instance.
55
// The "unsafe" method simply re-uses fMicros, eliminating the extra copy operation.
56
// See MicroProps::processQuantity() for details.
57
58
0
int32_t NumberFormatterImpl::format(UFormattedNumberData *results, UErrorCode &status) const {
59
0
    DecimalQuantity &inValue = results->quantity;
60
0
    FormattedStringBuilder &outString = results->getStringRef();
61
0
    MicroProps micros;
62
0
    preProcess(inValue, micros, status);
63
0
    if (U_FAILURE(status)) { return 0; }
64
0
    int32_t length = writeNumber(micros, inValue, outString, 0, status);
65
0
    length += writeAffixes(micros, outString, 0, length, status);
66
0
    results->outputUnit = std::move(micros.outputUnit);
67
0
    results->gender = micros.gender;
68
0
    return length;
69
0
}
70
71
void NumberFormatterImpl::preProcess(DecimalQuantity& inValue, MicroProps& microsOut,
72
0
                                     UErrorCode& status) const {
73
0
    if (U_FAILURE(status)) { return; }
74
0
    if (fMicroPropsGenerator == nullptr) {
75
0
        status = U_INTERNAL_PROGRAM_ERROR;
76
0
        return;
77
0
    }
78
0
    fMicroPropsGenerator->processQuantity(inValue, microsOut, status);
79
0
    microsOut.integerWidth.apply(inValue, status);
80
0
}
81
82
0
MicroProps& NumberFormatterImpl::preProcessUnsafe(DecimalQuantity& inValue, UErrorCode& status) {
83
0
    if (U_FAILURE(status)) {
84
0
        return fMicros; // must always return a value
85
0
    }
86
0
    if (fMicroPropsGenerator == nullptr) {
87
0
        status = U_INTERNAL_PROGRAM_ERROR;
88
0
        return fMicros; // must always return a value
89
0
    }
90
0
    fMicroPropsGenerator->processQuantity(inValue, fMicros, status);
91
0
    fMicros.integerWidth.apply(inValue, status);
92
0
    return fMicros;
93
0
}
94
95
int32_t NumberFormatterImpl::getPrefixSuffix(Signum signum, StandardPlural::Form plural,
96
0
                                             FormattedStringBuilder& outString, UErrorCode& status) const {
97
0
    if (U_FAILURE(status)) { return 0; }
98
    // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle, aka pattern modifier).
99
    // Safe path: use fImmutablePatternModifier.
100
0
    const Modifier* modifier = fImmutablePatternModifier->getModifier(signum, plural);
101
0
    modifier->apply(outString, 0, 0, status);
102
0
    if (U_FAILURE(status)) { return 0; }
103
0
    return modifier->getPrefixLength();
104
0
}
105
106
int32_t NumberFormatterImpl::getPrefixSuffixUnsafe(Signum signum, StandardPlural::Form plural,
107
0
                                                   FormattedStringBuilder& outString, UErrorCode& status) {
108
0
    if (U_FAILURE(status)) { return 0; }
109
    // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle, aka pattern modifier).
110
    // Unsafe path: use fPatternModifier.
111
0
    fPatternModifier->setNumberProperties(signum, plural);
112
0
    fPatternModifier->apply(outString, 0, 0, status);
113
0
    if (U_FAILURE(status)) { return 0; }
114
0
    return fPatternModifier->getPrefixLength();
115
0
}
116
117
0
NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, bool safe, UErrorCode& status) {
118
0
    fMicroPropsGenerator = macrosToMicroGenerator(macros, safe, status);
119
0
}
120
121
//////////
122
123
const MicroPropsGenerator*
124
0
NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, UErrorCode& status) {
125
0
    if (U_FAILURE(status)) { return nullptr; }
126
0
    const MicroPropsGenerator* chain = &fMicros;
127
128
    // Check that macros is error-free before continuing.
129
0
    if (macros.copyErrorTo(status)) {
130
0
        return nullptr;
131
0
    }
132
133
    // TODO: Accept currency symbols from DecimalFormatSymbols?
134
135
    // Pre-compute a few values for efficiency.
136
0
    bool isCurrency = utils::unitIsCurrency(macros.unit);
137
0
    bool isBaseUnit = utils::unitIsBaseUnit(macros.unit);
138
0
    bool isPercent = utils::unitIsPercent(macros.unit);
139
0
    bool isPermille = utils::unitIsPermille(macros.unit);
140
0
    bool isCompactNotation = macros.notation.fType == Notation::NTN_COMPACT;
141
0
    bool isAccounting =
142
0
            macros.sign == UNUM_SIGN_ACCOUNTING ||
143
0
            macros.sign == UNUM_SIGN_ACCOUNTING_ALWAYS ||
144
0
            macros.sign == UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO ||
145
0
            macros.sign == UNUM_SIGN_ACCOUNTING_NEGATIVE;
146
0
    CurrencyUnit currency(u"", status);
147
0
    if (isCurrency) {
148
0
        currency = CurrencyUnit(macros.unit, status); // Restore CurrencyUnit from MeasureUnit
149
0
    }
150
0
    UNumberUnitWidth unitWidth = UNUM_UNIT_WIDTH_SHORT;
151
0
    if (macros.unitWidth != UNUM_UNIT_WIDTH_COUNT) {
152
0
        unitWidth = macros.unitWidth;
153
0
    }
154
    // Use CLDR unit data for all MeasureUnits (not currency and not
155
    // no-unit), except use the dedicated percent pattern for percent and
156
    // permille. However, use the CLDR unit data for percent/permille if a
157
    // long name was requested OR if compact notation is being used, since
158
    // compact notation overrides the middle modifier (micros.modMiddle)
159
    // normally used for the percent pattern.
160
0
    bool isCldrUnit = !isCurrency
161
0
        && !isBaseUnit
162
0
        && (unitWidth == UNUM_UNIT_WIDTH_FULL_NAME
163
0
            || !(isPercent || isPermille)
164
0
            || isCompactNotation
165
0
        );
166
0
    bool isMixedUnit = isCldrUnit && (uprv_strcmp(macros.unit.getType(), "") == 0) &&
167
0
                       macros.unit.getComplexity(status) == UMEASURE_UNIT_MIXED;
168
169
    // Select the numbering system.
170
0
    LocalPointer<const NumberingSystem> nsLocal;
171
0
    const NumberingSystem* ns;
172
0
    if (macros.symbols.isNumberingSystem()) {
173
0
        ns = macros.symbols.getNumberingSystem();
174
0
    } else {
175
        // TODO: Is there a way to avoid creating the NumberingSystem object?
176
0
        ns = NumberingSystem::createInstance(macros.locale, status);
177
        // Give ownership to the function scope.
178
0
        nsLocal.adoptInstead(ns);
179
0
    }
180
0
    const char* nsName = U_SUCCESS(status) ? ns->getName() : "latn";
181
0
    uprv_strncpy(fMicros.nsName, nsName, 8);
182
0
    fMicros.nsName[8] = 0; // guarantee NUL-terminated
183
184
    // Default gender: none.
185
0
    fMicros.gender = "";
186
187
    // Resolve the symbols. Do this here because currency may need to customize them.
188
0
    if (macros.symbols.isDecimalFormatSymbols()) {
189
0
        fMicros.symbols = macros.symbols.getDecimalFormatSymbols();
190
0
    } else {
191
0
        LocalPointer<DecimalFormatSymbols> newSymbols(
192
0
            new DecimalFormatSymbols(macros.locale, *ns, status), status);
193
0
        if (U_FAILURE(status)) {
194
0
            return nullptr;
195
0
        }
196
0
        if (isCurrency) {
197
0
            newSymbols->setCurrency(currency.getISOCurrency(), status);
198
0
            if (U_FAILURE(status)) {
199
0
                return nullptr;
200
0
            }
201
0
        }
202
0
        fMicros.symbols = newSymbols.getAlias();
203
0
        fSymbols.adoptInstead(newSymbols.orphan());
204
0
    }
205
206
    // Load and parse the pattern string. It is used for grouping sizes and affixes only.
207
    // If we are formatting currency, check for a currency-specific pattern.
208
0
    const char16_t* pattern = nullptr;
209
0
    if (isCurrency && fMicros.symbols->getCurrencyPattern() != nullptr) {
210
0
        pattern = fMicros.symbols->getCurrencyPattern();
211
0
    }
212
0
    if (pattern == nullptr) {
213
0
        CldrPatternStyle patternStyle;
214
0
        if (isCldrUnit) {
215
0
            patternStyle = CLDR_PATTERN_STYLE_DECIMAL;
216
0
        } else if (isPercent || isPermille) {
217
0
            patternStyle = CLDR_PATTERN_STYLE_PERCENT;
218
0
        } else if (!isCurrency || unitWidth == UNUM_UNIT_WIDTH_FULL_NAME) {
219
0
            patternStyle = CLDR_PATTERN_STYLE_DECIMAL;
220
0
        } else if (isAccounting) {
221
            // NOTE: Although ACCOUNTING and ACCOUNTING_ALWAYS are only supported in currencies right now,
222
            // the API contract allows us to add support to other units in the future.
223
0
            patternStyle = CLDR_PATTERN_STYLE_ACCOUNTING;
224
0
        } else {
225
0
            patternStyle = CLDR_PATTERN_STYLE_CURRENCY;
226
0
        }
227
0
        pattern = utils::getPatternForStyle(macros.locale, nsName, patternStyle, status);
228
0
        if (U_FAILURE(status)) {
229
0
            return nullptr;
230
0
        }
231
0
    }
232
0
    auto patternInfo = new ParsedPatternInfo();
233
0
    if (patternInfo == nullptr) {
234
0
        status = U_MEMORY_ALLOCATION_ERROR;
235
0
        return nullptr;
236
0
    }
237
0
    fPatternInfo.adoptInstead(patternInfo);
238
0
    PatternParser::parseToPatternInfo(UnicodeString(pattern), *patternInfo, status);
239
0
    if (U_FAILURE(status)) {
240
0
        return nullptr;
241
0
    }
242
243
    /////////////////////////////////////////////////////////////////////////////////////
244
    /// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR ///
245
    /////////////////////////////////////////////////////////////////////////////////////
246
247
    // Unit Preferences and Conversions as our first step
248
0
    if (macros.usage.isSet()) {
249
0
        if (!isCldrUnit) {
250
            // We only support "usage" when the input unit is specified, and is
251
            // a CLDR Unit.
252
0
            status = U_ILLEGAL_ARGUMENT_ERROR;
253
0
            return nullptr;
254
0
        }
255
0
        auto usagePrefsHandler =
256
0
            new UsagePrefsHandler(macros.locale, macros.unit, macros.usage.fValue, chain, status);
257
0
        fUsagePrefsHandler.adoptInsteadAndCheckErrorCode(usagePrefsHandler, status);
258
0
        chain = fUsagePrefsHandler.getAlias();
259
0
    } else if (isMixedUnit) {
260
0
        auto unitConversionHandler = new UnitConversionHandler(macros.unit, chain, status);
261
0
        fUnitConversionHandler.adoptInsteadAndCheckErrorCode(unitConversionHandler, status);
262
0
        chain = fUnitConversionHandler.getAlias();
263
0
    }
264
265
    // Multiplier
266
0
    if (macros.scale.isValid()) {
267
0
        fMicros.helpers.multiplier.setAndChain(macros.scale, chain);
268
0
        chain = &fMicros.helpers.multiplier;
269
0
    }
270
271
    // Rounding strategy
272
0
    Precision precision;
273
0
    if (!macros.precision.isBogus()) {
274
0
        precision = macros.precision;
275
0
    } else if (isCompactNotation) {
276
0
        precision = Precision::integer().withMinDigits(2);
277
0
    } else if (isCurrency) {
278
0
        precision = Precision::currency(UCURR_USAGE_STANDARD);
279
0
    } else if (macros.usage.isSet()) {
280
        // Bogus Precision - it will get set in the UsagePrefsHandler instead
281
0
        precision = Precision();
282
0
    } else {
283
0
        precision = Precision::maxFraction(6);
284
0
    }
285
0
    UNumberFormatRoundingMode roundingMode;
286
0
    roundingMode = macros.roundingMode;
287
0
    fMicros.rounder = {precision, roundingMode, currency, status};
288
0
    if (U_FAILURE(status)) {
289
0
        return nullptr;
290
0
    }
291
292
    // Grouping strategy
293
0
    if (!macros.grouper.isBogus()) {
294
0
        fMicros.grouping = macros.grouper;
295
0
    } else if (isCompactNotation) {
296
        // Compact notation uses minGrouping by default since ICU 59
297
0
        fMicros.grouping = Grouper::forStrategy(UNUM_GROUPING_MIN2);
298
0
    } else {
299
0
        fMicros.grouping = Grouper::forStrategy(UNUM_GROUPING_AUTO);
300
0
    }
301
0
    fMicros.grouping.setLocaleData(*fPatternInfo, macros.locale);
302
303
    // Padding strategy
304
0
    if (!macros.padder.isBogus()) {
305
0
        fMicros.padding = macros.padder;
306
0
    } else {
307
0
        fMicros.padding = Padder::none();
308
0
    }
309
310
    // Integer width
311
0
    if (!macros.integerWidth.isBogus()) {
312
0
        fMicros.integerWidth = macros.integerWidth;
313
0
    } else {
314
0
        fMicros.integerWidth = IntegerWidth::standard();
315
0
    }
316
317
    // Sign display
318
0
    if (macros.sign != UNUM_SIGN_COUNT) {
319
0
        fMicros.sign = macros.sign;
320
0
    } else {
321
0
        fMicros.sign = UNUM_SIGN_AUTO;
322
0
    }
323
324
    // Decimal mark display
325
0
    if (macros.decimal != UNUM_DECIMAL_SEPARATOR_COUNT) {
326
0
        fMicros.decimal = macros.decimal;
327
0
    } else {
328
0
        fMicros.decimal = UNUM_DECIMAL_SEPARATOR_AUTO;
329
0
    }
330
331
    // Use monetary separator symbols
332
0
    fMicros.useCurrency = isCurrency;
333
334
    // Inner modifier (scientific notation)
335
0
    if (macros.notation.fType == Notation::NTN_SCIENTIFIC) {
336
0
        auto newScientificHandler = new ScientificHandler(&macros.notation, fMicros.symbols, chain);
337
0
        if (newScientificHandler == nullptr) {
338
0
            status = U_MEMORY_ALLOCATION_ERROR;
339
0
            return nullptr;
340
0
        }
341
0
        fScientificHandler.adoptInstead(newScientificHandler);
342
0
        chain = fScientificHandler.getAlias();
343
0
    } else {
344
        // No inner modifier required
345
0
        fMicros.modInner = &fMicros.helpers.emptyStrongModifier;
346
0
    }
347
348
    // Middle modifier (patterns, positive/negative, currency symbols, percent)
349
0
    auto patternModifier = new MutablePatternModifier(false);
350
0
    if (patternModifier == nullptr) {
351
0
        status = U_MEMORY_ALLOCATION_ERROR;
352
0
        return nullptr;
353
0
    }
354
0
    fPatternModifier.adoptInstead(patternModifier);
355
0
    patternModifier->setPatternInfo(
356
0
            macros.affixProvider != nullptr ? macros.affixProvider
357
0
                                            : static_cast<const AffixPatternProvider*>(fPatternInfo.getAlias()),
358
0
            kUndefinedField);
359
0
    patternModifier->setPatternAttributes(fMicros.sign, isPermille, macros.approximately);
360
0
    if (patternModifier->needsPlurals()) {
361
0
        patternModifier->setSymbols(
362
0
                fMicros.symbols,
363
0
                currency,
364
0
                unitWidth,
365
0
                resolvePluralRules(macros.rules, macros.locale, status),
366
0
                status);
367
0
    } else {
368
0
        patternModifier->setSymbols(fMicros.symbols, currency, unitWidth, nullptr, status);
369
0
    }
370
0
    if (safe) {
371
0
        fImmutablePatternModifier.adoptInsteadAndCheckErrorCode(patternModifier->createImmutable(status),
372
0
                                                                status);
373
0
    }
374
0
    if (U_FAILURE(status)) {
375
0
        return nullptr;
376
0
    }
377
378
    // Outer modifier (CLDR units and currency long names)
379
0
    if (isCldrUnit) {
380
0
        const char *unitDisplayCase = "";
381
0
        if (macros.unitDisplayCase.isSet()) {
382
0
            unitDisplayCase = macros.unitDisplayCase.fValue;
383
0
        }
384
0
        if (macros.usage.isSet()) {
385
0
            fLongNameMultiplexer.adoptInsteadAndCheckErrorCode(
386
0
                LongNameMultiplexer::forMeasureUnits(
387
0
                    macros.locale, *fUsagePrefsHandler->getOutputUnits(), unitWidth, unitDisplayCase,
388
0
                    resolvePluralRules(macros.rules, macros.locale, status), chain, status),
389
0
                status);
390
0
            chain = fLongNameMultiplexer.getAlias();
391
0
        } else if (isMixedUnit) {
392
0
            fMixedUnitLongNameHandler.adoptInsteadAndCheckErrorCode(new MixedUnitLongNameHandler(),
393
0
                                                                    status);
394
0
            MixedUnitLongNameHandler::forMeasureUnit(
395
0
                macros.locale, macros.unit, unitWidth, unitDisplayCase,
396
0
                resolvePluralRules(macros.rules, macros.locale, status), chain,
397
0
                fMixedUnitLongNameHandler.getAlias(), status);
398
0
            chain = fMixedUnitLongNameHandler.getAlias();
399
0
        } else {
400
0
            MeasureUnit unit = macros.unit;
401
0
            if (!utils::unitIsBaseUnit(macros.perUnit)) {
402
0
                unit = unit.product(macros.perUnit.reciprocal(status), status);
403
                // This isn't strictly necessary, but was what we specced out
404
                // when perUnit became a backward-compatibility thing:
405
                // unit/perUnit use case is only valid if both units are
406
                // built-ins, or the product is a built-in.
407
0
                if (uprv_strcmp(unit.getType(), "") == 0 &&
408
0
                    (uprv_strcmp(macros.unit.getType(), "") == 0 ||
409
0
                     uprv_strcmp(macros.perUnit.getType(), "") == 0)) {
410
0
                    status = U_UNSUPPORTED_ERROR;
411
0
                    return nullptr;
412
0
                }
413
0
            }
414
0
            fLongNameHandler.adoptInsteadAndCheckErrorCode(new LongNameHandler(), status);
415
0
            LongNameHandler::forMeasureUnit(macros.locale, unit, unitWidth, unitDisplayCase,
416
0
                                            resolvePluralRules(macros.rules, macros.locale, status),
417
0
                                            chain, fLongNameHandler.getAlias(), status);
418
0
            chain = fLongNameHandler.getAlias();
419
0
        }
420
0
    } else if (isCurrency && unitWidth == UNUM_UNIT_WIDTH_FULL_NAME) {
421
0
        fLongNameHandler.adoptInsteadAndCheckErrorCode(
422
0
            LongNameHandler::forCurrencyLongNames(
423
0
                macros.locale, currency, resolvePluralRules(macros.rules, macros.locale, status), chain,
424
0
                status),
425
0
            status);
426
0
        chain = fLongNameHandler.getAlias();
427
0
    } else {
428
        // No outer modifier required
429
0
        fMicros.modOuter = &fMicros.helpers.emptyWeakModifier;
430
0
    }
431
0
    if (U_FAILURE(status)) {
432
0
        return nullptr;
433
0
    }
434
435
    // Compact notation
436
0
    if (isCompactNotation) {
437
0
        CompactType compactType = (isCurrency && unitWidth != UNUM_UNIT_WIDTH_FULL_NAME)
438
0
                                  ? CompactType::TYPE_CURRENCY : CompactType::TYPE_DECIMAL;
439
0
        auto newCompactHandler = new CompactHandler(
440
0
            macros.notation.fUnion.compactStyle,
441
0
            macros.locale,
442
0
            nsName,
443
0
            compactType,
444
0
            resolvePluralRules(macros.rules, macros.locale, status),
445
0
            patternModifier,
446
0
            safe,
447
0
            chain,
448
0
            status);
449
0
        if (U_FAILURE(status)) {
450
0
            return nullptr;
451
0
        }
452
0
        if (newCompactHandler == nullptr) {
453
0
            status = U_MEMORY_ALLOCATION_ERROR;
454
0
            return nullptr;
455
0
        }
456
0
        fCompactHandler.adoptInstead(newCompactHandler);
457
0
        chain = fCompactHandler.getAlias();
458
0
    }
459
0
    if (U_FAILURE(status)) {
460
0
        return nullptr;
461
0
    }
462
463
    // Always add the pattern modifier as the last element of the chain.
464
0
    if (safe) {
465
0
        fImmutablePatternModifier->addToChain(chain);
466
0
        chain = fImmutablePatternModifier.getAlias();
467
0
    } else {
468
0
        patternModifier->addToChain(chain);
469
0
        chain = patternModifier;
470
0
    }
471
472
0
    return chain;
473
0
}
474
475
const PluralRules*
476
NumberFormatterImpl::resolvePluralRules(const PluralRules* rulesPtr, const Locale& locale,
477
0
                                        UErrorCode& status) {
478
0
    if (rulesPtr != nullptr) {
479
0
        return rulesPtr;
480
0
    }
481
    // Lazily create PluralRules
482
0
    if (fRules.isNull()) {
483
0
        fRules.adoptInstead(PluralRules::forLocale(locale, status));
484
0
    }
485
0
    return fRules.getAlias();
486
0
}
487
488
int32_t NumberFormatterImpl::writeAffixes(const MicroProps& micros, FormattedStringBuilder& string,
489
0
                                          int32_t start, int32_t end, UErrorCode& status) {
490
0
    U_ASSERT(micros.modOuter != nullptr);
491
    // Always apply the inner modifier (which is "strong").
492
0
    int32_t length = micros.modInner->apply(string, start, end, status);
493
0
    if (micros.padding.isValid()) {
494
0
        length += micros.padding
495
0
                .padAndApply(*micros.modMiddle, *micros.modOuter, string, start, length + end, status);
496
0
    } else {
497
0
        length += micros.modMiddle->apply(string, start, length + end, status);
498
0
        length += micros.modOuter->apply(string, start, length + end, status);
499
0
    }
500
0
    return length;
501
0
}
502
503
int32_t NumberFormatterImpl::writeNumber(const MicroProps& micros, DecimalQuantity& quantity,
504
                                         FormattedStringBuilder& string, int32_t index,
505
0
                                         UErrorCode& status) {
506
0
    int32_t length = 0;
507
0
    if (quantity.isInfinite()) {
508
0
        length += string.insert(
509
0
                length + index,
510
0
                micros.symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kInfinitySymbol),
511
0
                {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD},
512
0
                status);
513
514
0
    } else if (quantity.isNaN()) {
515
0
        length += string.insert(
516
0
                length + index,
517
0
                micros.symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kNaNSymbol),
518
0
                {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD},
519
0
                status);
520
521
0
    } else {
522
        // Add the integer digits
523
0
        length += writeIntegerDigits(micros, quantity, string, length + index, status);
524
525
        // Add the decimal point
526
0
        if (quantity.getLowerDisplayMagnitude() < 0 || micros.decimal == UNUM_DECIMAL_SEPARATOR_ALWAYS) {
527
0
            length += string.insert(
528
0
                    length + index,
529
0
                    micros.useCurrency ? micros.symbols->getSymbol(
530
0
                            DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol) : micros
531
0
                            .symbols
532
0
                            ->getSymbol(
533
0
                                    DecimalFormatSymbols::ENumberFormatSymbol::kDecimalSeparatorSymbol),
534
0
                    {UFIELD_CATEGORY_NUMBER, UNUM_DECIMAL_SEPARATOR_FIELD},
535
0
                    status);
536
0
        }
537
538
        // Add the fraction digits
539
0
        length += writeFractionDigits(micros, quantity, string, length + index, status);
540
541
0
        if (length == 0) {
542
            // Force output of the digit for value 0
543
0
            length += utils::insertDigitFromSymbols(
544
0
                    string,
545
0
                    index,
546
0
                    0,
547
0
                    *micros.symbols,
548
0
                    {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD},
549
0
                    status);
550
0
        }
551
0
    }
552
553
0
    return length;
554
0
}
555
556
int32_t NumberFormatterImpl::writeIntegerDigits(const MicroProps& micros, DecimalQuantity& quantity,
557
                                                FormattedStringBuilder& string, int32_t index,
558
0
                                                UErrorCode& status) {
559
0
    int length = 0;
560
0
    int integerCount = quantity.getUpperDisplayMagnitude() + 1;
561
0
    for (int i = 0; i < integerCount; i++) {
562
        // Add grouping separator
563
0
        if (micros.grouping.groupAtPosition(i, quantity)) {
564
0
            length += string.insert(
565
0
                    index,
566
0
                    micros.useCurrency ? micros.symbols->getSymbol(
567
0
                            DecimalFormatSymbols::ENumberFormatSymbol::kMonetaryGroupingSeparatorSymbol)
568
0
                                       : micros.symbols->getSymbol(
569
0
                            DecimalFormatSymbols::ENumberFormatSymbol::kGroupingSeparatorSymbol),
570
0
                    {UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD},
571
0
                    status);
572
0
        }
573
574
        // Get and append the next digit value
575
0
        int8_t nextDigit = quantity.getDigit(i);
576
0
        length += utils::insertDigitFromSymbols(
577
0
                string,
578
0
                index,
579
0
                nextDigit,
580
0
                *micros.symbols,
581
0
                {UFIELD_CATEGORY_NUMBER,
582
0
                UNUM_INTEGER_FIELD},
583
0
                status);
584
0
    }
585
0
    return length;
586
0
}
587
588
int32_t NumberFormatterImpl::writeFractionDigits(const MicroProps& micros, DecimalQuantity& quantity,
589
                                                 FormattedStringBuilder& string, int32_t index,
590
0
                                                 UErrorCode& status) {
591
0
    int length = 0;
592
0
    int fractionCount = -quantity.getLowerDisplayMagnitude();
593
0
    for (int i = 0; i < fractionCount; i++) {
594
        // Get and append the next digit value
595
0
        int8_t nextDigit = quantity.getDigit(-i - 1);
596
0
        length += utils::insertDigitFromSymbols(
597
0
                string,
598
0
                length + index,
599
0
                nextDigit,
600
0
                *micros.symbols,
601
0
                {UFIELD_CATEGORY_NUMBER, UNUM_FRACTION_FIELD},
602
0
                status);
603
0
    }
604
0
    return length;
605
0
}
606
607
#endif /* #if !UCONFIG_NO_FORMATTING */