tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

number_formatimpl.cpp (25913B)


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