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