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 NumberStringBuilder
& 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
, int8_t signum
,
85 StandardPlural::Form plural
,
86 NumberStringBuilder
& 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
, NumberStringBuilder
& 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(int8_t signum
, StandardPlural::Form plural
,
133 NumberStringBuilder
& 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(int8_t signum
, StandardPlural::Form plural
,
144 NumberStringBuilder
& 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 fMicros
.symbols
= (ns
!= nullptr)?
219 new DecimalFormatSymbols(macros
.locale
, *ns
, status
):
220 new DecimalFormatSymbols(macros
.locale
, status
);
221 // Give ownership to the NumberFormatterImpl.
222 fSymbols
.adoptInstead(fMicros
.symbols
);
224 // Resolve nsName and save (Apple rdar://51672521)
225 if (nsName
== nullptr && U_SUCCESS(status
)) {
226 nsName
= fMicros
.symbols
->getNSName();
228 if (nsName
== nullptr || nsName
[0] == 0) {
231 uprv_strncpy(fMicros
.nsName
, nsName
, 8);
232 fMicros
.nsName
[8] = 0; // guarantee NUL-terminated
234 // Load and parse the pattern string. It is used for grouping sizes and affixes only.
235 // If we are formatting currency, check for a currency-specific pattern.
236 const char16_t* pattern
= nullptr;
238 CurrencyFormatInfoResult info
= getCurrencyFormatInfo(
239 macros
.locale
, currency
.getSubtype(), status
);
241 pattern
= info
.pattern
;
242 // It's clunky to clone an object here, but this code is not frequently executed.
243 auto* symbols
= new DecimalFormatSymbols(*fMicros
.symbols
);
244 fMicros
.symbols
= symbols
;
245 fSymbols
.adoptInstead(symbols
);
247 DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol
,
248 UnicodeString(info
.decimalSeparator
),
251 DecimalFormatSymbols::ENumberFormatSymbol::kMonetaryGroupingSeparatorSymbol
,
252 UnicodeString(info
.groupingSeparator
),
256 if (pattern
== nullptr) {
257 CldrPatternStyle patternStyle
;
259 patternStyle
= CLDR_PATTERN_STYLE_DECIMAL
;
260 } else if (isPercent
|| isPermille
) {
261 patternStyle
= CLDR_PATTERN_STYLE_PERCENT
;
262 } else if (!isCurrency
|| unitWidth
== UNUM_UNIT_WIDTH_FULL_NAME
) {
263 patternStyle
= CLDR_PATTERN_STYLE_DECIMAL
;
264 } else if (isAccounting
) {
265 // NOTE: Although ACCOUNTING and ACCOUNTING_ALWAYS are only supported in currencies right now,
266 // the API contract allows us to add support to other units in the future.
267 patternStyle
= CLDR_PATTERN_STYLE_ACCOUNTING
;
269 patternStyle
= CLDR_PATTERN_STYLE_CURRENCY
;
271 pattern
= utils::getPatternForStyle(macros
.locale
, nsName
, patternStyle
, status
);
273 auto patternInfo
= new ParsedPatternInfo();
274 fPatternInfo
.adoptInstead(patternInfo
);
275 PatternParser::parseToPatternInfo(UnicodeString(pattern
), *patternInfo
, status
);
277 /////////////////////////////////////////////////////////////////////////////////////
278 /// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR ///
279 /////////////////////////////////////////////////////////////////////////////////////
282 if (macros
.scale
.isValid()) {
283 fMicros
.helpers
.multiplier
.setAndChain(macros
.scale
, chain
);
284 chain
= &fMicros
.helpers
.multiplier
;
289 if (!macros
.precision
.isBogus()) {
290 precision
= macros
.precision
;
291 } else if (macros
.notation
.fType
== Notation::NTN_COMPACT
) {
292 precision
= Precision::integer().withMinDigits(2);
293 } else if (isCurrency
) {
294 precision
= Precision::currency(UCURR_USAGE_STANDARD
);
296 precision
= Precision::maxFraction(6);
298 UNumberFormatRoundingMode roundingMode
;
299 if (macros
.roundingMode
!= kDefaultMode
) {
300 roundingMode
= macros
.roundingMode
;
302 // Temporary until ICU 64
303 roundingMode
= precision
.fRoundingMode
;
305 fMicros
.rounder
= {precision
, roundingMode
, currency
, status
};
308 if (!macros
.grouper
.isBogus()) {
309 fMicros
.grouping
= macros
.grouper
;
310 } else if (macros
.notation
.fType
== Notation::NTN_COMPACT
) {
311 // Compact notation uses minGrouping by default since ICU 59
312 fMicros
.grouping
= Grouper::forStrategy(UNUM_GROUPING_MIN2
);
314 fMicros
.grouping
= Grouper::forStrategy(UNUM_GROUPING_AUTO
);
316 fMicros
.grouping
.setLocaleData(*fPatternInfo
, macros
.locale
);
319 if (!macros
.padder
.isBogus()) {
320 fMicros
.padding
= macros
.padder
;
322 fMicros
.padding
= Padder::none();
326 if (!macros
.integerWidth
.isBogus()) {
327 fMicros
.integerWidth
= macros
.integerWidth
;
329 fMicros
.integerWidth
= IntegerWidth::standard();
333 if (macros
.sign
!= UNUM_SIGN_COUNT
) {
334 fMicros
.sign
= macros
.sign
;
336 fMicros
.sign
= UNUM_SIGN_AUTO
;
339 // Decimal mark display
340 if (macros
.decimal
!= UNUM_DECIMAL_SEPARATOR_COUNT
) {
341 fMicros
.decimal
= macros
.decimal
;
343 fMicros
.decimal
= UNUM_DECIMAL_SEPARATOR_AUTO
;
346 // Use monetary separator symbols
347 fMicros
.useCurrency
= isCurrency
;
349 // Inner modifier (scientific notation)
350 if (macros
.notation
.fType
== Notation::NTN_SCIENTIFIC
) {
351 fScientificHandler
.adoptInstead(new ScientificHandler(¯os
.notation
, fMicros
.symbols
, chain
));
352 chain
= fScientificHandler
.getAlias();
354 // No inner modifier required
355 fMicros
.modInner
= &fMicros
.helpers
.emptyStrongModifier
;
358 // Middle modifier (patterns, positive/negative, currency symbols, percent)
359 auto patternModifier
= new MutablePatternModifier(false);
360 fPatternModifier
.adoptInstead(patternModifier
);
361 patternModifier
->setPatternInfo(
362 macros
.affixProvider
!= nullptr ? macros
.affixProvider
363 : static_cast<const AffixPatternProvider
*>(fPatternInfo
.getAlias()),
365 patternModifier
->setPatternAttributes(fMicros
.sign
, isPermille
);
366 if (patternModifier
->needsPlurals()) {
367 patternModifier
->setSymbols(
371 resolvePluralRules(macros
.rules
, macros
.locale
, status
));
373 patternModifier
->setSymbols(fMicros
.symbols
, currencySymbols
, unitWidth
, nullptr);
376 fImmutablePatternModifier
.adoptInstead(patternModifier
->createImmutableAndChain(chain
, status
));
377 chain
= fImmutablePatternModifier
.getAlias();
379 patternModifier
->addToChain(chain
);
380 chain
= patternModifier
;
383 // Outer modifier (CLDR units and currency long names)
385 fLongNameHandler
.adoptInstead(
386 LongNameHandler::forMeasureUnit(
391 resolvePluralRules(macros
.rules
, macros
.locale
, status
),
394 chain
= fLongNameHandler
.getAlias();
395 } else if (isCurrency
&& unitWidth
== UNUM_UNIT_WIDTH_FULL_NAME
) {
396 fLongNameHandler
.adoptInstead(
397 LongNameHandler::forCurrencyLongNames(
400 resolvePluralRules(macros
.rules
, macros
.locale
, status
),
403 chain
= fLongNameHandler
.getAlias();
405 // No outer modifier required
406 fMicros
.modOuter
= &fMicros
.helpers
.emptyWeakModifier
;
410 // NOTE: Compact notation can (but might not) override the middle modifier and rounding.
411 // It therefore needs to go at the end of the chain.
412 if (macros
.notation
.fType
== Notation::NTN_COMPACT
) {
413 CompactType compactType
= (isCurrency
&& unitWidth
!= UNUM_UNIT_WIDTH_FULL_NAME
)
414 ? CompactType::TYPE_CURRENCY
: CompactType::TYPE_DECIMAL
;
415 fCompactHandler
.adoptInstead(
417 macros
.notation
.fUnion
.compactStyle
,
421 resolvePluralRules(macros
.rules
, macros
.locale
, status
),
422 safe
? patternModifier
: nullptr,
425 chain
= fCompactHandler
.getAlias();
432 NumberFormatterImpl::resolvePluralRules(const PluralRules
* rulesPtr
, const Locale
& locale
,
433 UErrorCode
& status
) {
434 if (rulesPtr
!= nullptr) {
437 // Lazily create PluralRules
438 if (fRules
.isNull()) {
439 fRules
.adoptInstead(PluralRules::forLocale(locale
, status
));
441 return fRules
.getAlias();
444 int32_t NumberFormatterImpl::writeAffixes(const MicroProps
& micros
, NumberStringBuilder
& string
,
445 int32_t start
, int32_t end
, UErrorCode
& status
) {
446 // Always apply the inner modifier (which is "strong").
447 int32_t length
= micros
.modInner
->apply(string
, start
, end
, status
);
448 if (micros
.padding
.isValid()) {
449 length
+= micros
.padding
450 .padAndApply(*micros
.modMiddle
, *micros
.modOuter
, string
, start
, length
+ end
, status
);
452 length
+= micros
.modMiddle
->apply(string
, start
, length
+ end
, status
);
453 length
+= micros
.modOuter
->apply(string
, start
, length
+ end
, status
);
458 int32_t NumberFormatterImpl::writeNumber(const MicroProps
& micros
, DecimalQuantity
& quantity
,
459 NumberStringBuilder
& string
, int32_t index
,
460 UErrorCode
& status
) {
462 if (quantity
.isInfinite()) {
463 length
+= string
.insert(
465 micros
.symbols
->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kInfinitySymbol
),
469 } else if (quantity
.isNaN()) {
470 length
+= string
.insert(
472 micros
.symbols
->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kNaNSymbol
),
477 // Add the integer digits
478 length
+= writeIntegerDigits(micros
, quantity
, string
, length
+ index
, status
);
480 // Add the decimal point
481 if (quantity
.getLowerDisplayMagnitude() < 0 || micros
.decimal
== UNUM_DECIMAL_SEPARATOR_ALWAYS
) {
482 length
+= string
.insert(
484 micros
.useCurrency
? micros
.symbols
->getSymbol(
485 DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol
) : micros
488 DecimalFormatSymbols::ENumberFormatSymbol::kDecimalSeparatorSymbol
),
489 UNUM_DECIMAL_SEPARATOR_FIELD
,
493 // Add the fraction digits
494 length
+= writeFractionDigits(micros
, quantity
, string
, length
+ index
, status
);
500 int32_t NumberFormatterImpl::writeIntegerDigits(const MicroProps
& micros
, DecimalQuantity
& quantity
,
501 NumberStringBuilder
& string
, int32_t index
,
502 UErrorCode
& status
) {
504 int integerCount
= quantity
.getUpperDisplayMagnitude() + 1;
505 for (int i
= 0; i
< integerCount
; i
++) {
506 // Add grouping separator
507 if (micros
.grouping
.groupAtPosition(i
, quantity
)) {
508 length
+= string
.insert(
510 micros
.useCurrency
? micros
.symbols
->getSymbol(
511 DecimalFormatSymbols::ENumberFormatSymbol::kMonetaryGroupingSeparatorSymbol
)
512 : micros
.symbols
->getSymbol(
513 DecimalFormatSymbols::ENumberFormatSymbol::kGroupingSeparatorSymbol
),
514 UNUM_GROUPING_SEPARATOR_FIELD
,
518 // Get and append the next digit value
519 int8_t nextDigit
= quantity
.getDigit(i
);
520 length
+= utils::insertDigitFromSymbols(
521 string
, index
, nextDigit
, *micros
.symbols
, UNUM_INTEGER_FIELD
, status
);
526 int32_t NumberFormatterImpl::writeFractionDigits(const MicroProps
& micros
, DecimalQuantity
& quantity
,
527 NumberStringBuilder
& string
, int32_t index
,
528 UErrorCode
& status
) {
530 int fractionCount
= -quantity
.getLowerDisplayMagnitude();
531 for (int i
= 0; i
< fractionCount
; i
++) {
532 // Get and append the next digit value
533 int8_t nextDigit
= quantity
.getDigit(-i
- 1);
534 length
+= utils::insertDigitFromSymbols(
535 string
, length
+ index
, nextDigit
, *micros
.symbols
, UNUM_FRACTION_FIELD
, status
);
540 #endif /* #if !UCONFIG_NO_FORMATTING */