]> git.saurik.com Git - apple/icu.git/blame_incremental - icuSources/i18n/number_formatimpl.cpp
ICU-66108.tar.gz
[apple/icu.git] / icuSources / i18n / number_formatimpl.cpp
... / ...
CommitLineData
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
23using namespace icu;
24using namespace icu::number;
25using namespace icu::number::impl;
26
27namespace {
28
29struct CurrencyFormatInfoResult {
30 bool exists;
31 const char16_t* pattern;
32 const char16_t* decimalSeparator;
33 const char16_t* groupingSeparator;
34};
35
36CurrencyFormatInfoResult
37getCurrencyFormatInfo(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
67MicroPropsGenerator::~MicroPropsGenerator() = default;
68
69
70NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, UErrorCode& status)
71 : NumberFormatterImpl(macros, true, status) {
72}
73
74int32_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
84int32_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
96int32_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
106void 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
118MicroProps& 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
132int32_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
143int32_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
154NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, bool safe, UErrorCode& status) {
155 fMicroPropsGenerator = macrosToMicroGenerator(macros, safe, status);
156}
157
158//////////
159
160const MicroPropsGenerator*
161NumberFormatterImpl::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 const NumberingSystem* ns = nullptr;
201 if (macros.symbols.isNumberingSystem()) {
202 ns = macros.symbols.getNumberingSystem();
203 }
204 // else there is no need to create a NumberingSystem object, it is only used
205 // for two things (Apple rdar://51672521):
206 // 1. Passing to new DecimalFormatSymbols; but if we do not have one, new
207 // DecimalFormat symbols can create one anyway so there is no need to do it
208 // ahead of time, and
209 // 2. Getting the nsName. But with a small DecimalFormatSymbols change (per
210 // rdar://51672521) we can get the name from DecimalFormatSymbols which we need
211 // here anyway.
212 const char* nsName = (ns != nullptr)? ns->getName() : nullptr;
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 = (ns != nullptr)?
219 new DecimalFormatSymbols(macros.locale, *ns, status):
220 new DecimalFormatSymbols(macros.locale, status);
221 if (newSymbols == nullptr) {
222 status = U_MEMORY_ALLOCATION_ERROR;
223 return nullptr;
224 }
225 fMicros.symbols = newSymbols;
226 // Give ownership to the NumberFormatterImpl.
227 fSymbols.adoptInstead(fMicros.symbols);
228 }
229 // Resolve nsName and save (Apple rdar://51672521)
230 if (nsName == nullptr && U_SUCCESS(status)) {
231 nsName = fMicros.symbols->getNSName();
232 }
233 if (nsName == nullptr || nsName[0] == 0) {
234 nsName = "latn";
235 }
236 uprv_strncpy(fMicros.nsName, nsName, 8);
237 fMicros.nsName[8] = 0; // guarantee NUL-terminated
238
239 // Load and parse the pattern string. It is used for grouping sizes and affixes only.
240 // If we are formatting currency, check for a currency-specific pattern.
241 const char16_t* pattern = nullptr;
242 if (isCurrency) {
243 CurrencyFormatInfoResult info = getCurrencyFormatInfo(
244 macros.locale, currency.getSubtype(), status);
245 if (info.exists) {
246 pattern = info.pattern;
247 // It's clunky to clone an object here, but this code is not frequently executed.
248 auto symbols = new DecimalFormatSymbols(*fMicros.symbols);
249 if (symbols == nullptr) {
250 status = U_MEMORY_ALLOCATION_ERROR;
251 return nullptr;
252 }
253 fMicros.symbols = symbols;
254 fSymbols.adoptInstead(symbols);
255 symbols->setSymbol(
256 DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol,
257 UnicodeString(info.decimalSeparator),
258 FALSE);
259 symbols->setSymbol(
260 DecimalFormatSymbols::ENumberFormatSymbol::kMonetaryGroupingSeparatorSymbol,
261 UnicodeString(info.groupingSeparator),
262 FALSE);
263 }
264 }
265 if (pattern == nullptr) {
266 CldrPatternStyle patternStyle;
267 if (isCldrUnit) {
268 patternStyle = CLDR_PATTERN_STYLE_DECIMAL;
269 } else if (isPercent || isPermille) {
270 patternStyle = CLDR_PATTERN_STYLE_PERCENT;
271 } else if (!isCurrency || unitWidth == UNUM_UNIT_WIDTH_FULL_NAME) {
272 patternStyle = CLDR_PATTERN_STYLE_DECIMAL;
273 } else if (isAccounting) {
274 // NOTE: Although ACCOUNTING and ACCOUNTING_ALWAYS are only supported in currencies right now,
275 // the API contract allows us to add support to other units in the future.
276 patternStyle = CLDR_PATTERN_STYLE_ACCOUNTING;
277 } else {
278 patternStyle = CLDR_PATTERN_STYLE_CURRENCY;
279 }
280 pattern = utils::getPatternForStyle(macros.locale, nsName, patternStyle, status);
281 }
282 auto patternInfo = new ParsedPatternInfo();
283 if (patternInfo == nullptr) {
284 status = U_MEMORY_ALLOCATION_ERROR;
285 return nullptr;
286 }
287 fPatternInfo.adoptInstead(patternInfo);
288 PatternParser::parseToPatternInfo(UnicodeString(pattern), *patternInfo, status);
289
290 /////////////////////////////////////////////////////////////////////////////////////
291 /// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR ///
292 /////////////////////////////////////////////////////////////////////////////////////
293
294 // Multiplier
295 if (macros.scale.isValid()) {
296 fMicros.helpers.multiplier.setAndChain(macros.scale, chain);
297 chain = &fMicros.helpers.multiplier;
298 }
299
300 // Rounding strategy
301 Precision precision;
302 if (!macros.precision.isBogus()) {
303 precision = macros.precision;
304 } else if (macros.notation.fType == Notation::NTN_COMPACT) {
305 precision = Precision::integer().withMinDigits(2);
306 } else if (isCurrency) {
307 precision = Precision::currency(UCURR_USAGE_STANDARD);
308 } else {
309 precision = Precision::maxFraction(6);
310 }
311 UNumberFormatRoundingMode roundingMode;
312 if (macros.roundingMode != kDefaultMode) {
313 roundingMode = macros.roundingMode;
314 } else {
315 // Temporary until ICU 64
316 roundingMode = precision.fRoundingMode;
317 }
318 fMicros.rounder = {precision, roundingMode, currency, status};
319
320 // Grouping strategy
321 if (!macros.grouper.isBogus()) {
322 fMicros.grouping = macros.grouper;
323 } else if (macros.notation.fType == Notation::NTN_COMPACT) {
324 // Compact notation uses minGrouping by default since ICU 59
325 fMicros.grouping = Grouper::forStrategy(UNUM_GROUPING_MIN2);
326 } else {
327 fMicros.grouping = Grouper::forStrategy(UNUM_GROUPING_AUTO);
328 }
329 fMicros.grouping.setLocaleData(*fPatternInfo, macros.locale);
330
331 // Padding strategy
332 if (!macros.padder.isBogus()) {
333 fMicros.padding = macros.padder;
334 } else {
335 fMicros.padding = Padder::none();
336 }
337
338 // Integer width
339 if (!macros.integerWidth.isBogus()) {
340 fMicros.integerWidth = macros.integerWidth;
341 } else {
342 fMicros.integerWidth = IntegerWidth::standard();
343 }
344
345 // Sign display
346 if (macros.sign != UNUM_SIGN_COUNT) {
347 fMicros.sign = macros.sign;
348 } else {
349 fMicros.sign = UNUM_SIGN_AUTO;
350 }
351
352 // Decimal mark display
353 if (macros.decimal != UNUM_DECIMAL_SEPARATOR_COUNT) {
354 fMicros.decimal = macros.decimal;
355 } else {
356 fMicros.decimal = UNUM_DECIMAL_SEPARATOR_AUTO;
357 }
358
359 // Use monetary separator symbols
360 fMicros.useCurrency = isCurrency;
361
362 // Inner modifier (scientific notation)
363 if (macros.notation.fType == Notation::NTN_SCIENTIFIC) {
364 auto newScientificHandler = new ScientificHandler(&macros.notation, fMicros.symbols, chain);
365 if (newScientificHandler == nullptr) {
366 status = U_MEMORY_ALLOCATION_ERROR;
367 return nullptr;
368 }
369 fScientificHandler.adoptInstead(newScientificHandler);
370 chain = fScientificHandler.getAlias();
371 } else {
372 // No inner modifier required
373 fMicros.modInner = &fMicros.helpers.emptyStrongModifier;
374 }
375
376 // Middle modifier (patterns, positive/negative, currency symbols, percent)
377 auto patternModifier = new MutablePatternModifier(false);
378 if (patternModifier == nullptr) {
379 status = U_MEMORY_ALLOCATION_ERROR;
380 return nullptr;
381 }
382 fPatternModifier.adoptInstead(patternModifier);
383 patternModifier->setPatternInfo(
384 macros.affixProvider != nullptr ? macros.affixProvider
385 : static_cast<const AffixPatternProvider*>(fPatternInfo.getAlias()),
386 UNUM_FIELD_COUNT);
387 patternModifier->setPatternAttributes(fMicros.sign, isPermille);
388 if (patternModifier->needsPlurals()) {
389 patternModifier->setSymbols(
390 fMicros.symbols,
391 currencySymbols,
392 unitWidth,
393 resolvePluralRules(macros.rules, macros.locale, status));
394 } else {
395 patternModifier->setSymbols(fMicros.symbols, currencySymbols, unitWidth, nullptr);
396 }
397 if (safe) {
398 fImmutablePatternModifier.adoptInstead(patternModifier->createImmutableAndChain(chain, status));
399 chain = fImmutablePatternModifier.getAlias();
400 } else {
401 patternModifier->addToChain(chain);
402 chain = patternModifier;
403 }
404
405 // Outer modifier (CLDR units and currency long names)
406 if (isCldrUnit) {
407 fLongNameHandler.adoptInstead(
408 LongNameHandler::forMeasureUnit(
409 macros.locale,
410 macros.unit,
411 macros.perUnit,
412 unitWidth,
413 resolvePluralRules(macros.rules, macros.locale, status),
414 chain,
415 status));
416 chain = fLongNameHandler.getAlias();
417 } else if (isCurrency && unitWidth == UNUM_UNIT_WIDTH_FULL_NAME) {
418 fLongNameHandler.adoptInstead(
419 LongNameHandler::forCurrencyLongNames(
420 macros.locale,
421 currency,
422 resolvePluralRules(macros.rules, macros.locale, status),
423 chain,
424 status));
425 chain = fLongNameHandler.getAlias();
426 } else {
427 // No outer modifier required
428 fMicros.modOuter = &fMicros.helpers.emptyWeakModifier;
429 }
430
431 // Compact notation
432 // NOTE: Compact notation can (but might not) override the middle modifier and rounding.
433 // It therefore needs to go at the end of the chain.
434 if (macros.notation.fType == Notation::NTN_COMPACT) {
435 CompactType compactType = (isCurrency && unitWidth != UNUM_UNIT_WIDTH_FULL_NAME)
436 ? CompactType::TYPE_CURRENCY : CompactType::TYPE_DECIMAL;
437 auto newCompactHandler = new CompactHandler(
438 macros.notation.fUnion.compactStyle,
439 macros.locale,
440 nsName,
441 compactType,
442 resolvePluralRules(macros.rules, macros.locale, status),
443 safe ? patternModifier : nullptr,
444 chain,
445 status);
446 if (newCompactHandler == nullptr) {
447 status = U_MEMORY_ALLOCATION_ERROR;
448 return nullptr;
449 }
450 fCompactHandler.adoptInstead(newCompactHandler);
451 chain = fCompactHandler.getAlias();
452 }
453
454 return chain;
455}
456
457const PluralRules*
458NumberFormatterImpl::resolvePluralRules(const PluralRules* rulesPtr, const Locale& locale,
459 UErrorCode& status) {
460 if (rulesPtr != nullptr) {
461 return rulesPtr;
462 }
463 // Lazily create PluralRules
464 if (fRules.isNull()) {
465 fRules.adoptInstead(PluralRules::forLocale(locale, status));
466 }
467 return fRules.getAlias();
468}
469
470int32_t NumberFormatterImpl::writeAffixes(const MicroProps& micros, FormattedStringBuilder& string,
471 int32_t start, int32_t end, UErrorCode& status) {
472 // Always apply the inner modifier (which is "strong").
473 int32_t length = micros.modInner->apply(string, start, end, status);
474 if (micros.padding.isValid()) {
475 length += micros.padding
476 .padAndApply(*micros.modMiddle, *micros.modOuter, string, start, length + end, status);
477 } else {
478 length += micros.modMiddle->apply(string, start, length + end, status);
479 length += micros.modOuter->apply(string, start, length + end, status);
480 }
481 return length;
482}
483
484int32_t NumberFormatterImpl::writeNumber(const MicroProps& micros, DecimalQuantity& quantity,
485 FormattedStringBuilder& string, int32_t index,
486 UErrorCode& status) {
487 int32_t length = 0;
488 if (quantity.isInfinite()) {
489 length += string.insert(
490 length + index,
491 micros.symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kInfinitySymbol),
492 UNUM_INTEGER_FIELD,
493 status);
494
495 } else if (quantity.isNaN()) {
496 length += string.insert(
497 length + index,
498 micros.symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kNaNSymbol),
499 UNUM_INTEGER_FIELD,
500 status);
501
502 } else {
503 // Add the integer digits
504 length += writeIntegerDigits(micros, quantity, string, length + index, status);
505
506 // Add the decimal point
507 if (quantity.getLowerDisplayMagnitude() < 0 || micros.decimal == UNUM_DECIMAL_SEPARATOR_ALWAYS) {
508 length += string.insert(
509 length + index,
510 micros.useCurrency ? micros.symbols->getSymbol(
511 DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol) : micros
512 .symbols
513 ->getSymbol(
514 DecimalFormatSymbols::ENumberFormatSymbol::kDecimalSeparatorSymbol),
515 UNUM_DECIMAL_SEPARATOR_FIELD,
516 status);
517 }
518
519 // Add the fraction digits
520 length += writeFractionDigits(micros, quantity, string, length + index, status);
521
522 if (length == 0) {
523 // Force output of the digit for value 0
524 length += utils::insertDigitFromSymbols(
525 string, index, 0, *micros.symbols, UNUM_INTEGER_FIELD, status);
526 }
527 }
528
529 return length;
530}
531
532int32_t NumberFormatterImpl::writeIntegerDigits(const MicroProps& micros, DecimalQuantity& quantity,
533 FormattedStringBuilder& string, int32_t index,
534 UErrorCode& status) {
535 int length = 0;
536 int integerCount = quantity.getUpperDisplayMagnitude() + 1;
537 for (int i = 0; i < integerCount; i++) {
538 // Add grouping separator
539 if (micros.grouping.groupAtPosition(i, quantity)) {
540 length += string.insert(
541 index,
542 micros.useCurrency ? micros.symbols->getSymbol(
543 DecimalFormatSymbols::ENumberFormatSymbol::kMonetaryGroupingSeparatorSymbol)
544 : micros.symbols->getSymbol(
545 DecimalFormatSymbols::ENumberFormatSymbol::kGroupingSeparatorSymbol),
546 UNUM_GROUPING_SEPARATOR_FIELD,
547 status);
548 }
549
550 // Get and append the next digit value
551 int8_t nextDigit = quantity.getDigit(i);
552 length += utils::insertDigitFromSymbols(
553 string, index, nextDigit, *micros.symbols, UNUM_INTEGER_FIELD, status);
554 }
555 return length;
556}
557
558int32_t NumberFormatterImpl::writeFractionDigits(const MicroProps& micros, DecimalQuantity& quantity,
559 FormattedStringBuilder& string, int32_t index,
560 UErrorCode& status) {
561 int length = 0;
562 int fractionCount = -quantity.getLowerDisplayMagnitude();
563 for (int i = 0; i < fractionCount; i++) {
564 // Get and append the next digit value
565 int8_t nextDigit = quantity.getDigit(-i - 1);
566 length += utils::insertDigitFromSymbols(
567 string, length + index, nextDigit, *micros.symbols, UNUM_FRACTION_FIELD, status);
568 }
569 return length;
570}
571
572#endif /* #if !UCONFIG_NO_FORMATTING */