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