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 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
;
133 maxInt
= maxInt
< 0 ? -1 : maxInt
> kMaxIntFracSig
? -1 : maxInt
;
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
;
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
);
149 double delta
= (roundingIncrement
/roundingIncrAdj
) - 1.0;
150 if (delta
> 0.001 || delta
< -0.001) {
151 roundingIncrAdj
= roundingIncrement
;
153 didAdjustRoundIncr
= true;
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
161 precision
= Precision::constructIncrement(roundingIncrAdj
, minFrac
);
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
);
174 if (!precision
.isBogus()) {
175 precision
.fRoundingMode
= roundingMode
;
176 macros
.precision
= precision
;
179 // Apple addition for <rdar://problem/39240173>
180 macros
.adjustDoublePrecision
= (!properties
.formatFullPrecision
&& !explicitMinMaxSig
&& maxFrac
>15);
186 macros
.integerWidth
= IntegerWidth(
187 static_cast<digits_t
>(minInt
),
188 static_cast<digits_t
>(maxInt
),
189 properties
.formatFailIfMoreThanMaxDigits
);
191 ///////////////////////
192 // GROUPING STRATEGY //
193 ///////////////////////
195 macros
.grouper
= Grouper::forProperties(properties
);
201 if (properties
.formatWidth
> 0) {
202 macros
.padder
= Padder::forProperties(properties
);
205 ///////////////////////////////
206 // DECIMAL MARK ALWAYS SHOWN //
207 ///////////////////////////////
209 macros
.decimal
= properties
.decimalSeparatorAlwaysShown
? UNUM_DECIMAL_SEPARATOR_ALWAYS
210 : UNUM_DECIMAL_SEPARATOR_AUTO
;
212 ///////////////////////
213 // SIGN ALWAYS SHOWN //
214 ///////////////////////
216 macros
.sign
= properties
.signAlwaysShown
? UNUM_SIGN_ALWAYS
: UNUM_SIGN_AUTO
;
218 /////////////////////////
219 // SCIENTIFIC NOTATION //
220 /////////////////////////
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.
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.
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.
234 macros
.integerWidth
= IntegerWidth::zeroFillTo(minInt
).truncateAt(maxInt
);
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);
262 int maxSig_
= minInt_
+ maxFrac_
;
263 // Bug #20058: if maxInt_ > minInt_ > 1, then minInt_ should be 1.
264 if (maxInt_
> minInt_
&& minInt_
> 1) {
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_
);
272 macros
.precision
.fRoundingMode
= roundingMode
;
276 //////////////////////
277 // COMPACT NOTATION //
278 //////////////////////
280 if (!properties
.compactStyle
.isNull()) {
281 if (properties
.compactStyle
.getNoError() == UNumberCompactStyle::UNUM_LONG
) {
282 macros
.notation
= Notation::compactLong();
284 macros
.notation
= Notation::compactShort();
286 // Do not forward the affix provider.
287 macros
.affixProvider
= nullptr;
294 macros
.scale
= scaleFromProperties(properties
);
296 //////////////////////
297 // PROPERTY EXPORTS //
298 //////////////////////
300 if (exportedProperties
!= nullptr) {
302 exportedProperties
->currency
= currency
;
303 exportedProperties
->roundingMode
= roundingMode
;
304 exportedProperties
->minimumIntegerDigits
= minInt
;
305 exportedProperties
->maximumIntegerDigits
= maxInt
== -1 ? INT32_MAX
: maxInt
;
308 if (precision
.fType
== Precision::PrecisionType::RND_CURRENCY
) {
309 rounding_
= precision
.withCurrency(currency
, status
);
311 rounding_
= precision
;
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
;
336 exportedProperties
->minimumFractionDigits
= minFrac_
;
337 exportedProperties
->maximumFractionDigits
= maxFrac_
;
338 exportedProperties
->minimumSignificantDigits
= minSig_
;
339 exportedProperties
->maximumSignificantDigits
= maxSig_
;
340 exportedProperties
->roundingIncrement
= increment_
;
347 void PropertiesAffixPatternProvider::setTo(const DecimalFormatProperties
& properties
, UErrorCode
& status
) {
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:
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.
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.
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
;
373 if (!properties
.positivePrefix
.isBogus()) {
375 } else if (!ppp
.isBogus()) {
378 // UTS 35: Default positive prefix is empty string.
382 if (!properties
.positiveSuffix
.isBogus()) {
384 } else if (!psp
.isBogus()) {
387 // UTS 35: Default positive suffix is empty string.
391 if (!properties
.negativePrefix
.isBogus()) {
393 } else if (!npp
.isBogus()) {
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
;
401 if (!properties
.negativeSuffix
.isBogus()) {
403 } else if (!nsp
.isBogus()) {
406 // UTS 35: Default negative prefix is the positive prefix.
407 negSuffix
= psp
.isBogus() ? u
"" : psp
;
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
));
419 char16_t PropertiesAffixPatternProvider::charAt(int flags
, int i
) const {
420 return getStringInternal(flags
).charAt(i
);
423 int PropertiesAffixPatternProvider::length(int flags
) const {
424 return getStringInternal(flags
).length();
427 UnicodeString
PropertiesAffixPatternProvider::getString(int32_t flags
) const {
428 return getStringInternal(flags
);
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
) {
438 } else if (negative
) {
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
);
452 bool PropertiesAffixPatternProvider::hasNegativeSubpattern() const {
454 (negSuffix
!= posSuffix
) ||
455 negPrefix
.tempSubString(1) != posPrefix
||
456 negPrefix
.charAt(0) != u
'-'
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
);
466 bool PropertiesAffixPatternProvider::hasCurrencySign() const {
467 return isCurrencyPattern
;
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
);
477 bool PropertiesAffixPatternProvider::hasBody() const {
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.
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(
496 IGNORE_ROUNDING_NEVER
,
498 affixesByPlural
[plural
].setTo(pluralProperties
, status
);
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
);
507 int32_t CurrencyPluralInfoAffixProvider::length(int32_t flags
) const {
508 int32_t pluralOrdinal
= (flags
& AFFIX_PLURAL_MASK
);
509 return affixesByPlural
[pluralOrdinal
].length(flags
);
512 UnicodeString
CurrencyPluralInfoAffixProvider::getString(int32_t flags
) const {
513 int32_t pluralOrdinal
= (flags
& AFFIX_PLURAL_MASK
);
514 return affixesByPlural
[pluralOrdinal
].getString(flags
);
517 bool CurrencyPluralInfoAffixProvider::positiveHasPlusSign() const {
518 return affixesByPlural
[StandardPlural::OTHER
].positiveHasPlusSign();
521 bool CurrencyPluralInfoAffixProvider::hasNegativeSubpattern() const {
522 return affixesByPlural
[StandardPlural::OTHER
].hasNegativeSubpattern();
525 bool CurrencyPluralInfoAffixProvider::negativeHasMinusSign() const {
526 return affixesByPlural
[StandardPlural::OTHER
].negativeHasMinusSign();
529 bool CurrencyPluralInfoAffixProvider::hasCurrencySign() const {
530 return affixesByPlural
[StandardPlural::OTHER
].hasCurrencySign();
533 bool CurrencyPluralInfoAffixProvider::containsSymbolType(AffixPatternType type
, UErrorCode
& status
) const {
534 return affixesByPlural
[StandardPlural::OTHER
].containsSymbolType(type
, status
);
537 bool CurrencyPluralInfoAffixProvider::hasBody() const {
538 return affixesByPlural
[StandardPlural::OTHER
].hasBody();
542 #endif /* #if !UCONFIG_NO_FORMATTING */