1 // © 2018 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
4 #include "unicode/utypes.h"
6 #if !UCONFIG_NO_FORMATTING
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
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"
19 using namespace icu::number
;
20 using namespace icu::number::impl
;
23 UnlocalizedNumberFormatter
NumberPropertyMapper::create(const DecimalFormatProperties
& properties
,
24 const DecimalFormatSymbols
& symbols
,
25 DecimalFormatWarehouse
& warehouse
,
27 return NumberFormatter::with().macros(oldToNew(properties
, symbols
, warehouse
, nullptr, status
));
30 UnlocalizedNumberFormatter
NumberPropertyMapper::create(const DecimalFormatProperties
& properties
,
31 const DecimalFormatSymbols
& symbols
,
32 DecimalFormatWarehouse
& warehouse
,
33 DecimalFormatProperties
& exportedProperties
,
35 return NumberFormatter::with().macros(
37 properties
, symbols
, warehouse
, &exportedProperties
, status
));
40 MacroProps
NumberPropertyMapper::oldToNew(const DecimalFormatProperties
& properties
,
41 const DecimalFormatSymbols
& symbols
,
42 DecimalFormatWarehouse
& warehouse
,
43 DecimalFormatProperties
* exportedProperties
,
46 Locale locale
= symbols
.getLocale();
52 macros
.symbols
.setTo(symbols
);
58 if (!properties
.currencyPluralInfo
.fPtr
.isNull()) {
59 macros
.rules
= properties
.currencyPluralInfo
.fPtr
->getPluralRules();
66 AffixPatternProvider
* affixProvider
;
67 if (properties
.currencyPluralInfo
.fPtr
.isNull()) {
68 warehouse
.currencyPluralInfoAPP
.setToBogus();
69 warehouse
.propertiesAPP
.setTo(properties
, status
);
70 affixProvider
= &warehouse
.propertiesAPP
;
72 warehouse
.currencyPluralInfoAPP
.setTo(*properties
.currencyPluralInfo
.fPtr
, properties
, status
);
73 warehouse
.propertiesAPP
.setToBogus();
74 affixProvider
= &warehouse
.currencyPluralInfoAPP
;
76 macros
.affixProvider
= affixProvider
;
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
);
88 // NOTE: Slicing is OK.
89 macros
.unit
= currency
; // NOLINT
91 warehouse
.currencySymbols
= {currency
, locale
, symbols
, status
};
92 macros
.currencySymbols
= &warehouse
.currencySymbols
;
94 ///////////////////////
95 // ROUNDING STRATEGY //
96 ///////////////////////
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
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) {
118 } else if (minFrac
== -1) {
119 minFrac
= std::min(maxFrac
, digits
);
120 } else /* if (maxFrac == -1) */ {
121 maxFrac
= std::max(minFrac
, digits
);
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
;
132 maxInt
= maxInt
< 0 ? -1 : maxInt
> kMaxIntFracSig
? -1 : maxInt
;
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
;
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
);
155 if (!precision
.isBogus()) {
156 precision
= precision
.withMode(roundingMode
);
157 macros
.precision
= precision
;
164 macros
.integerWidth
= IntegerWidth(
165 static_cast<digits_t
>(minInt
),
166 static_cast<digits_t
>(maxInt
),
167 properties
.formatFailIfMoreThanMaxDigits
);
169 ///////////////////////
170 // GROUPING STRATEGY //
171 ///////////////////////
173 macros
.grouper
= Grouper::forProperties(properties
);
179 if (properties
.formatWidth
!= -1) {
180 macros
.padder
= Padder::forProperties(properties
);
183 ///////////////////////////////
184 // DECIMAL MARK ALWAYS SHOWN //
185 ///////////////////////////////
187 macros
.decimal
= properties
.decimalSeparatorAlwaysShown
? UNUM_DECIMAL_SEPARATOR_ALWAYS
188 : UNUM_DECIMAL_SEPARATOR_AUTO
;
190 ///////////////////////
191 // SIGN ALWAYS SHOWN //
192 ///////////////////////
194 macros
.sign
= properties
.signAlwaysShown
? UNUM_SIGN_ALWAYS
: UNUM_SIGN_AUTO
;
196 /////////////////////////
197 // SCIENTIFIC NOTATION //
198 /////////////////////////
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.
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.
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.
212 macros
.integerWidth
= IntegerWidth::zeroFillTo(minInt
).truncateAt(maxInt
);
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
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
);
240 // All other scientific patterns, which mean round to minInt+maxFrac
241 macros
.precision
= Precision::constructSignificant(
242 minInt_
+ minFrac_
, minInt_
+ maxFrac_
).withMode(roundingMode
);
247 //////////////////////
248 // COMPACT NOTATION //
249 //////////////////////
251 if (!properties
.compactStyle
.isNull()) {
252 if (properties
.compactStyle
.getNoError() == UNumberCompactStyle::UNUM_LONG
) {
253 macros
.notation
= Notation::compactLong();
255 macros
.notation
= Notation::compactShort();
257 // Do not forward the affix provider.
258 macros
.affixProvider
= nullptr;
265 macros
.scale
= scaleFromProperties(properties
);
267 //////////////////////
268 // PROPERTY EXPORTS //
269 //////////////////////
271 if (exportedProperties
!= nullptr) {
273 exportedProperties
->currency
= currency
;
274 exportedProperties
->roundingMode
= roundingMode
;
275 exportedProperties
->minimumIntegerDigits
= minInt
;
276 exportedProperties
->maximumIntegerDigits
= maxInt
== -1 ? INT32_MAX
: maxInt
;
279 if (precision
.fType
== Precision::PrecisionType::RND_CURRENCY
) {
280 rounding_
= precision
.withCurrency(currency
, status
);
282 rounding_
= precision
;
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
;
301 exportedProperties
->minimumFractionDigits
= minFrac_
;
302 exportedProperties
->maximumFractionDigits
= maxFrac_
;
303 exportedProperties
->minimumSignificantDigits
= minSig_
;
304 exportedProperties
->maximumSignificantDigits
= maxSig_
;
305 exportedProperties
->roundingIncrement
= increment_
;
312 void PropertiesAffixPatternProvider::setTo(const DecimalFormatProperties
& properties
, UErrorCode
&) {
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:
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.
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.
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
;
340 if (!properties
.positivePrefix
.isBogus()) {
342 } else if (!ppp
.isBogus()) {
345 // UTS 35: Default positive prefix is empty string.
349 if (!properties
.positiveSuffix
.isBogus()) {
351 } else if (!psp
.isBogus()) {
354 // UTS 35: Default positive suffix is empty string.
358 if (!properties
.negativePrefix
.isBogus()) {
360 } else if (!npp
.isBogus()) {
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
;
368 if (!properties
.negativeSuffix
.isBogus()) {
370 } else if (!nsp
.isBogus()) {
373 // UTS 35: Default negative prefix is the positive prefix.
374 negSuffix
= psp
.isBogus() ? u
"" : psp
;
378 char16_t PropertiesAffixPatternProvider::charAt(int flags
, int i
) const {
379 return getStringInternal(flags
).charAt(i
);
382 int PropertiesAffixPatternProvider::length(int flags
) const {
383 return getStringInternal(flags
).length();
386 UnicodeString
PropertiesAffixPatternProvider::getString(int32_t flags
) const {
387 return getStringInternal(flags
);
390 const 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
) {
397 } else if (negative
) {
404 bool 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
);
411 bool PropertiesAffixPatternProvider::hasNegativeSubpattern() const {
412 // See comments in the constructor for more information on why this is always true.
416 bool PropertiesAffixPatternProvider::negativeHasMinusSign() const {
417 ErrorCode localStatus
;
418 return AffixUtils::containsType(negPrefix
, TYPE_MINUS_SIGN
, localStatus
) ||
419 AffixUtils::containsType(negSuffix
, TYPE_MINUS_SIGN
, localStatus
);
422 bool 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
);
430 bool 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
);
437 bool PropertiesAffixPatternProvider::hasBody() const {
442 void 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.
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(
456 IGNORE_ROUNDING_NEVER
,
458 affixesByPlural
[plural
].setTo(pluralProperties
, status
);
462 char16_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
);
467 int32_t CurrencyPluralInfoAffixProvider::length(int32_t flags
) const {
468 int32_t pluralOrdinal
= (flags
& AFFIX_PLURAL_MASK
);
469 return affixesByPlural
[pluralOrdinal
].length(flags
);
472 UnicodeString
CurrencyPluralInfoAffixProvider::getString(int32_t flags
) const {
473 int32_t pluralOrdinal
= (flags
& AFFIX_PLURAL_MASK
);
474 return affixesByPlural
[pluralOrdinal
].getString(flags
);
477 bool CurrencyPluralInfoAffixProvider::positiveHasPlusSign() const {
478 return affixesByPlural
[StandardPlural::OTHER
].positiveHasPlusSign();
481 bool CurrencyPluralInfoAffixProvider::hasNegativeSubpattern() const {
482 return affixesByPlural
[StandardPlural::OTHER
].hasNegativeSubpattern();
485 bool CurrencyPluralInfoAffixProvider::negativeHasMinusSign() const {
486 return affixesByPlural
[StandardPlural::OTHER
].negativeHasMinusSign();
489 bool CurrencyPluralInfoAffixProvider::hasCurrencySign() const {
490 return affixesByPlural
[StandardPlural::OTHER
].hasCurrencySign();
493 bool CurrencyPluralInfoAffixProvider::containsSymbolType(AffixPatternType type
, UErrorCode
& status
) const {
494 return affixesByPlural
[StandardPlural::OTHER
].containsSymbolType(type
, status
);
497 bool CurrencyPluralInfoAffixProvider::hasBody() const {
498 return affixesByPlural
[StandardPlural::OTHER
].hasBody();
502 #endif /* #if !UCONFIG_NO_FORMATTING */