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 namespace {
28 
29 struct CurrencyFormatInfoResult {
30     bool exists;
31     const char16_t* pattern;
32     const char16_t* decimalSeparator;
33     const char16_t* groupingSeparator;
34 };
35 
36 CurrencyFormatInfoResult
getCurrencyFormatInfo(const Locale & locale,const char * isoCode,UErrorCode & status)37 getCurrencyFormatInfo(const Locale& locale, const char* isoCode, UErrorCode& status) {
38     // TODO: Load this data in a centralized location like ICU4J?
39     // TODO: Move this into the CurrencySymbols class?
40     // TODO: Parts of this same data are loaded in dcfmtsym.cpp; should clean up.
41     CurrencyFormatInfoResult result = {false, nullptr, nullptr, nullptr};
42     if (U_FAILURE(status)) { return result; }
43     CharString key;
44     key.append("Currencies/", status);
45     key.append(isoCode, status);
46     UErrorCode localStatus = status;
47     LocalUResourceBundlePointer bundle(ures_open(U_ICUDATA_CURR, locale.getName(), &localStatus));
48     ures_getByKeyWithFallback(bundle.getAlias(), key.data(), bundle.getAlias(), &localStatus);
49     if (U_SUCCESS(localStatus) &&
50         ures_getSize(bundle.getAlias()) > 2) { // the length is 3 if more data is present
51         ures_getByIndex(bundle.getAlias(), 2, bundle.getAlias(), &localStatus);
52         int32_t dummy;
53         result.exists = true;
54         result.pattern = ures_getStringByIndex(bundle.getAlias(), 0, &dummy, &localStatus);
55         result.decimalSeparator = ures_getStringByIndex(bundle.getAlias(), 1, &dummy, &localStatus);
56         result.groupingSeparator = ures_getStringByIndex(bundle.getAlias(), 2, &dummy, &localStatus);
57         status = localStatus;
58     } else if (localStatus != U_MISSING_RESOURCE_ERROR) {
59         status = localStatus;
60     }
61     return result;
62 }
63 
64 }  // namespace
65 
66 
67 MicroPropsGenerator::~MicroPropsGenerator() = default;
68 
69 
NumberFormatterImpl(const MacroProps & macros,UErrorCode & status)70 NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, UErrorCode& status)
71     : NumberFormatterImpl(macros, true, status) {
72 }
73 
formatStatic(const MacroProps & macros,DecimalQuantity & inValue,FormattedStringBuilder & outString,UErrorCode & status)74 int32_t NumberFormatterImpl::formatStatic(const MacroProps& macros, DecimalQuantity& inValue,
75                                        FormattedStringBuilder& outString, UErrorCode& status) {
76     NumberFormatterImpl impl(macros, false, status);
77     MicroProps& micros = impl.preProcessUnsafe(inValue, status);
78     if (U_FAILURE(status)) { return 0; }
79     int32_t length = writeNumber(micros, inValue, outString, 0, status);
80     length += writeAffixes(micros, outString, 0, length, status);
81     return length;
82 }
83 
getPrefixSuffixStatic(const MacroProps & macros,Signum signum,StandardPlural::Form plural,FormattedStringBuilder & outString,UErrorCode & status)84 int32_t NumberFormatterImpl::getPrefixSuffixStatic(const MacroProps& macros, Signum signum,
85                                                    StandardPlural::Form plural,
86                                                    FormattedStringBuilder& outString, UErrorCode& status) {
87     NumberFormatterImpl impl(macros, false, status);
88     return impl.getPrefixSuffixUnsafe(signum, plural, outString, status);
89 }
90 
91 // NOTE: C++ SPECIFIC DIFFERENCE FROM JAVA:
92 // The "safe" apply method uses a new MicroProps. In the MicroPropsGenerator, fMicros is copied into the new instance.
93 // The "unsafe" method simply re-uses fMicros, eliminating the extra copy operation.
94 // See MicroProps::processQuantity() for details.
95 
format(DecimalQuantity & inValue,FormattedStringBuilder & outString,UErrorCode & status) const96 int32_t NumberFormatterImpl::format(DecimalQuantity& inValue, FormattedStringBuilder& outString,
97                                 UErrorCode& status) const {
98     MicroProps micros;
99     preProcess(inValue, micros, status);
100     if (U_FAILURE(status)) { return 0; }
101     int32_t length = writeNumber(micros, inValue, outString, 0, status);
102     length += writeAffixes(micros, outString, 0, length, status);
103     return length;
104 }
105 
preProcess(DecimalQuantity & inValue,MicroProps & microsOut,UErrorCode & status) const106 void NumberFormatterImpl::preProcess(DecimalQuantity& inValue, MicroProps& microsOut,
107                                      UErrorCode& status) const {
108     if (U_FAILURE(status)) { return; }
109     if (fMicroPropsGenerator == nullptr) {
110         status = U_INTERNAL_PROGRAM_ERROR;
111         return;
112     }
113     fMicroPropsGenerator->processQuantity(inValue, microsOut, status);
114     microsOut.rounder.apply(inValue, status);
115     microsOut.integerWidth.apply(inValue, status);
116 }
117 
preProcessUnsafe(DecimalQuantity & inValue,UErrorCode & status)118 MicroProps& NumberFormatterImpl::preProcessUnsafe(DecimalQuantity& inValue, UErrorCode& status) {
119     if (U_FAILURE(status)) {
120         return fMicros; // must always return a value
121     }
122     if (fMicroPropsGenerator == nullptr) {
123         status = U_INTERNAL_PROGRAM_ERROR;
124         return fMicros; // must always return a value
125     }
126     fMicroPropsGenerator->processQuantity(inValue, fMicros, status);
127     fMicros.rounder.apply(inValue, status);
128     fMicros.integerWidth.apply(inValue, status);
129     return fMicros;
130 }
131 
getPrefixSuffix(Signum signum,StandardPlural::Form plural,FormattedStringBuilder & outString,UErrorCode & status) const132 int32_t NumberFormatterImpl::getPrefixSuffix(Signum signum, StandardPlural::Form plural,
133                                              FormattedStringBuilder& outString, UErrorCode& status) const {
134     if (U_FAILURE(status)) { return 0; }
135     // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle, aka pattern modifier).
136     // Safe path: use fImmutablePatternModifier.
137     const Modifier* modifier = fImmutablePatternModifier->getModifier(signum, plural);
138     modifier->apply(outString, 0, 0, status);
139     if (U_FAILURE(status)) { return 0; }
140     return modifier->getPrefixLength();
141 }
142 
getPrefixSuffixUnsafe(Signum signum,StandardPlural::Form plural,FormattedStringBuilder & outString,UErrorCode & status)143 int32_t NumberFormatterImpl::getPrefixSuffixUnsafe(Signum signum, StandardPlural::Form plural,
144                                                    FormattedStringBuilder& outString, UErrorCode& status) {
145     if (U_FAILURE(status)) { return 0; }
146     // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle, aka pattern modifier).
147     // Unsafe path: use fPatternModifier.
148     fPatternModifier->setNumberProperties(signum, plural);
149     fPatternModifier->apply(outString, 0, 0, status);
150     if (U_FAILURE(status)) { return 0; }
151     return fPatternModifier->getPrefixLength();
152 }
153 
NumberFormatterImpl(const MacroProps & macros,bool safe,UErrorCode & status)154 NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, bool safe, UErrorCode& status) {
155     fMicroPropsGenerator = macrosToMicroGenerator(macros, safe, status);
156 }
157 
158 //////////
159 
160 const MicroPropsGenerator*
macrosToMicroGenerator(const MacroProps & macros,bool safe,UErrorCode & status)161 NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, UErrorCode& status) {
162     if (U_FAILURE(status)) { return nullptr; }
163     const MicroPropsGenerator* chain = &fMicros;
164 
165     // Check that macros is error-free before continuing.
166     if (macros.copyErrorTo(status)) {
167         return nullptr;
168     }
169 
170     // TODO: Accept currency symbols from DecimalFormatSymbols?
171 
172     // Pre-compute a few values for efficiency.
173     bool isCurrency = utils::unitIsCurrency(macros.unit);
174     bool isNoUnit = utils::unitIsNoUnit(macros.unit);
175     bool isPercent = utils::unitIsPercent(macros.unit);
176     bool isPermille = utils::unitIsPermille(macros.unit);
177     bool isAccounting =
178             macros.sign == UNUM_SIGN_ACCOUNTING || macros.sign == UNUM_SIGN_ACCOUNTING_ALWAYS ||
179             macros.sign == UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO;
180     CurrencyUnit currency(u"", status);
181     if (isCurrency) {
182         currency = CurrencyUnit(macros.unit, status); // Restore CurrencyUnit from MeasureUnit
183     }
184     const CurrencySymbols* currencySymbols;
185     if (macros.currencySymbols != nullptr) {
186         // Used by the DecimalFormat code path
187         currencySymbols = macros.currencySymbols;
188     } else {
189         fWarehouse.fCurrencySymbols = {currency, macros.locale, status};
190         currencySymbols = &fWarehouse.fCurrencySymbols;
191     }
192     UNumberUnitWidth unitWidth = UNUM_UNIT_WIDTH_SHORT;
193     if (macros.unitWidth != UNUM_UNIT_WIDTH_COUNT) {
194         unitWidth = macros.unitWidth;
195     }
196     bool isCldrUnit = !isCurrency && !isNoUnit &&
197         (unitWidth == UNUM_UNIT_WIDTH_FULL_NAME || !(isPercent || isPermille));
198 
199     // Select the numbering system.
200     LocalPointer<const NumberingSystem> nsLocal;
201     const NumberingSystem* ns;
202     if (macros.symbols.isNumberingSystem()) {
203         ns = macros.symbols.getNumberingSystem();
204     } else {
205         // TODO: Is there a way to avoid creating the NumberingSystem object?
206         ns = NumberingSystem::createInstance(macros.locale, status);
207         // Give ownership to the function scope.
208         nsLocal.adoptInstead(ns);
209     }
210     const char* nsName = U_SUCCESS(status) ? ns->getName() : "latn";
211     uprv_strncpy(fMicros.nsName, nsName, 8);
212     fMicros.nsName[8] = 0; // guarantee NUL-terminated
213 
214     // Resolve the symbols. Do this here because currency may need to customize them.
215     if (macros.symbols.isDecimalFormatSymbols()) {
216         fMicros.symbols = macros.symbols.getDecimalFormatSymbols();
217     } else {
218         auto newSymbols = new DecimalFormatSymbols(macros.locale, *ns, status);
219         if (newSymbols == nullptr) {
220             status = U_MEMORY_ALLOCATION_ERROR;
221             return nullptr;
222         }
223         fMicros.symbols = newSymbols;
224         // Give ownership to the NumberFormatterImpl.
225         fSymbols.adoptInstead(fMicros.symbols);
226     }
227 
228     // Load and parse the pattern string. It is used for grouping sizes and affixes only.
229     // If we are formatting currency, check for a currency-specific pattern.
230     const char16_t* pattern = nullptr;
231     if (isCurrency) {
232         CurrencyFormatInfoResult info = getCurrencyFormatInfo(
233                 macros.locale, currency.getSubtype(), status);
234         if (info.exists) {
235             pattern = info.pattern;
236             // It's clunky to clone an object here, but this code is not frequently executed.
237             auto symbols = new DecimalFormatSymbols(*fMicros.symbols);
238             if (symbols == nullptr) {
239                 status = U_MEMORY_ALLOCATION_ERROR;
240                 return nullptr;
241             }
242             fMicros.symbols = symbols;
243             fSymbols.adoptInstead(symbols);
244             symbols->setSymbol(
245                     DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol,
246                     UnicodeString(info.decimalSeparator),
247                     FALSE);
248             symbols->setSymbol(
249                     DecimalFormatSymbols::ENumberFormatSymbol::kMonetaryGroupingSeparatorSymbol,
250                     UnicodeString(info.groupingSeparator),
251                     FALSE);
252         }
253     }
254     if (pattern == nullptr) {
255         CldrPatternStyle patternStyle;
256         if (isCldrUnit) {
257             patternStyle = CLDR_PATTERN_STYLE_DECIMAL;
258         } else if (isPercent || isPermille) {
259             patternStyle = CLDR_PATTERN_STYLE_PERCENT;
260         } else if (!isCurrency || unitWidth == UNUM_UNIT_WIDTH_FULL_NAME) {
261             patternStyle = CLDR_PATTERN_STYLE_DECIMAL;
262         } else if (isAccounting) {
263             // NOTE: Although ACCOUNTING and ACCOUNTING_ALWAYS are only supported in currencies right now,
264             // the API contract allows us to add support to other units in the future.
265             patternStyle = CLDR_PATTERN_STYLE_ACCOUNTING;
266         } else {
267             patternStyle = CLDR_PATTERN_STYLE_CURRENCY;
268         }
269         pattern = utils::getPatternForStyle(macros.locale, nsName, patternStyle, status);
270     }
271     auto patternInfo = new ParsedPatternInfo();
272     if (patternInfo == nullptr) {
273         status = U_MEMORY_ALLOCATION_ERROR;
274         return nullptr;
275     }
276     fPatternInfo.adoptInstead(patternInfo);
277     PatternParser::parseToPatternInfo(UnicodeString(pattern), *patternInfo, status);
278 
279     /////////////////////////////////////////////////////////////////////////////////////
280     /// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR ///
281     /////////////////////////////////////////////////////////////////////////////////////
282 
283     // Multiplier
284     if (macros.scale.isValid()) {
285         fMicros.helpers.multiplier.setAndChain(macros.scale, chain);
286         chain = &fMicros.helpers.multiplier;
287     }
288 
289     // Rounding strategy
290     Precision precision;
291     if (!macros.precision.isBogus()) {
292         precision = macros.precision;
293     } else if (macros.notation.fType == Notation::NTN_COMPACT) {
294         precision = Precision::integer().withMinDigits(2);
295     } else if (isCurrency) {
296         precision = Precision::currency(UCURR_USAGE_STANDARD);
297     } else {
298         precision = Precision::maxFraction(6);
299     }
300     UNumberFormatRoundingMode roundingMode;
301     if (macros.roundingMode != kDefaultMode) {
302         roundingMode = macros.roundingMode;
303     } else {
304         // Temporary until ICU 64
305         roundingMode = precision.fRoundingMode;
306     }
307     fMicros.rounder = {precision, roundingMode, currency, status};
308 
309     // Grouping strategy
310     if (!macros.grouper.isBogus()) {
311         fMicros.grouping = macros.grouper;
312     } else if (macros.notation.fType == Notation::NTN_COMPACT) {
313         // Compact notation uses minGrouping by default since ICU 59
314         fMicros.grouping = Grouper::forStrategy(UNUM_GROUPING_MIN2);
315     } else {
316         fMicros.grouping = Grouper::forStrategy(UNUM_GROUPING_AUTO);
317     }
318     fMicros.grouping.setLocaleData(*fPatternInfo, macros.locale);
319 
320     // Padding strategy
321     if (!macros.padder.isBogus()) {
322         fMicros.padding = macros.padder;
323     } else {
324         fMicros.padding = Padder::none();
325     }
326 
327     // Integer width
328     if (!macros.integerWidth.isBogus()) {
329         fMicros.integerWidth = macros.integerWidth;
330     } else {
331         fMicros.integerWidth = IntegerWidth::standard();
332     }
333 
334     // Sign display
335     if (macros.sign != UNUM_SIGN_COUNT) {
336         fMicros.sign = macros.sign;
337     } else {
338         fMicros.sign = UNUM_SIGN_AUTO;
339     }
340 
341     // Decimal mark display
342     if (macros.decimal != UNUM_DECIMAL_SEPARATOR_COUNT) {
343         fMicros.decimal = macros.decimal;
344     } else {
345         fMicros.decimal = UNUM_DECIMAL_SEPARATOR_AUTO;
346     }
347 
348     // Use monetary separator symbols
349     fMicros.useCurrency = isCurrency;
350 
351     // Inner modifier (scientific notation)
352     if (macros.notation.fType == Notation::NTN_SCIENTIFIC) {
353         auto newScientificHandler = new ScientificHandler(&macros.notation, fMicros.symbols, chain);
354         if (newScientificHandler == nullptr) {
355             status = U_MEMORY_ALLOCATION_ERROR;
356             return nullptr;
357         }
358         fScientificHandler.adoptInstead(newScientificHandler);
359         chain = fScientificHandler.getAlias();
360     } else {
361         // No inner modifier required
362         fMicros.modInner = &fMicros.helpers.emptyStrongModifier;
363     }
364 
365     // Middle modifier (patterns, positive/negative, currency symbols, percent)
366     auto patternModifier = new MutablePatternModifier(false);
367     if (patternModifier == nullptr) {
368         status = U_MEMORY_ALLOCATION_ERROR;
369         return nullptr;
370     }
371     fPatternModifier.adoptInstead(patternModifier);
372     patternModifier->setPatternInfo(
373             macros.affixProvider != nullptr ? macros.affixProvider
374                                             : static_cast<const AffixPatternProvider*>(fPatternInfo.getAlias()),
375             UNUM_FIELD_COUNT);
376     patternModifier->setPatternAttributes(fMicros.sign, isPermille);
377     if (patternModifier->needsPlurals()) {
378         patternModifier->setSymbols(
379                 fMicros.symbols,
380                 currencySymbols,
381                 unitWidth,
382                 resolvePluralRules(macros.rules, macros.locale, status));
383     } else {
384         patternModifier->setSymbols(fMicros.symbols, currencySymbols, unitWidth, nullptr);
385     }
386     if (safe) {
387         fImmutablePatternModifier.adoptInstead(patternModifier->createImmutableAndChain(chain, status));
388         chain = fImmutablePatternModifier.getAlias();
389     } else {
390         patternModifier->addToChain(chain);
391         chain = patternModifier;
392     }
393 
394     // Outer modifier (CLDR units and currency long names)
395     if (isCldrUnit) {
396         fLongNameHandler.adoptInstead(
397                 LongNameHandler::forMeasureUnit(
398                         macros.locale,
399                         macros.unit,
400                         macros.perUnit,
401                         unitWidth,
402                         resolvePluralRules(macros.rules, macros.locale, status),
403                         chain,
404                         status));
405         chain = fLongNameHandler.getAlias();
406     } else if (isCurrency && unitWidth == UNUM_UNIT_WIDTH_FULL_NAME) {
407         fLongNameHandler.adoptInstead(
408                 LongNameHandler::forCurrencyLongNames(
409                         macros.locale,
410                         currency,
411                         resolvePluralRules(macros.rules, macros.locale, status),
412                         chain,
413                         status));
414         chain = fLongNameHandler.getAlias();
415     } else {
416         // No outer modifier required
417         fMicros.modOuter = &fMicros.helpers.emptyWeakModifier;
418     }
419 
420     // Compact notation
421     // NOTE: Compact notation can (but might not) override the middle modifier and rounding.
422     // It therefore needs to go at the end of the chain.
423     if (macros.notation.fType == Notation::NTN_COMPACT) {
424         CompactType compactType = (isCurrency && unitWidth != UNUM_UNIT_WIDTH_FULL_NAME)
425                                   ? CompactType::TYPE_CURRENCY : CompactType::TYPE_DECIMAL;
426         auto newCompactHandler = new CompactHandler(
427             macros.notation.fUnion.compactStyle,
428             macros.locale,
429             nsName,
430             compactType,
431             resolvePluralRules(macros.rules, macros.locale, status),
432             safe ? patternModifier : nullptr,
433             chain,
434             status);
435         if (newCompactHandler == nullptr) {
436             status = U_MEMORY_ALLOCATION_ERROR;
437             return nullptr;
438         }
439         fCompactHandler.adoptInstead(newCompactHandler);
440         chain = fCompactHandler.getAlias();
441     }
442 
443     return chain;
444 }
445 
446 const PluralRules*
resolvePluralRules(const PluralRules * rulesPtr,const Locale & locale,UErrorCode & status)447 NumberFormatterImpl::resolvePluralRules(const PluralRules* rulesPtr, const Locale& locale,
448                                         UErrorCode& status) {
449     if (rulesPtr != nullptr) {
450         return rulesPtr;
451     }
452     // Lazily create PluralRules
453     if (fRules.isNull()) {
454         fRules.adoptInstead(PluralRules::forLocale(locale, status));
455     }
456     return fRules.getAlias();
457 }
458 
writeAffixes(const MicroProps & micros,FormattedStringBuilder & string,int32_t start,int32_t end,UErrorCode & status)459 int32_t NumberFormatterImpl::writeAffixes(const MicroProps& micros, FormattedStringBuilder& string,
460                                           int32_t start, int32_t end, UErrorCode& status) {
461     // Always apply the inner modifier (which is "strong").
462     int32_t length = micros.modInner->apply(string, start, end, status);
463     if (micros.padding.isValid()) {
464         length += micros.padding
465                 .padAndApply(*micros.modMiddle, *micros.modOuter, string, start, length + end, status);
466     } else {
467         length += micros.modMiddle->apply(string, start, length + end, status);
468         length += micros.modOuter->apply(string, start, length + end, status);
469     }
470     return length;
471 }
472 
writeNumber(const MicroProps & micros,DecimalQuantity & quantity,FormattedStringBuilder & string,int32_t index,UErrorCode & status)473 int32_t NumberFormatterImpl::writeNumber(const MicroProps& micros, DecimalQuantity& quantity,
474                                          FormattedStringBuilder& string, int32_t index,
475                                          UErrorCode& status) {
476     int32_t length = 0;
477     if (quantity.isInfinite()) {
478         length += string.insert(
479                 length + index,
480                 micros.symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kInfinitySymbol),
481                 UNUM_INTEGER_FIELD,
482                 status);
483 
484     } else if (quantity.isNaN()) {
485         length += string.insert(
486                 length + index,
487                 micros.symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kNaNSymbol),
488                 UNUM_INTEGER_FIELD,
489                 status);
490 
491     } else {
492         // Add the integer digits
493         length += writeIntegerDigits(micros, quantity, string, length + index, status);
494 
495         // Add the decimal point
496         if (quantity.getLowerDisplayMagnitude() < 0 || micros.decimal == UNUM_DECIMAL_SEPARATOR_ALWAYS) {
497             length += string.insert(
498                     length + index,
499                     micros.useCurrency ? micros.symbols->getSymbol(
500                             DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol) : micros
501                             .symbols
502                             ->getSymbol(
503                                     DecimalFormatSymbols::ENumberFormatSymbol::kDecimalSeparatorSymbol),
504                     UNUM_DECIMAL_SEPARATOR_FIELD,
505                     status);
506         }
507 
508         // Add the fraction digits
509         length += writeFractionDigits(micros, quantity, string, length + index, status);
510     }
511 
512     return length;
513 }
514 
writeIntegerDigits(const MicroProps & micros,DecimalQuantity & quantity,FormattedStringBuilder & string,int32_t index,UErrorCode & status)515 int32_t NumberFormatterImpl::writeIntegerDigits(const MicroProps& micros, DecimalQuantity& quantity,
516                                                 FormattedStringBuilder& string, int32_t index,
517                                                 UErrorCode& status) {
518     int length = 0;
519     int integerCount = quantity.getUpperDisplayMagnitude() + 1;
520     for (int i = 0; i < integerCount; i++) {
521         // Add grouping separator
522         if (micros.grouping.groupAtPosition(i, quantity)) {
523             length += string.insert(
524                     index,
525                     micros.useCurrency ? micros.symbols->getSymbol(
526                             DecimalFormatSymbols::ENumberFormatSymbol::kMonetaryGroupingSeparatorSymbol)
527                                        : micros.symbols->getSymbol(
528                             DecimalFormatSymbols::ENumberFormatSymbol::kGroupingSeparatorSymbol),
529                     UNUM_GROUPING_SEPARATOR_FIELD,
530                     status);
531         }
532 
533         // Get and append the next digit value
534         int8_t nextDigit = quantity.getDigit(i);
535         length += utils::insertDigitFromSymbols(
536                 string, index, nextDigit, *micros.symbols, UNUM_INTEGER_FIELD, status);
537     }
538     return length;
539 }
540 
writeFractionDigits(const MicroProps & micros,DecimalQuantity & quantity,FormattedStringBuilder & string,int32_t index,UErrorCode & status)541 int32_t NumberFormatterImpl::writeFractionDigits(const MicroProps& micros, DecimalQuantity& quantity,
542                                                  FormattedStringBuilder& string, int32_t index,
543                                                  UErrorCode& status) {
544     int length = 0;
545     int fractionCount = -quantity.getLowerDisplayMagnitude();
546     for (int i = 0; i < fractionCount; i++) {
547         // Get and append the next digit value
548         int8_t nextDigit = quantity.getDigit(-i - 1);
549         length += utils::insertDigitFromSymbols(
550                 string, length + index, nextDigit, *micros.symbols, UNUM_FRACTION_FIELD, status);
551     }
552     return length;
553 }
554 
555 #endif /* #if !UCONFIG_NO_FORMATTING */
556