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