1 // © 2017 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
9 #include "unicode/ures.h"
12 #include "number_formatimpl.h"
13 #include "unicode/numfmt.h"
14 #include "number_patternstring.h"
15 #include "number_utils.h"
16 #include "unicode/numberformatter.h"
17 #include "unicode/dcfmtsym.h"
18 #include "number_scientific.h"
19 #include "number_compact.h"
24 using namespace icu::number
;
25 using namespace icu::number::impl
;
29 struct CurrencyFormatInfoResult
{
31 const char16_t* pattern
;
32 const char16_t* decimalSeparator
;
33 const char16_t* groupingSeparator
;
36 CurrencyFormatInfoResult
37 getCurrencyFormatInfo(const Locale
& locale
, const char* isoCode
, UErrorCode
& status
) {
38 // TODO: Load this data in a centralized location like ICU4J?
39 // TODO: Move this into the CurrencySymbols class?
40 // TODO: Parts of this same data are loaded in dcfmtsym.cpp; should clean up.
41 CurrencyFormatInfoResult result
= {false, nullptr, nullptr, nullptr};
42 if (U_FAILURE(status
)) { return result
; }
44 key
.append("Currencies/", status
);
45 key
.append(isoCode
, status
);
46 UErrorCode localStatus
= status
;
47 LocalUResourceBundlePointer
bundle(ures_open(U_ICUDATA_CURR
, locale
.getName(), &localStatus
));
48 ures_getByKeyWithFallback(bundle
.getAlias(), key
.data(), bundle
.getAlias(), &localStatus
);
49 if (U_SUCCESS(localStatus
) &&
50 ures_getSize(bundle
.getAlias()) > 2) { // the length is 3 if more data is present
51 ures_getByIndex(bundle
.getAlias(), 2, bundle
.getAlias(), &localStatus
);
54 result
.pattern
= ures_getStringByIndex(bundle
.getAlias(), 0, &dummy
, &localStatus
);
55 result
.decimalSeparator
= ures_getStringByIndex(bundle
.getAlias(), 1, &dummy
, &localStatus
);
56 result
.groupingSeparator
= ures_getStringByIndex(bundle
.getAlias(), 2, &dummy
, &localStatus
);
58 } else if (localStatus
!= U_MISSING_RESOURCE_ERROR
) {
67 MicroPropsGenerator::~MicroPropsGenerator() = default;
70 NumberFormatterImpl::NumberFormatterImpl(const MacroProps
& macros
, UErrorCode
& status
)
71 : NumberFormatterImpl(macros
, true, status
) {
74 int32_t NumberFormatterImpl::formatStatic(const MacroProps
& macros
, DecimalQuantity
& inValue
,
75 FormattedStringBuilder
& outString
, UErrorCode
& status
) {
76 NumberFormatterImpl
impl(macros
, false, status
);
77 MicroProps
& micros
= impl
.preProcessUnsafe(inValue
, status
);
78 if (U_FAILURE(status
)) { return 0; }
79 int32_t length
= writeNumber(micros
, inValue
, outString
, 0, status
);
80 length
+= writeAffixes(micros
, outString
, 0, length
, status
);
84 int32_t NumberFormatterImpl::getPrefixSuffixStatic(const MacroProps
& macros
, Signum signum
,
85 StandardPlural::Form plural
,
86 FormattedStringBuilder
& outString
, UErrorCode
& status
) {
87 NumberFormatterImpl
impl(macros
, false, status
);
88 return impl
.getPrefixSuffixUnsafe(signum
, plural
, outString
, status
);
91 // NOTE: C++ SPECIFIC DIFFERENCE FROM JAVA:
92 // The "safe" apply method uses a new MicroProps. In the MicroPropsGenerator, fMicros is copied into the new instance.
93 // The "unsafe" method simply re-uses fMicros, eliminating the extra copy operation.
94 // See MicroProps::processQuantity() for details.
96 int32_t NumberFormatterImpl::format(DecimalQuantity
& inValue
, FormattedStringBuilder
& outString
,
97 UErrorCode
& status
) const {
99 preProcess(inValue
, micros
, status
);
100 if (U_FAILURE(status
)) { return 0; }
101 int32_t length
= writeNumber(micros
, inValue
, outString
, 0, status
);
102 length
+= writeAffixes(micros
, outString
, 0, length
, status
);
106 void NumberFormatterImpl::preProcess(DecimalQuantity
& inValue
, MicroProps
& microsOut
,
107 UErrorCode
& status
) const {
108 if (U_FAILURE(status
)) { return; }
109 if (fMicroPropsGenerator
== nullptr) {
110 status
= U_INTERNAL_PROGRAM_ERROR
;
113 fMicroPropsGenerator
->processQuantity(inValue
, microsOut
, status
);
114 microsOut
.rounder
.apply(inValue
, status
);
115 microsOut
.integerWidth
.apply(inValue
, status
);
118 MicroProps
& NumberFormatterImpl::preProcessUnsafe(DecimalQuantity
& inValue
, UErrorCode
& status
) {
119 if (U_FAILURE(status
)) {
120 return fMicros
; // must always return a value
122 if (fMicroPropsGenerator
== nullptr) {
123 status
= U_INTERNAL_PROGRAM_ERROR
;
124 return fMicros
; // must always return a value
126 fMicroPropsGenerator
->processQuantity(inValue
, fMicros
, status
);
127 fMicros
.rounder
.apply(inValue
, status
);
128 fMicros
.integerWidth
.apply(inValue
, status
);
132 int32_t NumberFormatterImpl::getPrefixSuffix(Signum signum
, StandardPlural::Form plural
,
133 FormattedStringBuilder
& outString
, UErrorCode
& status
) const {
134 if (U_FAILURE(status
)) { return 0; }
135 // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle, aka pattern modifier).
136 // Safe path: use fImmutablePatternModifier.
137 const Modifier
* modifier
= fImmutablePatternModifier
->getModifier(signum
, plural
);
138 modifier
->apply(outString
, 0, 0, status
);
139 if (U_FAILURE(status
)) { return 0; }
140 return modifier
->getPrefixLength();
143 int32_t NumberFormatterImpl::getPrefixSuffixUnsafe(Signum signum
, StandardPlural::Form plural
,
144 FormattedStringBuilder
& outString
, UErrorCode
& status
) {
145 if (U_FAILURE(status
)) { return 0; }
146 // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle, aka pattern modifier).
147 // Unsafe path: use fPatternModifier.
148 fPatternModifier
->setNumberProperties(signum
, plural
);
149 fPatternModifier
->apply(outString
, 0, 0, status
);
150 if (U_FAILURE(status
)) { return 0; }
151 return fPatternModifier
->getPrefixLength();
154 NumberFormatterImpl::NumberFormatterImpl(const MacroProps
& macros
, bool safe
, UErrorCode
& status
) {
155 fMicroPropsGenerator
= macrosToMicroGenerator(macros
, safe
, status
);
160 const MicroPropsGenerator
*
161 NumberFormatterImpl::macrosToMicroGenerator(const MacroProps
& macros
, bool safe
, UErrorCode
& status
) {
162 if (U_FAILURE(status
)) { return nullptr; }
163 const MicroPropsGenerator
* chain
= &fMicros
;
165 // Check that macros is error-free before continuing.
166 if (macros
.copyErrorTo(status
)) {
170 // TODO: Accept currency symbols from DecimalFormatSymbols?
172 // Pre-compute a few values for efficiency.
173 bool isCurrency
= utils::unitIsCurrency(macros
.unit
);
174 bool isNoUnit
= utils::unitIsNoUnit(macros
.unit
);
175 bool isPercent
= utils::unitIsPercent(macros
.unit
);
176 bool isPermille
= utils::unitIsPermille(macros
.unit
);
178 macros
.sign
== UNUM_SIGN_ACCOUNTING
|| macros
.sign
== UNUM_SIGN_ACCOUNTING_ALWAYS
||
179 macros
.sign
== UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO
;
180 CurrencyUnit
currency(u
"", status
);
182 currency
= CurrencyUnit(macros
.unit
, status
); // Restore CurrencyUnit from MeasureUnit
184 const CurrencySymbols
* currencySymbols
;
185 if (macros
.currencySymbols
!= nullptr) {
186 // Used by the DecimalFormat code path
187 currencySymbols
= macros
.currencySymbols
;
189 fWarehouse
.fCurrencySymbols
= {currency
, macros
.locale
, status
};
190 currencySymbols
= &fWarehouse
.fCurrencySymbols
;
192 UNumberUnitWidth unitWidth
= UNUM_UNIT_WIDTH_SHORT
;
193 if (macros
.unitWidth
!= UNUM_UNIT_WIDTH_COUNT
) {
194 unitWidth
= macros
.unitWidth
;
196 bool isCldrUnit
= !isCurrency
&& !isNoUnit
&&
197 (unitWidth
== UNUM_UNIT_WIDTH_FULL_NAME
|| !(isPercent
|| isPermille
));
199 // Select the numbering system.
200 const NumberingSystem
* ns
= nullptr;
201 if (macros
.symbols
.isNumberingSystem()) {
202 ns
= macros
.symbols
.getNumberingSystem();
204 // else there is no need to create a NumberingSystem object, it is only used
205 // for two things (Apple rdar://51672521):
206 // 1. Passing to new DecimalFormatSymbols; but if we do not have one, new
207 // DecimalFormat symbols can create one anyway so there is no need to do it
208 // ahead of time, and
209 // 2. Getting the nsName. But with a small DecimalFormatSymbols change (per
210 // rdar://51672521) we can get the name from DecimalFormatSymbols which we need
212 const char* nsName
= (ns
!= nullptr)? ns
->getName() : nullptr;
214 // Resolve the symbols. Do this here because currency may need to customize them.
215 if (macros
.symbols
.isDecimalFormatSymbols()) {
216 fMicros
.symbols
= macros
.symbols
.getDecimalFormatSymbols();
218 auto newSymbols
= (ns
!= nullptr)?
219 new DecimalFormatSymbols(macros
.locale
, *ns
, status
):
220 new DecimalFormatSymbols(macros
.locale
, status
);
221 if (newSymbols
== nullptr) {
222 status
= U_MEMORY_ALLOCATION_ERROR
;
225 fMicros
.symbols
= newSymbols
;
226 // Give ownership to the NumberFormatterImpl.
227 fSymbols
.adoptInstead(fMicros
.symbols
);
229 // Resolve nsName and save (Apple rdar://51672521)
230 if (nsName
== nullptr && U_SUCCESS(status
)) {
231 nsName
= fMicros
.symbols
->getNSName();
233 if (nsName
== nullptr || nsName
[0] == 0) {
236 uprv_strncpy(fMicros
.nsName
, nsName
, 8);
237 fMicros
.nsName
[8] = 0; // guarantee NUL-terminated
239 // Load and parse the pattern string. It is used for grouping sizes and affixes only.
240 // If we are formatting currency, check for a currency-specific pattern.
241 const char16_t* pattern
= nullptr;
243 CurrencyFormatInfoResult info
= getCurrencyFormatInfo(
244 macros
.locale
, currency
.getSubtype(), status
);
246 pattern
= info
.pattern
;
247 // It's clunky to clone an object here, but this code is not frequently executed.
248 auto symbols
= new DecimalFormatSymbols(*fMicros
.symbols
);
249 if (symbols
== nullptr) {
250 status
= U_MEMORY_ALLOCATION_ERROR
;
253 fMicros
.symbols
= symbols
;
254 fSymbols
.adoptInstead(symbols
);
256 DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol
,
257 UnicodeString(info
.decimalSeparator
),
260 DecimalFormatSymbols::ENumberFormatSymbol::kMonetaryGroupingSeparatorSymbol
,
261 UnicodeString(info
.groupingSeparator
),
265 if (pattern
== nullptr) {
266 CldrPatternStyle patternStyle
;
268 patternStyle
= CLDR_PATTERN_STYLE_DECIMAL
;
269 } else if (isPercent
|| isPermille
) {
270 patternStyle
= CLDR_PATTERN_STYLE_PERCENT
;
271 } else if (!isCurrency
|| unitWidth
== UNUM_UNIT_WIDTH_FULL_NAME
) {
272 patternStyle
= CLDR_PATTERN_STYLE_DECIMAL
;
273 } else if (isAccounting
) {
274 // NOTE: Although ACCOUNTING and ACCOUNTING_ALWAYS are only supported in currencies right now,
275 // the API contract allows us to add support to other units in the future.
276 patternStyle
= CLDR_PATTERN_STYLE_ACCOUNTING
;
278 patternStyle
= CLDR_PATTERN_STYLE_CURRENCY
;
280 pattern
= utils::getPatternForStyle(macros
.locale
, nsName
, patternStyle
, status
);
282 auto patternInfo
= new ParsedPatternInfo();
283 if (patternInfo
== nullptr) {
284 status
= U_MEMORY_ALLOCATION_ERROR
;
287 fPatternInfo
.adoptInstead(patternInfo
);
288 PatternParser::parseToPatternInfo(UnicodeString(pattern
), *patternInfo
, status
);
290 /////////////////////////////////////////////////////////////////////////////////////
291 /// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR ///
292 /////////////////////////////////////////////////////////////////////////////////////
295 if (macros
.scale
.isValid()) {
296 fMicros
.helpers
.multiplier
.setAndChain(macros
.scale
, chain
);
297 chain
= &fMicros
.helpers
.multiplier
;
302 if (!macros
.precision
.isBogus()) {
303 precision
= macros
.precision
;
304 } else if (macros
.notation
.fType
== Notation::NTN_COMPACT
) {
305 precision
= Precision::integer().withMinDigits(2);
306 } else if (isCurrency
) {
307 precision
= Precision::currency(UCURR_USAGE_STANDARD
);
309 precision
= Precision::maxFraction(6);
311 UNumberFormatRoundingMode roundingMode
;
312 if (macros
.roundingMode
!= kDefaultMode
) {
313 roundingMode
= macros
.roundingMode
;
315 // Temporary until ICU 64
316 roundingMode
= precision
.fRoundingMode
;
318 fMicros
.rounder
= {precision
, roundingMode
, currency
, status
};
321 if (!macros
.grouper
.isBogus()) {
322 fMicros
.grouping
= macros
.grouper
;
323 } else if (macros
.notation
.fType
== Notation::NTN_COMPACT
) {
324 // Compact notation uses minGrouping by default since ICU 59
325 fMicros
.grouping
= Grouper::forStrategy(UNUM_GROUPING_MIN2
);
327 fMicros
.grouping
= Grouper::forStrategy(UNUM_GROUPING_AUTO
);
329 fMicros
.grouping
.setLocaleData(*fPatternInfo
, macros
.locale
);
332 if (!macros
.padder
.isBogus()) {
333 fMicros
.padding
= macros
.padder
;
335 fMicros
.padding
= Padder::none();
339 if (!macros
.integerWidth
.isBogus()) {
340 fMicros
.integerWidth
= macros
.integerWidth
;
342 fMicros
.integerWidth
= IntegerWidth::standard();
346 if (macros
.sign
!= UNUM_SIGN_COUNT
) {
347 fMicros
.sign
= macros
.sign
;
349 fMicros
.sign
= UNUM_SIGN_AUTO
;
352 // Decimal mark display
353 if (macros
.decimal
!= UNUM_DECIMAL_SEPARATOR_COUNT
) {
354 fMicros
.decimal
= macros
.decimal
;
356 fMicros
.decimal
= UNUM_DECIMAL_SEPARATOR_AUTO
;
359 // Use monetary separator symbols
360 fMicros
.useCurrency
= isCurrency
;
362 // Inner modifier (scientific notation)
363 if (macros
.notation
.fType
== Notation::NTN_SCIENTIFIC
) {
364 auto newScientificHandler
= new ScientificHandler(¯os
.notation
, fMicros
.symbols
, chain
);
365 if (newScientificHandler
== nullptr) {
366 status
= U_MEMORY_ALLOCATION_ERROR
;
369 fScientificHandler
.adoptInstead(newScientificHandler
);
370 chain
= fScientificHandler
.getAlias();
372 // No inner modifier required
373 fMicros
.modInner
= &fMicros
.helpers
.emptyStrongModifier
;
376 // Middle modifier (patterns, positive/negative, currency symbols, percent)
377 auto patternModifier
= new MutablePatternModifier(false);
378 if (patternModifier
== nullptr) {
379 status
= U_MEMORY_ALLOCATION_ERROR
;
382 fPatternModifier
.adoptInstead(patternModifier
);
383 patternModifier
->setPatternInfo(
384 macros
.affixProvider
!= nullptr ? macros
.affixProvider
385 : static_cast<const AffixPatternProvider
*>(fPatternInfo
.getAlias()),
387 patternModifier
->setPatternAttributes(fMicros
.sign
, isPermille
);
388 if (patternModifier
->needsPlurals()) {
389 patternModifier
->setSymbols(
393 resolvePluralRules(macros
.rules
, macros
.locale
, status
));
395 patternModifier
->setSymbols(fMicros
.symbols
, currencySymbols
, unitWidth
, nullptr);
398 fImmutablePatternModifier
.adoptInstead(patternModifier
->createImmutableAndChain(chain
, status
));
399 chain
= fImmutablePatternModifier
.getAlias();
401 patternModifier
->addToChain(chain
);
402 chain
= patternModifier
;
405 // Outer modifier (CLDR units and currency long names)
407 fLongNameHandler
.adoptInstead(
408 LongNameHandler::forMeasureUnit(
413 resolvePluralRules(macros
.rules
, macros
.locale
, status
),
416 chain
= fLongNameHandler
.getAlias();
417 } else if (isCurrency
&& unitWidth
== UNUM_UNIT_WIDTH_FULL_NAME
) {
418 fLongNameHandler
.adoptInstead(
419 LongNameHandler::forCurrencyLongNames(
422 resolvePluralRules(macros
.rules
, macros
.locale
, status
),
425 chain
= fLongNameHandler
.getAlias();
427 // No outer modifier required
428 fMicros
.modOuter
= &fMicros
.helpers
.emptyWeakModifier
;
432 // NOTE: Compact notation can (but might not) override the middle modifier and rounding.
433 // It therefore needs to go at the end of the chain.
434 if (macros
.notation
.fType
== Notation::NTN_COMPACT
) {
435 CompactType compactType
= (isCurrency
&& unitWidth
!= UNUM_UNIT_WIDTH_FULL_NAME
)
436 ? CompactType::TYPE_CURRENCY
: CompactType::TYPE_DECIMAL
;
437 auto newCompactHandler
= new CompactHandler(
438 macros
.notation
.fUnion
.compactStyle
,
442 resolvePluralRules(macros
.rules
, macros
.locale
, status
),
443 safe
? patternModifier
: nullptr,
446 if (newCompactHandler
== nullptr) {
447 status
= U_MEMORY_ALLOCATION_ERROR
;
450 fCompactHandler
.adoptInstead(newCompactHandler
);
451 chain
= fCompactHandler
.getAlias();
458 NumberFormatterImpl::resolvePluralRules(const PluralRules
* rulesPtr
, const Locale
& locale
,
459 UErrorCode
& status
) {
460 if (rulesPtr
!= nullptr) {
463 // Lazily create PluralRules
464 if (fRules
.isNull()) {
465 fRules
.adoptInstead(PluralRules::forLocale(locale
, status
));
467 return fRules
.getAlias();
470 int32_t NumberFormatterImpl::writeAffixes(const MicroProps
& micros
, FormattedStringBuilder
& string
,
471 int32_t start
, int32_t end
, UErrorCode
& status
) {
472 // Always apply the inner modifier (which is "strong").
473 int32_t length
= micros
.modInner
->apply(string
, start
, end
, status
);
474 if (micros
.padding
.isValid()) {
475 length
+= micros
.padding
476 .padAndApply(*micros
.modMiddle
, *micros
.modOuter
, string
, start
, length
+ end
, status
);
478 length
+= micros
.modMiddle
->apply(string
, start
, length
+ end
, status
);
479 length
+= micros
.modOuter
->apply(string
, start
, length
+ end
, status
);
484 int32_t NumberFormatterImpl::writeNumber(const MicroProps
& micros
, DecimalQuantity
& quantity
,
485 FormattedStringBuilder
& string
, int32_t index
,
486 UErrorCode
& status
) {
488 if (quantity
.isInfinite()) {
489 length
+= string
.insert(
491 micros
.symbols
->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kInfinitySymbol
),
495 } else if (quantity
.isNaN()) {
496 length
+= string
.insert(
498 micros
.symbols
->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kNaNSymbol
),
503 // Add the integer digits
504 length
+= writeIntegerDigits(micros
, quantity
, string
, length
+ index
, status
);
506 // Add the decimal point
507 if (quantity
.getLowerDisplayMagnitude() < 0 || micros
.decimal
== UNUM_DECIMAL_SEPARATOR_ALWAYS
) {
508 length
+= string
.insert(
510 micros
.useCurrency
? micros
.symbols
->getSymbol(
511 DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol
) : micros
514 DecimalFormatSymbols::ENumberFormatSymbol::kDecimalSeparatorSymbol
),
515 UNUM_DECIMAL_SEPARATOR_FIELD
,
519 // Add the fraction digits
520 length
+= writeFractionDigits(micros
, quantity
, string
, length
+ index
, status
);
523 // Force output of the digit for value 0
524 length
+= utils::insertDigitFromSymbols(
525 string
, index
, 0, *micros
.symbols
, UNUM_INTEGER_FIELD
, status
);
532 int32_t NumberFormatterImpl::writeIntegerDigits(const MicroProps
& micros
, DecimalQuantity
& quantity
,
533 FormattedStringBuilder
& string
, int32_t index
,
534 UErrorCode
& status
) {
536 int integerCount
= quantity
.getUpperDisplayMagnitude() + 1;
537 for (int i
= 0; i
< integerCount
; i
++) {
538 // Add grouping separator
539 if (micros
.grouping
.groupAtPosition(i
, quantity
)) {
540 length
+= string
.insert(
542 micros
.useCurrency
? micros
.symbols
->getSymbol(
543 DecimalFormatSymbols::ENumberFormatSymbol::kMonetaryGroupingSeparatorSymbol
)
544 : micros
.symbols
->getSymbol(
545 DecimalFormatSymbols::ENumberFormatSymbol::kGroupingSeparatorSymbol
),
546 UNUM_GROUPING_SEPARATOR_FIELD
,
550 // Get and append the next digit value
551 int8_t nextDigit
= quantity
.getDigit(i
);
552 length
+= utils::insertDigitFromSymbols(
553 string
, index
, nextDigit
, *micros
.symbols
, UNUM_INTEGER_FIELD
, status
);
558 int32_t NumberFormatterImpl::writeFractionDigits(const MicroProps
& micros
, DecimalQuantity
& quantity
,
559 FormattedStringBuilder
& string
, int32_t index
,
560 UErrorCode
& status
) {
562 int fractionCount
= -quantity
.getLowerDisplayMagnitude();
563 for (int i
= 0; i
< fractionCount
; i
++) {
564 // Get and append the next digit value
565 int8_t nextDigit
= quantity
.getDigit(-i
- 1);
566 length
+= utils::insertDigitFromSymbols(
567 string
, length
+ index
, nextDigit
, *micros
.symbols
, UNUM_FRACTION_FIELD
, status
);
572 #endif /* #if !UCONFIG_NO_FORMATTING */