]> git.saurik.com Git - apple/icu.git/blame - icuSources/i18n/number_mapper.cpp
ICU-62141.0.1.tar.gz
[apple/icu.git] / icuSources / i18n / number_mapper.cpp
CommitLineData
0f5d89e8
A
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
18using namespace icu;
19using namespace icu::number;
20using namespace icu::number::impl;
21
22
23UnlocalizedNumberFormatter 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
30UnlocalizedNumberFormatter 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
40MacroProps 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
229 // have been manipulated for display purposes.
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 // All other scientific patterns, which mean round to minInt+maxFrac
241 macros.precision = Precision::constructSignificant(
242 minInt_ + minFrac_, minInt_ + maxFrac_).withMode(roundingMode);
243 }
244 }
245 }
246
247 //////////////////////
248 // COMPACT NOTATION //
249 //////////////////////
250
251 if (!properties.compactStyle.isNull()) {
252 if (properties.compactStyle.getNoError() == UNumberCompactStyle::UNUM_LONG) {
253 macros.notation = Notation::compactLong();
254 } else {
255 macros.notation = Notation::compactShort();
256 }
257 // Do not forward the affix provider.
258 macros.affixProvider = nullptr;
259 }
260
261 /////////////////
262 // MULTIPLIERS //
263 /////////////////
264
265 macros.scale = scaleFromProperties(properties);
266
267 //////////////////////
268 // PROPERTY EXPORTS //
269 //////////////////////
270
271 if (exportedProperties != nullptr) {
272
273 exportedProperties->currency = currency;
274 exportedProperties->roundingMode = roundingMode;
275 exportedProperties->minimumIntegerDigits = minInt;
276 exportedProperties->maximumIntegerDigits = maxInt == -1 ? INT32_MAX : maxInt;
277
278 Precision rounding_;
279 if (precision.fType == Precision::PrecisionType::RND_CURRENCY) {
280 rounding_ = precision.withCurrency(currency, status);
281 } else {
282 rounding_ = precision;
283 }
284 int minFrac_ = minFrac;
285 int maxFrac_ = maxFrac;
286 int minSig_ = minSig;
287 int maxSig_ = maxSig;
288 double increment_ = 0.0;
289 if (rounding_.fType == Precision::PrecisionType::RND_FRACTION) {
290 minFrac_ = rounding_.fUnion.fracSig.fMinFrac;
291 maxFrac_ = rounding_.fUnion.fracSig.fMaxFrac;
292 } else if (rounding_.fType == Precision::PrecisionType::RND_INCREMENT) {
293 increment_ = rounding_.fUnion.increment.fIncrement;
294 minFrac_ = rounding_.fUnion.increment.fMinFrac;
295 maxFrac_ = rounding_.fUnion.increment.fMinFrac;
296 } else if (rounding_.fType == Precision::PrecisionType::RND_SIGNIFICANT) {
297 minSig_ = rounding_.fUnion.fracSig.fMinSig;
298 maxSig_ = rounding_.fUnion.fracSig.fMaxSig;
299 }
300
301 exportedProperties->minimumFractionDigits = minFrac_;
302 exportedProperties->maximumFractionDigits = maxFrac_;
303 exportedProperties->minimumSignificantDigits = minSig_;
304 exportedProperties->maximumSignificantDigits = maxSig_;
305 exportedProperties->roundingIncrement = increment_;
306 }
307
308 return macros;
309}
310
311
312void PropertiesAffixPatternProvider::setTo(const DecimalFormatProperties& properties, UErrorCode&) {
313 fBogus = false;
314
315 // There are two ways to set affixes in DecimalFormat: via the pattern string (applyPattern), and via the
316 // explicit setters (setPositivePrefix and friends). The way to resolve the settings is as follows:
317 //
318 // 1) If the explicit setting is present for the field, use it.
319 // 2) Otherwise, follows UTS 35 rules based on the pattern string.
320 //
321 // Importantly, the explicit setters affect only the one field they override. If you set the positive
322 // prefix, that should not affect the negative prefix. Since it is impossible for the user of this class
323 // to know whether the origin for a string was the override or the pattern, we have to say that we always
324 // have a negative subpattern and perform all resolution logic here.
325
326 // Convenience: Extract the properties into local variables.
327 // Variables are named with three chars: [p/n][p/s][o/p]
328 // [p/n] => p for positive, n for negative
329 // [p/s] => p for prefix, s for suffix
330 // [o/p] => o for escaped custom override string, p for pattern string
331 UnicodeString ppo = AffixUtils::escape(properties.positivePrefix);
332 UnicodeString pso = AffixUtils::escape(properties.positiveSuffix);
333 UnicodeString npo = AffixUtils::escape(properties.negativePrefix);
334 UnicodeString nso = AffixUtils::escape(properties.negativeSuffix);
335 const UnicodeString& ppp = properties.positivePrefixPattern;
336 const UnicodeString& psp = properties.positiveSuffixPattern;
337 const UnicodeString& npp = properties.negativePrefixPattern;
338 const UnicodeString& nsp = properties.negativeSuffixPattern;
339
340 if (!properties.positivePrefix.isBogus()) {
341 posPrefix = ppo;
342 } else if (!ppp.isBogus()) {
343 posPrefix = ppp;
344 } else {
345 // UTS 35: Default positive prefix is empty string.
346 posPrefix = u"";
347 }
348
349 if (!properties.positiveSuffix.isBogus()) {
350 posSuffix = pso;
351 } else if (!psp.isBogus()) {
352 posSuffix = psp;
353 } else {
354 // UTS 35: Default positive suffix is empty string.
355 posSuffix = u"";
356 }
357
358 if (!properties.negativePrefix.isBogus()) {
359 negPrefix = npo;
360 } else if (!npp.isBogus()) {
361 negPrefix = npp;
362 } else {
363 // UTS 35: Default negative prefix is "-" with positive prefix.
364 // Important: We prepend the "-" to the pattern, not the override!
365 negPrefix = ppp.isBogus() ? u"-" : u"-" + ppp;
366 }
367
368 if (!properties.negativeSuffix.isBogus()) {
369 negSuffix = nso;
370 } else if (!nsp.isBogus()) {
371 negSuffix = nsp;
372 } else {
373 // UTS 35: Default negative prefix is the positive prefix.
374 negSuffix = psp.isBogus() ? u"" : psp;
375 }
376}
377
378char16_t PropertiesAffixPatternProvider::charAt(int flags, int i) const {
379 return getStringInternal(flags).charAt(i);
380}
381
382int PropertiesAffixPatternProvider::length(int flags) const {
383 return getStringInternal(flags).length();
384}
385
386UnicodeString PropertiesAffixPatternProvider::getString(int32_t flags) const {
387 return getStringInternal(flags);
388}
389
390const UnicodeString& PropertiesAffixPatternProvider::getStringInternal(int32_t flags) const {
391 bool prefix = (flags & AFFIX_PREFIX) != 0;
392 bool negative = (flags & AFFIX_NEGATIVE_SUBPATTERN) != 0;
393 if (prefix && negative) {
394 return negPrefix;
395 } else if (prefix) {
396 return posPrefix;
397 } else if (negative) {
398 return negSuffix;
399 } else {
400 return posSuffix;
401 }
402}
403
404bool PropertiesAffixPatternProvider::positiveHasPlusSign() const {
405 // TODO: Change the internal APIs to propagate out the error?
406 ErrorCode localStatus;
407 return AffixUtils::containsType(posPrefix, TYPE_PLUS_SIGN, localStatus) ||
408 AffixUtils::containsType(posSuffix, TYPE_PLUS_SIGN, localStatus);
409}
410
411bool PropertiesAffixPatternProvider::hasNegativeSubpattern() const {
412 // See comments in the constructor for more information on why this is always true.
413 return true;
414}
415
416bool PropertiesAffixPatternProvider::negativeHasMinusSign() const {
417 ErrorCode localStatus;
418 return AffixUtils::containsType(negPrefix, TYPE_MINUS_SIGN, localStatus) ||
419 AffixUtils::containsType(negSuffix, TYPE_MINUS_SIGN, localStatus);
420}
421
422bool PropertiesAffixPatternProvider::hasCurrencySign() const {
423 ErrorCode localStatus;
424 return AffixUtils::hasCurrencySymbols(posPrefix, localStatus) ||
425 AffixUtils::hasCurrencySymbols(posSuffix, localStatus) ||
426 AffixUtils::hasCurrencySymbols(negPrefix, localStatus) ||
427 AffixUtils::hasCurrencySymbols(negSuffix, localStatus);
428}
429
430bool PropertiesAffixPatternProvider::containsSymbolType(AffixPatternType type, UErrorCode& status) const {
431 return AffixUtils::containsType(posPrefix, type, status) ||
432 AffixUtils::containsType(posSuffix, type, status) ||
433 AffixUtils::containsType(negPrefix, type, status) ||
434 AffixUtils::containsType(negSuffix, type, status);
435}
436
437bool PropertiesAffixPatternProvider::hasBody() const {
438 return true;
439}
440
441
442void CurrencyPluralInfoAffixProvider::setTo(const CurrencyPluralInfo& cpi,
443 const DecimalFormatProperties& properties,
444 UErrorCode& status) {
445 // We need to use a PropertiesAffixPatternProvider, not the simpler version ParsedPatternInfo,
446 // because user-specified affix overrides still need to work.
447 fBogus = false;
448 DecimalFormatProperties pluralProperties(properties);
449 for (int32_t plural = 0; plural < StandardPlural::COUNT; plural++) {
450 const char* keyword = StandardPlural::getKeyword(static_cast<StandardPlural::Form>(plural));
451 UnicodeString patternString;
452 patternString = cpi.getCurrencyPluralPattern(keyword, patternString);
453 PatternParser::parseToExistingProperties(
454 patternString,
455 pluralProperties,
456 IGNORE_ROUNDING_NEVER,
457 status);
458 affixesByPlural[plural].setTo(pluralProperties, status);
459 }
460}
461
462char16_t CurrencyPluralInfoAffixProvider::charAt(int32_t flags, int32_t i) const {
463 int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
464 return affixesByPlural[pluralOrdinal].charAt(flags, i);
465}
466
467int32_t CurrencyPluralInfoAffixProvider::length(int32_t flags) const {
468 int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
469 return affixesByPlural[pluralOrdinal].length(flags);
470}
471
472UnicodeString CurrencyPluralInfoAffixProvider::getString(int32_t flags) const {
473 int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
474 return affixesByPlural[pluralOrdinal].getString(flags);
475}
476
477bool CurrencyPluralInfoAffixProvider::positiveHasPlusSign() const {
478 return affixesByPlural[StandardPlural::OTHER].positiveHasPlusSign();
479}
480
481bool CurrencyPluralInfoAffixProvider::hasNegativeSubpattern() const {
482 return affixesByPlural[StandardPlural::OTHER].hasNegativeSubpattern();
483}
484
485bool CurrencyPluralInfoAffixProvider::negativeHasMinusSign() const {
486 return affixesByPlural[StandardPlural::OTHER].negativeHasMinusSign();
487}
488
489bool CurrencyPluralInfoAffixProvider::hasCurrencySign() const {
490 return affixesByPlural[StandardPlural::OTHER].hasCurrencySign();
491}
492
493bool CurrencyPluralInfoAffixProvider::containsSymbolType(AffixPatternType type, UErrorCode& status) const {
494 return affixesByPlural[StandardPlural::OTHER].containsSymbolType(type, status);
495}
496
497bool CurrencyPluralInfoAffixProvider::hasBody() const {
498 return affixesByPlural[StandardPlural::OTHER].hasBody();
499}
500
501
502#endif /* #if !UCONFIG_NO_FORMATTING */