diff options
Diffstat (limited to 'intl/icu/source/i18n/number_formatimpl.cpp')
-rw-r--r-- | intl/icu/source/i18n/number_formatimpl.cpp | 647 |
1 files changed, 647 insertions, 0 deletions
diff --git a/intl/icu/source/i18n/number_formatimpl.cpp b/intl/icu/source/i18n/number_formatimpl.cpp new file mode 100644 index 0000000000..53bac49a55 --- /dev/null +++ b/intl/icu/source/i18n/number_formatimpl.cpp @@ -0,0 +1,647 @@ +// © 2017 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +#include "cstring.h" +#include "unicode/ures.h" +#include "uresimp.h" +#include "charstr.h" +#include "number_formatimpl.h" +#include "unicode/numfmt.h" +#include "number_patternstring.h" +#include "number_utils.h" +#include "unicode/numberformatter.h" +#include "unicode/dcfmtsym.h" +#include "number_scientific.h" +#include "number_compact.h" +#include "uresimp.h" +#include "ureslocs.h" + +using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; + + +NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, UErrorCode& status) + : NumberFormatterImpl(macros, true, status) { +} + +int32_t NumberFormatterImpl::formatStatic(const MacroProps ¯os, UFormattedNumberData *results, + UErrorCode &status) { + DecimalQuantity &inValue = results->quantity; + FormattedStringBuilder &outString = results->getStringRef(); + NumberFormatterImpl impl(macros, false, status); + MicroProps& micros = impl.preProcessUnsafe(inValue, status); + if (U_FAILURE(status)) { return 0; } + int32_t length = writeNumber(micros.simple, inValue, outString, 0, status); + length += writeAffixes(micros, outString, 0, length, status); + results->outputUnit = std::move(micros.outputUnit); + results->gender = micros.gender; + return length; +} + +int32_t NumberFormatterImpl::getPrefixSuffixStatic(const MacroProps& macros, Signum signum, + StandardPlural::Form plural, + FormattedStringBuilder& outString, UErrorCode& status) { + NumberFormatterImpl impl(macros, false, status); + return impl.getPrefixSuffixUnsafe(signum, plural, outString, status); +} + +// NOTE: C++ SPECIFIC DIFFERENCE FROM JAVA: +// The "safe" apply method uses a new MicroProps. In the MicroPropsGenerator, fMicros is copied into the new instance. +// The "unsafe" method simply re-uses fMicros, eliminating the extra copy operation. +// See MicroProps::processQuantity() for details. + +int32_t NumberFormatterImpl::format(UFormattedNumberData *results, UErrorCode &status) const { + DecimalQuantity &inValue = results->quantity; + FormattedStringBuilder &outString = results->getStringRef(); + MicroProps micros; + preProcess(inValue, micros, status); + if (U_FAILURE(status)) { return 0; } + int32_t length = writeNumber(micros.simple, inValue, outString, 0, status); + length += writeAffixes(micros, outString, 0, length, status); + results->outputUnit = std::move(micros.outputUnit); + results->gender = micros.gender; + return length; +} + +void NumberFormatterImpl::preProcess(DecimalQuantity& inValue, MicroProps& microsOut, + UErrorCode& status) const { + if (U_FAILURE(status)) { return; } + if (fMicroPropsGenerator == nullptr) { + status = U_INTERNAL_PROGRAM_ERROR; + return; + } + fMicroPropsGenerator->processQuantity(inValue, microsOut, status); + microsOut.integerWidth.apply(inValue, status); +} + +MicroProps& NumberFormatterImpl::preProcessUnsafe(DecimalQuantity& inValue, UErrorCode& status) { + if (U_FAILURE(status)) { + return fMicros; // must always return a value + } + if (fMicroPropsGenerator == nullptr) { + status = U_INTERNAL_PROGRAM_ERROR; + return fMicros; // must always return a value + } + fMicroPropsGenerator->processQuantity(inValue, fMicros, status); + fMicros.integerWidth.apply(inValue, status); + return fMicros; +} + +int32_t NumberFormatterImpl::getPrefixSuffix(Signum signum, StandardPlural::Form plural, + FormattedStringBuilder& outString, UErrorCode& status) const { + if (U_FAILURE(status)) { return 0; } + // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle, aka pattern modifier). + // Safe path: use fImmutablePatternModifier. + const Modifier* modifier = fImmutablePatternModifier->getModifier(signum, plural); + modifier->apply(outString, 0, 0, status); + if (U_FAILURE(status)) { return 0; } + return modifier->getPrefixLength(); +} + +int32_t NumberFormatterImpl::getPrefixSuffixUnsafe(Signum signum, StandardPlural::Form plural, + FormattedStringBuilder& outString, UErrorCode& status) { + if (U_FAILURE(status)) { return 0; } + // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle, aka pattern modifier). + // Unsafe path: use fPatternModifier. + fPatternModifier->setNumberProperties(signum, plural); + fPatternModifier->apply(outString, 0, 0, status); + if (U_FAILURE(status)) { return 0; } + return fPatternModifier->getPrefixLength(); +} + +NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, bool safe, UErrorCode& status) { + fMicroPropsGenerator = macrosToMicroGenerator(macros, safe, status); +} + +////////// + +const MicroPropsGenerator* +NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, UErrorCode& status) { + if (U_FAILURE(status)) { return nullptr; } + const MicroPropsGenerator* chain = &fMicros; + + // Check that macros is error-free before continuing. + if (macros.copyErrorTo(status)) { + return nullptr; + } + + // TODO: Accept currency symbols from DecimalFormatSymbols? + + // Pre-compute a few values for efficiency. + bool isCurrency = utils::unitIsCurrency(macros.unit); + bool isBaseUnit = utils::unitIsBaseUnit(macros.unit); + bool isPercent = utils::unitIsPercent(macros.unit); + bool isPermille = utils::unitIsPermille(macros.unit); + bool isCompactNotation = macros.notation.fType == Notation::NTN_COMPACT; + bool isAccounting = + macros.sign == UNUM_SIGN_ACCOUNTING || + macros.sign == UNUM_SIGN_ACCOUNTING_ALWAYS || + macros.sign == UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO || + macros.sign == UNUM_SIGN_ACCOUNTING_NEGATIVE; + CurrencyUnit currency(u"", status); + if (isCurrency) { + currency = CurrencyUnit(macros.unit, status); // Restore CurrencyUnit from MeasureUnit + } + UNumberUnitWidth unitWidth = UNUM_UNIT_WIDTH_SHORT; + if (macros.unitWidth != UNUM_UNIT_WIDTH_COUNT) { + unitWidth = macros.unitWidth; + } + // Use CLDR unit data for all MeasureUnits (not currency and not + // no-unit), except use the dedicated percent pattern for percent and + // permille. However, use the CLDR unit data for percent/permille if a + // long name was requested OR if compact notation is being used, since + // compact notation overrides the middle modifier (micros.modMiddle) + // normally used for the percent pattern. + bool isCldrUnit = !isCurrency + && !isBaseUnit + && (unitWidth == UNUM_UNIT_WIDTH_FULL_NAME + || !(isPercent || isPermille) + || isCompactNotation + ); + bool isMixedUnit = isCldrUnit && (uprv_strcmp(macros.unit.getType(), "") == 0) && + macros.unit.getComplexity(status) == UMEASURE_UNIT_MIXED; + + // Select the numbering system. + LocalPointer<const NumberingSystem> nsLocal; + const NumberingSystem* ns; + if (macros.symbols.isNumberingSystem()) { + ns = macros.symbols.getNumberingSystem(); + } else { + // TODO: Is there a way to avoid creating the NumberingSystem object? + ns = NumberingSystem::createInstance(macros.locale, status); + // Give ownership to the function scope. + nsLocal.adoptInstead(ns); + } + const char* nsName = U_SUCCESS(status) ? ns->getName() : "latn"; + uprv_strncpy(fMicros.nsName, nsName, 8); + fMicros.nsName[8] = 0; // guarantee NUL-terminated + + // Default gender: none. + fMicros.gender = ""; + + // Resolve the symbols. Do this here because currency may need to customize them. + if (macros.symbols.isDecimalFormatSymbols()) { + fMicros.simple.symbols = macros.symbols.getDecimalFormatSymbols(); + } else { + LocalPointer<DecimalFormatSymbols> newSymbols( + new DecimalFormatSymbols(macros.locale, *ns, status), status); + if (U_FAILURE(status)) { + return nullptr; + } + if (isCurrency) { + newSymbols->setCurrency(currency.getISOCurrency(), status); + if (U_FAILURE(status)) { + return nullptr; + } + } + fMicros.simple.symbols = newSymbols.getAlias(); + fSymbols.adoptInstead(newSymbols.orphan()); + } + + // Load and parse the pattern string. It is used for grouping sizes and affixes only. + // If we are formatting currency, check for a currency-specific pattern. + const char16_t* pattern = nullptr; + if (isCurrency && fMicros.simple.symbols->getCurrencyPattern() != nullptr) { + pattern = fMicros.simple.symbols->getCurrencyPattern(); + } + if (pattern == nullptr) { + CldrPatternStyle patternStyle; + if (isCldrUnit) { + patternStyle = CLDR_PATTERN_STYLE_DECIMAL; + } else if (isPercent || isPermille) { + patternStyle = CLDR_PATTERN_STYLE_PERCENT; + } else if (!isCurrency || unitWidth == UNUM_UNIT_WIDTH_FULL_NAME) { + patternStyle = CLDR_PATTERN_STYLE_DECIMAL; + } else if (isAccounting) { + // NOTE: Although ACCOUNTING and ACCOUNTING_ALWAYS are only supported in currencies right now, + // the API contract allows us to add support to other units in the future. + patternStyle = CLDR_PATTERN_STYLE_ACCOUNTING; + } else { + patternStyle = CLDR_PATTERN_STYLE_CURRENCY; + } + pattern = utils::getPatternForStyle(macros.locale, nsName, patternStyle, status); + if (U_FAILURE(status)) { + return nullptr; + } + } + auto patternInfo = new ParsedPatternInfo(); + if (patternInfo == nullptr) { + status = U_MEMORY_ALLOCATION_ERROR; + return nullptr; + } + fPatternInfo.adoptInstead(patternInfo); + PatternParser::parseToPatternInfo(UnicodeString(pattern), *patternInfo, status); + if (U_FAILURE(status)) { + return nullptr; + } + + ///////////////////////////////////////////////////////////////////////////////////// + /// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR /// + ///////////////////////////////////////////////////////////////////////////////////// + + // Unit Preferences and Conversions as our first step + if (macros.usage.isSet()) { + if (!isCldrUnit) { + // We only support "usage" when the input unit is specified, and is + // a CLDR Unit. + status = U_ILLEGAL_ARGUMENT_ERROR; + return nullptr; + } + auto usagePrefsHandler = + new UsagePrefsHandler(macros.locale, macros.unit, macros.usage.fValue, chain, status); + fUsagePrefsHandler.adoptInsteadAndCheckErrorCode(usagePrefsHandler, status); + chain = fUsagePrefsHandler.getAlias(); + } else if (isMixedUnit) { + auto unitConversionHandler = new UnitConversionHandler(macros.unit, chain, status); + fUnitConversionHandler.adoptInsteadAndCheckErrorCode(unitConversionHandler, status); + chain = fUnitConversionHandler.getAlias(); + } + + // Multiplier + if (macros.scale.isValid()) { + fMicros.helpers.multiplier.setAndChain(macros.scale, chain); + chain = &fMicros.helpers.multiplier; + } + + // Rounding strategy + Precision precision; + if (!macros.precision.isBogus()) { + precision = macros.precision; + } else if (isCompactNotation) { + precision = Precision::integer().withMinDigits(2); + } else if (isCurrency) { + precision = Precision::currency(UCURR_USAGE_STANDARD); + } else if (macros.usage.isSet()) { + // Bogus Precision - it will get set in the UsagePrefsHandler instead + precision = Precision(); + } else { + precision = Precision::maxFraction(6); + } + UNumberFormatRoundingMode roundingMode; + roundingMode = macros.roundingMode; + fMicros.rounder = {precision, roundingMode, currency, status}; + if (U_FAILURE(status)) { + return nullptr; + } + + // Grouping strategy + if (!macros.grouper.isBogus()) { + fMicros.simple.grouping = macros.grouper; + } else if (isCompactNotation) { + // Compact notation uses minGrouping by default since ICU 59 + fMicros.simple.grouping = Grouper::forStrategy(UNUM_GROUPING_MIN2); + } else { + fMicros.simple.grouping = Grouper::forStrategy(UNUM_GROUPING_AUTO); + } + fMicros.simple.grouping.setLocaleData(*fPatternInfo, macros.locale); + + // Padding strategy + if (!macros.padder.isBogus()) { + fMicros.padding = macros.padder; + } else { + fMicros.padding = Padder::none(); + } + + // Integer width + if (!macros.integerWidth.isBogus()) { + fMicros.integerWidth = macros.integerWidth; + } else { + fMicros.integerWidth = IntegerWidth::standard(); + } + + // Sign display + if (macros.sign != UNUM_SIGN_COUNT) { + fMicros.sign = macros.sign; + } else { + fMicros.sign = UNUM_SIGN_AUTO; + } + + // Decimal mark display + if (macros.decimal != UNUM_DECIMAL_SEPARATOR_COUNT) { + fMicros.simple.decimal = macros.decimal; + } else { + fMicros.simple.decimal = UNUM_DECIMAL_SEPARATOR_AUTO; + } + + // Use monetary separator symbols + fMicros.simple.useCurrency = isCurrency; + + // Inner modifier (scientific notation) + if (macros.notation.fType == Notation::NTN_SCIENTIFIC) { + auto newScientificHandler = new ScientificHandler(¯os.notation, fMicros.simple.symbols, chain); + if (newScientificHandler == nullptr) { + status = U_MEMORY_ALLOCATION_ERROR; + return nullptr; + } + fScientificHandler.adoptInstead(newScientificHandler); + chain = fScientificHandler.getAlias(); + } else { + // No inner modifier required + fMicros.modInner = &fMicros.helpers.emptyStrongModifier; + } + + // Middle modifier (patterns, positive/negative, currency symbols, percent) + auto patternModifier = new MutablePatternModifier(false); + if (patternModifier == nullptr) { + status = U_MEMORY_ALLOCATION_ERROR; + return nullptr; + } + fPatternModifier.adoptInstead(patternModifier); + const AffixPatternProvider* affixProvider = + macros.affixProvider != nullptr && ( + // For more information on this condition, see ICU-22073 + !isCompactNotation || isCurrency == macros.affixProvider->hasCurrencySign()) + ? macros.affixProvider + : static_cast<const AffixPatternProvider*>(fPatternInfo.getAlias()); + patternModifier->setPatternInfo(affixProvider, kUndefinedField); + patternModifier->setPatternAttributes(fMicros.sign, isPermille, macros.approximately); + if (patternModifier->needsPlurals()) { + patternModifier->setSymbols( + fMicros.simple.symbols, + currency, + unitWidth, + resolvePluralRules(macros.rules, macros.locale, status), + status); + } else { + patternModifier->setSymbols(fMicros.simple.symbols, currency, unitWidth, nullptr, status); + } + if (safe) { + fImmutablePatternModifier.adoptInsteadAndCheckErrorCode(patternModifier->createImmutable(status), + status); + } + if (U_FAILURE(status)) { + return nullptr; + } + + // currencyAsDecimal + if (affixProvider->currencyAsDecimal()) { + fMicros.simple.currencyAsDecimal = patternModifier->getCurrencySymbolForUnitWidth(status); + } + + // Outer modifier (CLDR units and currency long names) + if (isCldrUnit) { + const char *unitDisplayCase = ""; + if (macros.unitDisplayCase.isSet()) { + unitDisplayCase = macros.unitDisplayCase.fValue; + } + if (macros.usage.isSet()) { + fLongNameMultiplexer.adoptInsteadAndCheckErrorCode( + LongNameMultiplexer::forMeasureUnits( + macros.locale, *fUsagePrefsHandler->getOutputUnits(), unitWidth, unitDisplayCase, + resolvePluralRules(macros.rules, macros.locale, status), chain, status), + status); + chain = fLongNameMultiplexer.getAlias(); + } else if (isMixedUnit) { + fMixedUnitLongNameHandler.adoptInsteadAndCheckErrorCode(new MixedUnitLongNameHandler(), + status); + MixedUnitLongNameHandler::forMeasureUnit( + macros.locale, macros.unit, unitWidth, unitDisplayCase, + resolvePluralRules(macros.rules, macros.locale, status), chain, + fMixedUnitLongNameHandler.getAlias(), status); + chain = fMixedUnitLongNameHandler.getAlias(); + } else { + MeasureUnit unit = macros.unit; + if (!utils::unitIsBaseUnit(macros.perUnit)) { + unit = unit.product(macros.perUnit.reciprocal(status), status); + // This isn't strictly necessary, but was what we specced out + // when perUnit became a backward-compatibility thing: + // unit/perUnit use case is only valid if both units are + // built-ins, or the product is a built-in. + if (uprv_strcmp(unit.getType(), "") == 0 && + (uprv_strcmp(macros.unit.getType(), "") == 0 || + uprv_strcmp(macros.perUnit.getType(), "") == 0)) { + status = U_UNSUPPORTED_ERROR; + return nullptr; + } + } + fLongNameHandler.adoptInsteadAndCheckErrorCode(new LongNameHandler(), status); + LongNameHandler::forMeasureUnit(macros.locale, unit, unitWidth, unitDisplayCase, + resolvePluralRules(macros.rules, macros.locale, status), + chain, fLongNameHandler.getAlias(), status); + chain = fLongNameHandler.getAlias(); + } + } else if (isCurrency && unitWidth == UNUM_UNIT_WIDTH_FULL_NAME) { + fLongNameHandler.adoptInsteadAndCheckErrorCode( + LongNameHandler::forCurrencyLongNames( + macros.locale, currency, resolvePluralRules(macros.rules, macros.locale, status), chain, + status), + status); + chain = fLongNameHandler.getAlias(); + } else { + // No outer modifier required + fMicros.modOuter = &fMicros.helpers.emptyWeakModifier; + } + if (U_FAILURE(status)) { + return nullptr; + } + + // Compact notation + if (isCompactNotation) { + CompactType compactType = (isCurrency && unitWidth != UNUM_UNIT_WIDTH_FULL_NAME) + ? CompactType::TYPE_CURRENCY : CompactType::TYPE_DECIMAL; + auto newCompactHandler = new CompactHandler( + macros.notation.fUnion.compactStyle, + macros.locale, + nsName, + compactType, + resolvePluralRules(macros.rules, macros.locale, status), + patternModifier, + safe, + chain, + status); + if (U_FAILURE(status)) { + return nullptr; + } + if (newCompactHandler == nullptr) { + status = U_MEMORY_ALLOCATION_ERROR; + return nullptr; + } + fCompactHandler.adoptInstead(newCompactHandler); + chain = fCompactHandler.getAlias(); + } + if (U_FAILURE(status)) { + return nullptr; + } + + // Always add the pattern modifier as the last element of the chain. + if (safe) { + fImmutablePatternModifier->addToChain(chain); + chain = fImmutablePatternModifier.getAlias(); + } else { + patternModifier->addToChain(chain); + chain = patternModifier; + } + + return chain; +} + +const PluralRules* +NumberFormatterImpl::resolvePluralRules( + const PluralRules* rulesPtr, + const Locale& locale, + UErrorCode& status) { + if (rulesPtr != nullptr) { + return rulesPtr; + } + // Lazily create PluralRules + if (fRules.isNull()) { + fRules.adoptInstead(PluralRules::forLocale(locale, status)); + } + return fRules.getAlias(); +} + +int32_t NumberFormatterImpl::writeAffixes( + const MicroProps& micros, + FormattedStringBuilder& string, + int32_t start, + int32_t end, + UErrorCode& status) { + U_ASSERT(micros.modOuter != nullptr); + // Always apply the inner modifier (which is "strong"). + int32_t length = micros.modInner->apply(string, start, end, status); + if (micros.padding.isValid()) { + length += micros.padding + .padAndApply(*micros.modMiddle, *micros.modOuter, string, start, length + end, status); + } else { + length += micros.modMiddle->apply(string, start, length + end, status); + length += micros.modOuter->apply(string, start, length + end, status); + } + return length; +} + +int32_t NumberFormatterImpl::writeNumber( + const SimpleMicroProps& micros, + DecimalQuantity& quantity, + FormattedStringBuilder& string, + int32_t index, + UErrorCode& status) { + int32_t length = 0; + if (quantity.isInfinite()) { + length += string.insert( + length + index, + micros.symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kInfinitySymbol), + {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD}, + status); + + } else if (quantity.isNaN()) { + length += string.insert( + length + index, + micros.symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kNaNSymbol), + {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD}, + status); + + } else { + // Add the integer digits + length += writeIntegerDigits( + micros, + quantity, + string, + length + index, + status); + + // Add the decimal point + if (quantity.getLowerDisplayMagnitude() < 0 || micros.decimal == UNUM_DECIMAL_SEPARATOR_ALWAYS) { + if (!micros.currencyAsDecimal.isBogus()) { + length += string.insert( + length + index, + micros.currencyAsDecimal, + {UFIELD_CATEGORY_NUMBER, UNUM_CURRENCY_FIELD}, + status); + } else if (micros.useCurrency) { + length += string.insert( + length + index, + micros.symbols->getSymbol( + DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol), + {UFIELD_CATEGORY_NUMBER, UNUM_DECIMAL_SEPARATOR_FIELD}, + status); + } else { + length += string.insert( + length + index, + micros.symbols->getSymbol( + DecimalFormatSymbols::ENumberFormatSymbol::kDecimalSeparatorSymbol), + {UFIELD_CATEGORY_NUMBER, UNUM_DECIMAL_SEPARATOR_FIELD}, + status); + } + } + + // Add the fraction digits + length += writeFractionDigits(micros, quantity, string, length + index, status); + + if (length == 0) { + // Force output of the digit for value 0 + length += utils::insertDigitFromSymbols( + string, + index, + 0, + *micros.symbols, + {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD}, + status); + } + } + + return length; +} + +int32_t NumberFormatterImpl::writeIntegerDigits( + const SimpleMicroProps& micros, + DecimalQuantity& quantity, + FormattedStringBuilder& string, + int32_t index, + UErrorCode& status) { + int length = 0; + int integerCount = quantity.getUpperDisplayMagnitude() + 1; + for (int i = 0; i < integerCount; i++) { + // Add grouping separator + if (micros.grouping.groupAtPosition(i, quantity)) { + length += string.insert( + index, + micros.useCurrency ? micros.symbols->getSymbol( + DecimalFormatSymbols::ENumberFormatSymbol::kMonetaryGroupingSeparatorSymbol) + : micros.symbols->getSymbol( + DecimalFormatSymbols::ENumberFormatSymbol::kGroupingSeparatorSymbol), + {UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD}, + status); + } + + // Get and append the next digit value + int8_t nextDigit = quantity.getDigit(i); + length += utils::insertDigitFromSymbols( + string, + index, + nextDigit, + *micros.symbols, + {UFIELD_CATEGORY_NUMBER, + UNUM_INTEGER_FIELD}, + status); + } + return length; +} + +int32_t NumberFormatterImpl::writeFractionDigits( + const SimpleMicroProps& micros, + DecimalQuantity& quantity, + FormattedStringBuilder& string, + int32_t index, + UErrorCode& status) { + int length = 0; + int fractionCount = -quantity.getLowerDisplayMagnitude(); + for (int i = 0; i < fractionCount; i++) { + // Get and append the next digit value + int8_t nextDigit = quantity.getDigit(-i - 1); + length += utils::insertDigitFromSymbols( + string, + length + index, + nextDigit, + *micros.symbols, + {UFIELD_CATEGORY_NUMBER, UNUM_FRACTION_FIELD}, + status); + } + return length; +} + +#endif /* #if !UCONFIG_NO_FORMATTING */ |