Coverage Report

Created: 2025-06-24 06:43

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