tor-browser

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

number_mapper.cpp (21656B)


      1 // © 2018 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 // Allow implicit conversion from char16_t* to UnicodeString for this file:
      9 // Helpful in toString methods and elsewhere.
     10 #define UNISTR_FROM_STRING_EXPLICIT
     11 
     12 #include "number_mapper.h"
     13 #include "number_patternstring.h"
     14 #include "unicode/errorcode.h"
     15 #include "number_utils.h"
     16 
     17 using namespace icu;
     18 using namespace icu::number;
     19 using namespace icu::number::impl;
     20 
     21 
     22 UnlocalizedNumberFormatter NumberPropertyMapper::create(const DecimalFormatProperties& properties,
     23                                                        const DecimalFormatSymbols& symbols,
     24                                                        DecimalFormatWarehouse& warehouse,
     25                                                        UErrorCode& status) {
     26    return NumberFormatter::with().macros(oldToNew(properties, symbols, warehouse, nullptr, status));
     27 }
     28 
     29 UnlocalizedNumberFormatter NumberPropertyMapper::create(const DecimalFormatProperties& properties,
     30                                                        const DecimalFormatSymbols& symbols,
     31                                                        DecimalFormatWarehouse& warehouse,
     32                                                        DecimalFormatProperties& exportedProperties,
     33                                                        UErrorCode& status) {
     34    return NumberFormatter::with().macros(
     35            oldToNew(
     36                    properties, symbols, warehouse, &exportedProperties, status));
     37 }
     38 
     39 MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& properties,
     40                                          const DecimalFormatSymbols& symbols,
     41                                          DecimalFormatWarehouse& warehouse,
     42                                          DecimalFormatProperties* exportedProperties,
     43                                          UErrorCode& status) {
     44    MacroProps macros;
     45    Locale locale = symbols.getLocale();
     46 
     47    /////////////
     48    // SYMBOLS //
     49    /////////////
     50 
     51    macros.symbols.setTo(symbols);
     52 
     53    //////////////////
     54    // PLURAL RULES //
     55    //////////////////
     56 
     57    if (!properties.currencyPluralInfo.fPtr.isNull()) {
     58        macros.rules = properties.currencyPluralInfo.fPtr->getPluralRules();
     59    }
     60 
     61    /////////////
     62    // AFFIXES //
     63    /////////////
     64 
     65    warehouse.affixProvider.setTo(properties, status);
     66    macros.affixProvider = &warehouse.affixProvider.get();
     67 
     68    ///////////
     69    // UNITS //
     70    ///////////
     71 
     72    bool useCurrency = (
     73            !properties.currency.isNull() ||
     74            !properties.currencyPluralInfo.fPtr.isNull() ||
     75            !properties.currencyUsage.isNull() ||
     76            warehouse.affixProvider.get().hasCurrencySign());
     77    CurrencyUnit currency;
     78    UCurrencyUsage currencyUsage;
     79    if (useCurrency) {
     80        currency = resolveCurrency(properties, locale, status);
     81        currencyUsage = properties.currencyUsage.getOrDefault(UCURR_USAGE_STANDARD);
     82        // NOTE: Slicing is OK.
     83        macros.unit = currency; // NOLINT
     84    }
     85 
     86    ///////////////////////
     87    // ROUNDING STRATEGY //
     88    ///////////////////////
     89 
     90    int32_t maxInt = properties.maximumIntegerDigits;
     91    int32_t minInt = properties.minimumIntegerDigits;
     92    int32_t maxFrac = properties.maximumFractionDigits;
     93    int32_t minFrac = properties.minimumFractionDigits;
     94    int32_t minSig = properties.minimumSignificantDigits;
     95    int32_t maxSig = properties.maximumSignificantDigits;
     96    double roundingIncrement = properties.roundingIncrement;
     97    // Not assigning directly to macros.roundingMode here: we change
     98    // roundingMode if and when we also change macros.precision.
     99    RoundingMode roundingMode = properties.roundingMode.getOrDefault(UNUM_ROUND_HALFEVEN);
    100    bool explicitMinMaxFrac = minFrac != -1 || maxFrac != -1;
    101    bool explicitMinMaxSig = minSig != -1 || maxSig != -1;
    102    // Resolve min/max frac for currencies, required for the validation logic and for when minFrac or
    103    // maxFrac was
    104    // set (but not both) on a currency instance.
    105    // NOTE: Increments are handled in "Precision.constructCurrency()".
    106    if (useCurrency && (minFrac == -1 || maxFrac == -1)) {
    107        int32_t digits = ucurr_getDefaultFractionDigitsForUsage(
    108                currency.getISOCurrency(), currencyUsage, &status);
    109        if (minFrac == -1 && maxFrac == -1) {
    110            minFrac = digits;
    111            maxFrac = digits;
    112        } else if (minFrac == -1) {
    113            minFrac = std::min(maxFrac, digits);
    114        } else /* if (maxFrac == -1) */ {
    115            maxFrac = std::max(minFrac, digits);
    116        }
    117    }
    118    // Validate min/max int/frac.
    119    // For backwards compatibility, minimum overrides maximum if the two conflict.
    120    if (minInt == 0 && maxFrac != 0) {
    121        minFrac = (minFrac < 0 || (minFrac == 0 && maxInt == 0)) ? 1 : minFrac;
    122        maxFrac = maxFrac < 0 ? -1 : maxFrac < minFrac ? minFrac : maxFrac;
    123        minInt = 0;
    124        maxInt = maxInt < 0 ? -1 : maxInt > kMaxIntFracSig ? -1 : maxInt;
    125    } else {
    126        // Force a digit before the decimal point.
    127        minFrac = minFrac < 0 ? 0 : minFrac;
    128        maxFrac = maxFrac < 0 ? -1 : maxFrac < minFrac ? minFrac : maxFrac;
    129        minInt = minInt <= 0 ? 1 : minInt > kMaxIntFracSig ? 1 : minInt;
    130        maxInt = maxInt < 0 ? -1 : maxInt < minInt ? minInt : maxInt > kMaxIntFracSig ? -1 : maxInt;
    131    }
    132    Precision precision;
    133    if (!properties.currencyUsage.isNull()) {
    134        U_ASSERT(useCurrency);
    135        precision = Precision::constructCurrency(currencyUsage).withCurrency(currency);
    136    } else if (roundingIncrement != 0.0) {
    137        if (PatternStringUtils::ignoreRoundingIncrement(roundingIncrement, maxFrac)) {
    138            precision = Precision::constructFraction(minFrac, maxFrac);
    139        } else {
    140            // Convert the double increment to an integer increment
    141            precision = Precision::increment(roundingIncrement).withMinFraction(minFrac);
    142        }
    143    } else if (explicitMinMaxSig) {
    144        minSig = minSig < 1 ? 1 : minSig > kMaxIntFracSig ? kMaxIntFracSig : minSig;
    145        maxSig = maxSig < 0 ? kMaxIntFracSig : maxSig < minSig ? minSig : maxSig > kMaxIntFracSig
    146                                                                          ? kMaxIntFracSig : maxSig;
    147        precision = Precision::constructSignificant(minSig, maxSig);
    148    } else if (explicitMinMaxFrac) {
    149        precision = Precision::constructFraction(minFrac, maxFrac);
    150    } else if (useCurrency) {
    151        precision = Precision::constructCurrency(currencyUsage);
    152    }
    153    if (!precision.isBogus()) {
    154        macros.roundingMode = roundingMode;
    155        macros.precision = precision;
    156    }
    157 
    158    ///////////////////
    159    // INTEGER WIDTH //
    160    ///////////////////
    161 
    162    macros.integerWidth = IntegerWidth(
    163            static_cast<digits_t>(minInt),
    164            static_cast<digits_t>(maxInt),
    165            properties.formatFailIfMoreThanMaxDigits);
    166 
    167    ///////////////////////
    168    // GROUPING STRATEGY //
    169    ///////////////////////
    170 
    171    macros.grouper = Grouper::forProperties(properties);
    172 
    173    /////////////
    174    // PADDING //
    175    /////////////
    176 
    177    if (properties.formatWidth > 0) {
    178        macros.padder = Padder::forProperties(properties);
    179    }
    180 
    181    ///////////////////////////////
    182    // DECIMAL MARK ALWAYS SHOWN //
    183    ///////////////////////////////
    184 
    185    macros.decimal = properties.decimalSeparatorAlwaysShown ? UNUM_DECIMAL_SEPARATOR_ALWAYS
    186                                                            : UNUM_DECIMAL_SEPARATOR_AUTO;
    187 
    188    ///////////////////////
    189    // SIGN ALWAYS SHOWN //
    190    ///////////////////////
    191 
    192    macros.sign = properties.signAlwaysShown ? UNUM_SIGN_ALWAYS : UNUM_SIGN_AUTO;
    193 
    194    /////////////////////////
    195    // SCIENTIFIC NOTATION //
    196    /////////////////////////
    197 
    198    if (properties.minimumExponentDigits != -1) {
    199        // Scientific notation is required.
    200        // This whole section feels like a hack, but it is needed for regression tests.
    201        // The mapping from property bag to scientific notation is nontrivial due to LDML rules.
    202        if (maxInt > 8) {
    203            // But #13110: The maximum of 8 digits has unknown origins and is not in the spec.
    204            // If maxInt is greater than 8, it is set to minInt, even if minInt is greater than 8.
    205            maxInt = minInt;
    206            macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt);
    207        } else if (maxInt > minInt && minInt > 1) {
    208            // Bug #13289: if maxInt > minInt > 1, then minInt should be 1.
    209            minInt = 1;
    210            macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt);
    211        }
    212        int engineering = maxInt < 0 ? -1 : maxInt;
    213        macros.notation = ScientificNotation(
    214                // Engineering interval:
    215                static_cast<int8_t>(engineering),
    216                // Enforce minimum integer digits (for patterns like "000.00E0"):
    217                (engineering == minInt),
    218                // Minimum exponent digits:
    219                static_cast<digits_t>(properties.minimumExponentDigits),
    220                // Exponent sign always shown:
    221                properties.exponentSignAlwaysShown ? UNUM_SIGN_ALWAYS : UNUM_SIGN_AUTO);
    222        // Scientific notation also involves overriding the rounding mode.
    223        // TODO: Overriding here is a bit of a hack. Should this logic go earlier?
    224        if (macros.precision.fType == Precision::PrecisionType::RND_FRACTION) {
    225            // For the purposes of rounding, get the original min/max int/frac, since the local
    226            // variables have been manipulated for display purposes.
    227            int maxInt_ = properties.maximumIntegerDigits;
    228            int minInt_ = properties.minimumIntegerDigits;
    229            int minFrac_ = properties.minimumFractionDigits;
    230            int maxFrac_ = properties.maximumFractionDigits;
    231            if (minInt_ == 0 && maxFrac_ == 0) {
    232                // Patterns like "#E0" and "##E0", which mean no rounding!
    233                macros.precision = Precision::unlimited();
    234            } else if (minInt_ == 0 && minFrac_ == 0) {
    235                // Patterns like "#.##E0" (no zeros in the mantissa), which mean round to maxFrac+1
    236                macros.precision = Precision::constructSignificant(1, maxFrac_ + 1);
    237            } else {
    238                int maxSig_ = minInt_ + maxFrac_;
    239                // Bug #20058: if maxInt_ > minInt_ > 1, then minInt_ should be 1.
    240                if (maxInt_ > minInt_ && minInt_ > 1) {
    241                    minInt_ = 1;
    242                }
    243                int minSig_ = minInt_ + minFrac_;
    244                // To avoid regression, maxSig is not reset when minInt_ set to 1.
    245                // TODO: Reset maxSig_ = 1 + minFrac_ to follow the spec.
    246                macros.precision = Precision::constructSignificant(minSig_, maxSig_);
    247            }
    248            macros.roundingMode = roundingMode;
    249        }
    250    }
    251 
    252    //////////////////////
    253    // COMPACT NOTATION //
    254    //////////////////////
    255 
    256    if (!properties.compactStyle.isNull()) {
    257        if (properties.compactStyle.getNoError() == UNumberCompactStyle::UNUM_LONG) {
    258            macros.notation = Notation::compactLong();
    259        } else {
    260            macros.notation = Notation::compactShort();
    261        }
    262    }
    263 
    264    /////////////////
    265    // MULTIPLIERS //
    266    /////////////////
    267 
    268    macros.scale = scaleFromProperties(properties);
    269 
    270    //////////////////////
    271    // PROPERTY EXPORTS //
    272    //////////////////////
    273 
    274    if (exportedProperties != nullptr) {
    275 
    276        exportedProperties->currency = currency;
    277        exportedProperties->roundingMode = roundingMode;
    278        exportedProperties->minimumIntegerDigits = minInt;
    279        exportedProperties->maximumIntegerDigits = maxInt == -1 ? INT32_MAX : maxInt;
    280 
    281        Precision rounding_;
    282        if (useCurrency && precision.fType == Precision::PrecisionType::RND_CURRENCY) {
    283            rounding_ = precision.withCurrency(currency, status);
    284        } else {
    285            rounding_ = precision;
    286        }
    287        int minFrac_ = minFrac;
    288        int maxFrac_ = maxFrac;
    289        int minSig_ = minSig;
    290        int maxSig_ = maxSig;
    291        double increment_ = 0.0;
    292        if (rounding_.fType == Precision::PrecisionType::RND_FRACTION) {
    293            minFrac_ = rounding_.fUnion.fracSig.fMinFrac;
    294            maxFrac_ = rounding_.fUnion.fracSig.fMaxFrac;
    295        } else if (rounding_.fType == Precision::PrecisionType::RND_INCREMENT
    296                || rounding_.fType == Precision::PrecisionType::RND_INCREMENT_ONE
    297                || rounding_.fType == Precision::PrecisionType::RND_INCREMENT_FIVE) {
    298            minFrac_ = rounding_.fUnion.increment.fMinFrac;
    299            // If incrementRounding is used, maxFrac is set equal to minFrac
    300            maxFrac_ = rounding_.fUnion.increment.fMinFrac;
    301            // Convert the integer increment to a double
    302            DecimalQuantity dq;
    303            dq.setToLong(rounding_.fUnion.increment.fIncrement);
    304            dq.adjustMagnitude(rounding_.fUnion.increment.fIncrementMagnitude);
    305            increment_ = dq.toDouble();
    306        } else if (rounding_.fType == Precision::PrecisionType::RND_SIGNIFICANT) {
    307            minSig_ = rounding_.fUnion.fracSig.fMinSig;
    308            maxSig_ = rounding_.fUnion.fracSig.fMaxSig;
    309        }
    310 
    311        exportedProperties->minimumFractionDigits = minFrac_;
    312        exportedProperties->maximumFractionDigits = maxFrac_;
    313        exportedProperties->minimumSignificantDigits = minSig_;
    314        exportedProperties->maximumSignificantDigits = maxSig_;
    315        exportedProperties->roundingIncrement = increment_;
    316    }
    317 
    318    return macros;
    319 }
    320 
    321 
    322 void PropertiesAffixPatternProvider::setTo(const DecimalFormatProperties& properties, UErrorCode& status) {
    323    fBogus = false;
    324 
    325    // There are two ways to set affixes in DecimalFormat: via the pattern string (applyPattern), and via the
    326    // explicit setters (setPositivePrefix and friends).  The way to resolve the settings is as follows:
    327    //
    328    // 1) If the explicit setting is present for the field, use it.
    329    // 2) Otherwise, follows UTS 35 rules based on the pattern string.
    330    //
    331    // Importantly, the explicit setters affect only the one field they override.  If you set the positive
    332    // prefix, that should not affect the negative prefix.
    333 
    334    // Convenience: Extract the properties into local variables.
    335    // Variables are named with three chars: [p/n][p/s][o/p]
    336    // [p/n] => p for positive, n for negative
    337    // [p/s] => p for prefix, s for suffix
    338    // [o/p] => o for escaped custom override string, p for pattern string
    339    UnicodeString ppo = AffixUtils::escape(properties.positivePrefix);
    340    UnicodeString pso = AffixUtils::escape(properties.positiveSuffix);
    341    UnicodeString npo = AffixUtils::escape(properties.negativePrefix);
    342    UnicodeString nso = AffixUtils::escape(properties.negativeSuffix);
    343    const UnicodeString& ppp = properties.positivePrefixPattern;
    344    const UnicodeString& psp = properties.positiveSuffixPattern;
    345    const UnicodeString& npp = properties.negativePrefixPattern;
    346    const UnicodeString& nsp = properties.negativeSuffixPattern;
    347 
    348    if (!properties.positivePrefix.isBogus()) {
    349        posPrefix = ppo;
    350    } else if (!ppp.isBogus()) {
    351        posPrefix = ppp;
    352    } else {
    353        // UTS 35: Default positive prefix is empty string.
    354        posPrefix = u"";
    355    }
    356 
    357    if (!properties.positiveSuffix.isBogus()) {
    358        posSuffix = pso;
    359    } else if (!psp.isBogus()) {
    360        posSuffix = psp;
    361    } else {
    362        // UTS 35: Default positive suffix is empty string.
    363        posSuffix = u"";
    364    }
    365 
    366    if (!properties.negativePrefix.isBogus()) {
    367        negPrefix = npo;
    368    } else if (!npp.isBogus()) {
    369        negPrefix = npp;
    370    } else {
    371        // UTS 35: Default negative prefix is "-" with positive prefix.
    372        // Important: We prepend the "-" to the pattern, not the override!
    373        negPrefix = ppp.isBogus() ? u"-" : u"-" + ppp;
    374    }
    375 
    376    if (!properties.negativeSuffix.isBogus()) {
    377        negSuffix = nso;
    378    } else if (!nsp.isBogus()) {
    379        negSuffix = nsp;
    380    } else {
    381        // UTS 35: Default negative prefix is the positive prefix.
    382        negSuffix = psp.isBogus() ? u"" : psp;
    383    }
    384 
    385    // For declaring if this is a currency pattern, we need to look at the
    386    // original pattern, not at any user-specified overrides.
    387    isCurrencyPattern = (
    388        AffixUtils::hasCurrencySymbols(ppp, status) ||
    389        AffixUtils::hasCurrencySymbols(psp, status) ||
    390        AffixUtils::hasCurrencySymbols(npp, status) ||
    391        AffixUtils::hasCurrencySymbols(nsp, status) ||
    392        properties.currencyAsDecimal);
    393 
    394    fCurrencyAsDecimal = properties.currencyAsDecimal;
    395 }
    396 
    397 char16_t PropertiesAffixPatternProvider::charAt(int flags, int i) const {
    398    return getStringInternal(flags).charAt(i);
    399 }
    400 
    401 int PropertiesAffixPatternProvider::length(int flags) const {
    402    return getStringInternal(flags).length();
    403 }
    404 
    405 UnicodeString PropertiesAffixPatternProvider::getString(int32_t flags) const {
    406    return getStringInternal(flags);
    407 }
    408 
    409 const UnicodeString& PropertiesAffixPatternProvider::getStringInternal(int32_t flags) const {
    410    bool prefix = (flags & AFFIX_PREFIX) != 0;
    411    bool negative = (flags & AFFIX_NEGATIVE_SUBPATTERN) != 0;
    412    if (prefix && negative) {
    413        return negPrefix;
    414    } else if (prefix) {
    415        return posPrefix;
    416    } else if (negative) {
    417        return negSuffix;
    418    } else {
    419        return posSuffix;
    420    }
    421 }
    422 
    423 bool PropertiesAffixPatternProvider::positiveHasPlusSign() const {
    424    // TODO: Change the internal APIs to propagate out the error?
    425    ErrorCode localStatus;
    426    return AffixUtils::containsType(posPrefix, TYPE_PLUS_SIGN, localStatus) ||
    427           AffixUtils::containsType(posSuffix, TYPE_PLUS_SIGN, localStatus);
    428 }
    429 
    430 bool PropertiesAffixPatternProvider::hasNegativeSubpattern() const {
    431    return (
    432        (negSuffix != posSuffix) ||
    433        negPrefix.tempSubString(1) != posPrefix ||
    434        negPrefix.charAt(0) != u'-'
    435    );
    436 }
    437 
    438 bool PropertiesAffixPatternProvider::negativeHasMinusSign() const {
    439    ErrorCode localStatus;
    440    return AffixUtils::containsType(negPrefix, TYPE_MINUS_SIGN, localStatus) ||
    441           AffixUtils::containsType(negSuffix, TYPE_MINUS_SIGN, localStatus);
    442 }
    443 
    444 bool PropertiesAffixPatternProvider::hasCurrencySign() const {
    445    return isCurrencyPattern;
    446 }
    447 
    448 bool PropertiesAffixPatternProvider::containsSymbolType(AffixPatternType type, UErrorCode& status) const {
    449    return AffixUtils::containsType(posPrefix, type, status) ||
    450           AffixUtils::containsType(posSuffix, type, status) ||
    451           AffixUtils::containsType(negPrefix, type, status) ||
    452           AffixUtils::containsType(negSuffix, type, status);
    453 }
    454 
    455 bool PropertiesAffixPatternProvider::hasBody() const {
    456    return true;
    457 }
    458 
    459 bool PropertiesAffixPatternProvider::currencyAsDecimal() const {
    460    return fCurrencyAsDecimal;
    461 }
    462 
    463 
    464 void CurrencyPluralInfoAffixProvider::setTo(const CurrencyPluralInfo& cpi,
    465                                            const DecimalFormatProperties& properties,
    466                                            UErrorCode& status) {
    467    // We need to use a PropertiesAffixPatternProvider, not the simpler version ParsedPatternInfo,
    468    // because user-specified affix overrides still need to work.
    469    fBogus = false;
    470    DecimalFormatProperties pluralProperties(properties);
    471    for (int32_t plural = 0; plural < StandardPlural::COUNT; plural++) {
    472        const char* keyword = StandardPlural::getKeyword(static_cast<StandardPlural::Form>(plural));
    473        UnicodeString patternString;
    474        patternString = cpi.getCurrencyPluralPattern(keyword, patternString);
    475        PatternParser::parseToExistingProperties(
    476                patternString,
    477                pluralProperties,
    478                IGNORE_ROUNDING_NEVER,
    479                status);
    480        affixesByPlural[plural].setTo(pluralProperties, status);
    481    }
    482 }
    483 
    484 char16_t CurrencyPluralInfoAffixProvider::charAt(int32_t flags, int32_t i) const {
    485    int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
    486    return affixesByPlural[pluralOrdinal].charAt(flags, i);
    487 }
    488 
    489 int32_t CurrencyPluralInfoAffixProvider::length(int32_t flags) const {
    490    int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
    491    return affixesByPlural[pluralOrdinal].length(flags);
    492 }
    493 
    494 UnicodeString CurrencyPluralInfoAffixProvider::getString(int32_t flags) const {
    495    int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
    496    return affixesByPlural[pluralOrdinal].getString(flags);
    497 }
    498 
    499 bool CurrencyPluralInfoAffixProvider::positiveHasPlusSign() const {
    500    return affixesByPlural[StandardPlural::OTHER].positiveHasPlusSign();
    501 }
    502 
    503 bool CurrencyPluralInfoAffixProvider::hasNegativeSubpattern() const {
    504    return affixesByPlural[StandardPlural::OTHER].hasNegativeSubpattern();
    505 }
    506 
    507 bool CurrencyPluralInfoAffixProvider::negativeHasMinusSign() const {
    508    return affixesByPlural[StandardPlural::OTHER].negativeHasMinusSign();
    509 }
    510 
    511 bool CurrencyPluralInfoAffixProvider::hasCurrencySign() const {
    512    return affixesByPlural[StandardPlural::OTHER].hasCurrencySign();
    513 }
    514 
    515 bool CurrencyPluralInfoAffixProvider::containsSymbolType(AffixPatternType type, UErrorCode& status) const {
    516    return affixesByPlural[StandardPlural::OTHER].containsSymbolType(type, status);
    517 }
    518 
    519 bool CurrencyPluralInfoAffixProvider::hasBody() const {
    520    return affixesByPlural[StandardPlural::OTHER].hasBody();
    521 }
    522 
    523 bool CurrencyPluralInfoAffixProvider::currencyAsDecimal() const {
    524    return affixesByPlural[StandardPlural::OTHER].currencyAsDecimal();
    525 }
    526 
    527 
    528 #endif /* #if !UCONFIG_NO_FORMATTING */