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() ||
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
);
90 // NOTE: Slicing is OK.
91 macros
.unit
= currency
; // NOLINT
93 warehouse
.currencySymbols
= {currency
, locale
, symbols
, status
};
94 macros
.currencySymbols
= &warehouse
.currencySymbols
;
96 ///////////////////////
97 // ROUNDING STRATEGY //
98 ///////////////////////
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
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) {
121 } else if (minFrac
== -1) {
122 minFrac
= std::min(maxFrac
, digits
);
123 } else /* if (maxFrac == -1) */ {
124 maxFrac
= std::max(minFrac
, digits
);
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 if (minInt
== 0 && maxFrac
!= 0) {
131 // Force a digit after the decimal point.
132 minFrac
= minFrac
<= 0 ? 1 : minFrac
;
133 maxFrac
= maxFrac
< 0 ? -1 : maxFrac
< minFrac
? minFrac
: maxFrac
;
135 maxInt
= maxInt
< 0 ? -1 : maxInt
> kMaxIntFracSig
? -1 : maxInt
;
137 // Force a digit before the decimal point.
138 minFrac
= minFrac
< 0 ? 0 : minFrac
;
139 maxFrac
= maxFrac
< 0 ? -1 : maxFrac
< minFrac
? minFrac
: maxFrac
;
140 minInt
= minInt
<= 0 ? 1 : minInt
> kMaxIntFracSig
? 1 : minInt
;
141 maxInt
= maxInt
< 0 ? -1 : maxInt
< minInt
? minInt
: maxInt
> kMaxIntFracSig
? -1 : maxInt
;
144 if (!properties
.currencyUsage
.isNull()) {
145 precision
= Precision::constructCurrency(currencyUsage
).withCurrency(currency
);
146 } else if (roundingIncrement
!= 0.0) {
147 double roundingIncrAdj
= roundingIncrement
;
148 if (!explicitMinMaxSig
&& PatternStringUtils::ignoreRoundingIncrement(&roundingIncrAdj
, maxFrac
)) {
149 precision
= Precision::constructFraction(minFrac
, maxFrac
);
151 double delta
= (roundingIncrement
/roundingIncrAdj
) - 1.0;
152 if (delta
> 0.001 || delta
< -0.001) {
153 roundingIncrAdj
= roundingIncrement
;
155 didAdjustRoundIncr
= true;
157 if (explicitMinMaxSig
) {
158 minSig
= minSig
< 1 ? 1 : minSig
> kMaxIntFracSig
? kMaxIntFracSig
: minSig
;
159 maxSig
= maxSig
< 0 ? kMaxIntFracSig
: maxSig
< minSig
? minSig
: maxSig
> kMaxIntFracSig
160 ? kMaxIntFracSig
: maxSig
;
161 precision
= Precision::constructIncrementSignificant(roundingIncrAdj
, minSig
, maxSig
); // Apple rdar://52538227
163 precision
= Precision::constructIncrement(roundingIncrAdj
, minFrac
);
166 } else if (explicitMinMaxSig
) {
167 minSig
= minSig
< 1 ? 1 : minSig
> kMaxIntFracSig
? kMaxIntFracSig
: minSig
;
168 maxSig
= maxSig
< 0 ? kMaxIntFracSig
: maxSig
< minSig
? minSig
: maxSig
> kMaxIntFracSig
169 ? kMaxIntFracSig
: maxSig
;
170 precision
= Precision::constructSignificant(minSig
, maxSig
);
171 } else if (explicitMinMaxFrac
) {
172 precision
= Precision::constructFraction(minFrac
, maxFrac
);
173 } else if (useCurrency
) {
174 precision
= Precision::constructCurrency(currencyUsage
);
176 if (!precision
.isBogus()) {
177 precision
.fRoundingMode
= roundingMode
;
178 macros
.precision
= precision
;
181 // Apple addition for <rdar://problem/39240173>
182 macros
.adjustDoublePrecision
= (!properties
.formatFullPrecision
&& !explicitMinMaxSig
&& maxFrac
>15);
188 macros
.integerWidth
= IntegerWidth(
189 static_cast<digits_t
>(minInt
),
190 static_cast<digits_t
>(maxInt
),
191 properties
.formatFailIfMoreThanMaxDigits
);
193 ///////////////////////
194 // GROUPING STRATEGY //
195 ///////////////////////
197 macros
.grouper
= Grouper::forProperties(properties
);
203 if (properties
.formatWidth
> 0) {
204 macros
.padder
= Padder::forProperties(properties
);
207 ///////////////////////////////
208 // DECIMAL MARK ALWAYS SHOWN //
209 ///////////////////////////////
211 macros
.decimal
= properties
.decimalSeparatorAlwaysShown
? UNUM_DECIMAL_SEPARATOR_ALWAYS
212 : UNUM_DECIMAL_SEPARATOR_AUTO
;
214 ///////////////////////
215 // SIGN ALWAYS SHOWN //
216 ///////////////////////
218 macros
.sign
= properties
.signAlwaysShown
? UNUM_SIGN_ALWAYS
: UNUM_SIGN_AUTO
;
220 /////////////////////////
221 // SCIENTIFIC NOTATION //
222 /////////////////////////
224 if (properties
.minimumExponentDigits
!= -1) {
225 // Scientific notation is required.
226 // This whole section feels like a hack, but it is needed for regression tests.
227 // The mapping from property bag to scientific notation is nontrivial due to LDML rules.
229 // But #13110: The maximum of 8 digits has unknown origins and is not in the spec.
230 // If maxInt is greater than 8, it is set to minInt, even if minInt is greater than 8.
232 macros
.integerWidth
= IntegerWidth::zeroFillTo(minInt
).truncateAt(maxInt
);
233 } else if (maxInt
> minInt
&& minInt
> 1) {
234 // Bug #13289: if maxInt > minInt > 1, then minInt should be 1.
236 macros
.integerWidth
= IntegerWidth::zeroFillTo(minInt
).truncateAt(maxInt
);
238 int engineering
= maxInt
< 0 ? -1 : maxInt
;
239 macros
.notation
= ScientificNotation(
240 // Engineering interval:
241 static_cast<int8_t>(engineering
),
242 // Enforce minimum integer digits (for patterns like "000.00E0"):
243 (engineering
== minInt
),
244 // Minimum exponent digits:
245 static_cast<digits_t
>(properties
.minimumExponentDigits
),
246 // Exponent sign always shown:
247 properties
.exponentSignAlwaysShown
? UNUM_SIGN_ALWAYS
: UNUM_SIGN_AUTO
);
248 // Scientific notation also involves overriding the rounding mode.
249 // TODO: Overriding here is a bit of a hack. Should this logic go earlier?
250 if (macros
.precision
.fType
== Precision::PrecisionType::RND_FRACTION
) {
251 // For the purposes of rounding, get the original min/max int/frac, since the local
252 // variables have been manipulated for display purposes.
253 int maxInt_
= properties
.maximumIntegerDigits
;
254 int minInt_
= properties
.minimumIntegerDigits
;
255 int minFrac_
= properties
.minimumFractionDigits
;
256 int maxFrac_
= properties
.maximumFractionDigits
;
257 if (minInt_
== 0 && maxFrac_
== 0) {
258 // Patterns like "#E0" and "##E0", which mean no rounding!
259 macros
.precision
= Precision::unlimited();
260 } else if (minInt_
== 0 && minFrac_
== 0) {
261 // Patterns like "#.##E0" (no zeros in the mantissa), which mean round to maxFrac+1
262 macros
.precision
= Precision::constructSignificant(1, maxFrac_
+ 1);
264 int maxSig_
= minInt_
+ maxFrac_
;
265 // Bug #20058: if maxInt_ > minInt_ > 1, then minInt_ should be 1.
266 if (maxInt_
> minInt_
&& minInt_
> 1) {
269 int minSig_
= minInt_
+ minFrac_
;
270 // To avoid regression, maxSig is not reset when minInt_ set to 1.
271 // TODO: Reset maxSig_ = 1 + minFrac_ to follow the spec.
272 macros
.precision
= Precision::constructSignificant(minSig_
, maxSig_
);
274 macros
.precision
.fRoundingMode
= roundingMode
;
278 //////////////////////
279 // COMPACT NOTATION //
280 //////////////////////
282 if (!properties
.compactStyle
.isNull()) {
283 if (properties
.compactStyle
.getNoError() == UNumberCompactStyle::UNUM_LONG
) {
284 macros
.notation
= Notation::compactLong();
286 macros
.notation
= Notation::compactShort();
288 // Do not forward the affix provider.
289 macros
.affixProvider
= nullptr;
296 macros
.scale
= scaleFromProperties(properties
);
298 //////////////////////
299 // PROPERTY EXPORTS //
300 //////////////////////
302 if (exportedProperties
!= nullptr) {
304 exportedProperties
->currency
= currency
;
305 exportedProperties
->roundingMode
= roundingMode
;
306 exportedProperties
->minimumIntegerDigits
= minInt
;
307 exportedProperties
->maximumIntegerDigits
= maxInt
== -1 ? INT32_MAX
: maxInt
;
310 if (precision
.fType
== Precision::PrecisionType::RND_CURRENCY
) {
311 rounding_
= precision
.withCurrency(currency
, status
);
313 rounding_
= precision
;
315 int minFrac_
= minFrac
;
316 int maxFrac_
= maxFrac
;
317 int minSig_
= minSig
;
318 int maxSig_
= maxSig
;
319 double increment_
= 0.0;
320 if (rounding_
.fType
== Precision::PrecisionType::RND_FRACTION
) {
321 minFrac_
= rounding_
.fUnion
.fracSig
.fMinFrac
;
322 maxFrac_
= rounding_
.fUnion
.fracSig
.fMaxFrac
;
323 } else if (rounding_
.fType
== Precision::PrecisionType::RND_INCREMENT
324 || rounding_
.fType
== Precision::PrecisionType::RND_INCREMENT_ONE
325 || rounding_
.fType
== Precision::PrecisionType::RND_INCREMENT_FIVE
) {
326 increment_
= (didAdjustRoundIncr
)? roundingIncrement
: rounding_
.fUnion
.increment
.fIncrement
; // rdar://51452216
327 minFrac_
= rounding_
.fUnion
.increment
.fMinFrac
;
328 maxFrac_
= rounding_
.fUnion
.increment
.fMinFrac
;
329 } else if (rounding_
.fType
== Precision::PrecisionType::RND_SIGNIFICANT
) {
330 minSig_
= rounding_
.fUnion
.fracSig
.fMinSig
;
331 maxSig_
= rounding_
.fUnion
.fracSig
.fMaxSig
;
332 } else if (rounding_
.fType
== Precision::PrecisionType::RND_INCREMENT_SIGNIFICANT
) { // Apple rdar://52538227
333 increment_
= (didAdjustRoundIncr
)? roundingIncrement
: rounding_
.fUnion
.incrSig
.fIncrement
; // rdar://51452216
334 minSig_
= rounding_
.fUnion
.incrSig
.fMinSig
;
335 maxSig_
= rounding_
.fUnion
.incrSig
.fMaxSig
;
338 exportedProperties
->minimumFractionDigits
= minFrac_
;
339 exportedProperties
->maximumFractionDigits
= maxFrac_
;
340 exportedProperties
->minimumSignificantDigits
= minSig_
;
341 exportedProperties
->maximumSignificantDigits
= maxSig_
;
342 exportedProperties
->roundingIncrement
= increment_
;
349 void PropertiesAffixPatternProvider::setTo(const DecimalFormatProperties
& properties
, UErrorCode
& status
) {
352 // There are two ways to set affixes in DecimalFormat: via the pattern string (applyPattern), and via the
353 // explicit setters (setPositivePrefix and friends). The way to resolve the settings is as follows:
355 // 1) If the explicit setting is present for the field, use it.
356 // 2) Otherwise, follows UTS 35 rules based on the pattern string.
358 // Importantly, the explicit setters affect only the one field they override. If you set the positive
359 // prefix, that should not affect the negative prefix.
361 // Convenience: Extract the properties into local variables.
362 // Variables are named with three chars: [p/n][p/s][o/p]
363 // [p/n] => p for positive, n for negative
364 // [p/s] => p for prefix, s for suffix
365 // [o/p] => o for escaped custom override string, p for pattern string
366 UnicodeString ppo
= AffixUtils::escape(properties
.positivePrefix
);
367 UnicodeString pso
= AffixUtils::escape(properties
.positiveSuffix
);
368 UnicodeString npo
= AffixUtils::escape(properties
.negativePrefix
);
369 UnicodeString nso
= AffixUtils::escape(properties
.negativeSuffix
);
370 const UnicodeString
& ppp
= properties
.positivePrefixPattern
;
371 const UnicodeString
& psp
= properties
.positiveSuffixPattern
;
372 const UnicodeString
& npp
= properties
.negativePrefixPattern
;
373 const UnicodeString
& nsp
= properties
.negativeSuffixPattern
;
375 if (!properties
.positivePrefix
.isBogus()) {
377 } else if (!ppp
.isBogus()) {
380 // UTS 35: Default positive prefix is empty string.
384 if (!properties
.positiveSuffix
.isBogus()) {
386 } else if (!psp
.isBogus()) {
389 // UTS 35: Default positive suffix is empty string.
393 if (!properties
.negativePrefix
.isBogus()) {
395 } else if (!npp
.isBogus()) {
398 // UTS 35: Default negative prefix is "-" with positive prefix.
399 // Important: We prepend the "-" to the pattern, not the override!
400 negPrefix
= ppp
.isBogus() ? u
"-" : u
"-" + ppp
;
403 if (!properties
.negativeSuffix
.isBogus()) {
405 } else if (!nsp
.isBogus()) {
408 // UTS 35: Default negative prefix is the positive prefix.
409 negSuffix
= psp
.isBogus() ? u
"" : psp
;
412 // For declaring if this is a currency pattern, we need to look at the
413 // original pattern, not at any user-specified overrides.
414 isCurrencyPattern
= (
415 AffixUtils::hasCurrencySymbols(ppp
, status
) ||
416 AffixUtils::hasCurrencySymbols(psp
, status
) ||
417 AffixUtils::hasCurrencySymbols(npp
, status
) ||
418 AffixUtils::hasCurrencySymbols(nsp
, status
));
421 char16_t PropertiesAffixPatternProvider::charAt(int flags
, int i
) const {
422 return getStringInternal(flags
).charAt(i
);
425 int PropertiesAffixPatternProvider::length(int flags
) const {
426 return getStringInternal(flags
).length();
429 UnicodeString
PropertiesAffixPatternProvider::getString(int32_t flags
) const {
430 return getStringInternal(flags
);
433 const UnicodeString
& PropertiesAffixPatternProvider::getStringInternal(int32_t flags
) const {
434 bool prefix
= (flags
& AFFIX_PREFIX
) != 0;
435 bool negative
= (flags
& AFFIX_NEGATIVE_SUBPATTERN
) != 0;
436 if (prefix
&& negative
) {
440 } else if (negative
) {
447 bool PropertiesAffixPatternProvider::positiveHasPlusSign() const {
448 // TODO: Change the internal APIs to propagate out the error?
449 ErrorCode localStatus
;
450 return AffixUtils::containsType(posPrefix
, TYPE_PLUS_SIGN
, localStatus
) ||
451 AffixUtils::containsType(posSuffix
, TYPE_PLUS_SIGN
, localStatus
);
454 bool PropertiesAffixPatternProvider::hasNegativeSubpattern() const {
456 (negSuffix
!= posSuffix
) ||
457 negPrefix
.tempSubString(1) != posPrefix
||
458 negPrefix
.charAt(0) != u
'-'
462 bool PropertiesAffixPatternProvider::negativeHasMinusSign() const {
463 ErrorCode localStatus
;
464 return AffixUtils::containsType(negPrefix
, TYPE_MINUS_SIGN
, localStatus
) ||
465 AffixUtils::containsType(negSuffix
, TYPE_MINUS_SIGN
, localStatus
);
468 bool PropertiesAffixPatternProvider::hasCurrencySign() const {
469 return isCurrencyPattern
;
472 bool PropertiesAffixPatternProvider::containsSymbolType(AffixPatternType type
, UErrorCode
& status
) const {
473 return AffixUtils::containsType(posPrefix
, type
, status
) ||
474 AffixUtils::containsType(posSuffix
, type
, status
) ||
475 AffixUtils::containsType(negPrefix
, type
, status
) ||
476 AffixUtils::containsType(negSuffix
, type
, status
);
479 bool PropertiesAffixPatternProvider::hasBody() const {
484 void CurrencyPluralInfoAffixProvider::setTo(const CurrencyPluralInfo
& cpi
,
485 const DecimalFormatProperties
& properties
,
486 UErrorCode
& status
) {
487 // We need to use a PropertiesAffixPatternProvider, not the simpler version ParsedPatternInfo,
488 // because user-specified affix overrides still need to work.
490 DecimalFormatProperties
pluralProperties(properties
);
491 for (int32_t plural
= 0; plural
< StandardPlural::COUNT
; plural
++) {
492 const char* keyword
= StandardPlural::getKeyword(static_cast<StandardPlural::Form
>(plural
));
493 UnicodeString patternString
;
494 patternString
= cpi
.getCurrencyPluralPattern(keyword
, patternString
);
495 PatternParser::parseToExistingProperties(
498 IGNORE_ROUNDING_NEVER
,
500 affixesByPlural
[plural
].setTo(pluralProperties
, status
);
504 char16_t CurrencyPluralInfoAffixProvider::charAt(int32_t flags
, int32_t i
) const {
505 int32_t pluralOrdinal
= (flags
& AFFIX_PLURAL_MASK
);
506 return affixesByPlural
[pluralOrdinal
].charAt(flags
, i
);
509 int32_t CurrencyPluralInfoAffixProvider::length(int32_t flags
) const {
510 int32_t pluralOrdinal
= (flags
& AFFIX_PLURAL_MASK
);
511 return affixesByPlural
[pluralOrdinal
].length(flags
);
514 UnicodeString
CurrencyPluralInfoAffixProvider::getString(int32_t flags
) const {
515 int32_t pluralOrdinal
= (flags
& AFFIX_PLURAL_MASK
);
516 return affixesByPlural
[pluralOrdinal
].getString(flags
);
519 bool CurrencyPluralInfoAffixProvider::positiveHasPlusSign() const {
520 return affixesByPlural
[StandardPlural::OTHER
].positiveHasPlusSign();
523 bool CurrencyPluralInfoAffixProvider::hasNegativeSubpattern() const {
524 return affixesByPlural
[StandardPlural::OTHER
].hasNegativeSubpattern();
527 bool CurrencyPluralInfoAffixProvider::negativeHasMinusSign() const {
528 return affixesByPlural
[StandardPlural::OTHER
].negativeHasMinusSign();
531 bool CurrencyPluralInfoAffixProvider::hasCurrencySign() const {
532 return affixesByPlural
[StandardPlural::OTHER
].hasCurrencySign();
535 bool CurrencyPluralInfoAffixProvider::containsSymbolType(AffixPatternType type
, UErrorCode
& status
) const {
536 return affixesByPlural
[StandardPlural::OTHER
].containsSymbolType(type
, status
);
539 bool CurrencyPluralInfoAffixProvider::hasBody() const {
540 return affixesByPlural
[StandardPlural::OTHER
].hasBody();
544 #endif /* #if !UCONFIG_NO_FORMATTING */