1// © 2017 and later: Unicode, Inc. and others.
2// License & terms of use: http://www.unicode.org/copyright.html
3
4#include "unicode/utypes.h"
5
6#if !UCONFIG_NO_FORMATTING
7
8#include "cstring.h"
9#include "unicode/ures.h"
10#include "uresimp.h"
11#include "charstr.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"
20#include "uresimp.h"
21#include "ureslocs.h"
22
23using namespace icu;
24using namespace icu::number;
25using namespace icu::number::impl;
26
27
28MicroPropsGenerator::~MicroPropsGenerator() = default;
29
30
31NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, UErrorCode& status)
32 : NumberFormatterImpl(macros, true, status) {
33}
34
35int32_t NumberFormatterImpl::formatStatic(const MacroProps& macros, DecimalQuantity& inValue,
36 FormattedStringBuilder& outString, UErrorCode& status) {
37 NumberFormatterImpl impl(macros, false, status);
38 MicroProps& micros = impl.preProcessUnsafe(inValue, status);
39 if (U_FAILURE(status)) { return 0; }
40 int32_t length = writeNumber(micros, inValue, outString, 0, status);
41 length += writeAffixes(micros, outString, 0, length, status);
42 return length;
43}
44
45int32_t NumberFormatterImpl::getPrefixSuffixStatic(const MacroProps& macros, Signum signum,
46 StandardPlural::Form plural,
47 FormattedStringBuilder& outString, UErrorCode& status) {
48 NumberFormatterImpl impl(macros, false, status);
49 return impl.getPrefixSuffixUnsafe(signum, plural, outString, status);
50}
51
52// NOTE: C++ SPECIFIC DIFFERENCE FROM JAVA:
53// The "safe" apply method uses a new MicroProps. In the MicroPropsGenerator, fMicros is copied into the new instance.
54// The "unsafe" method simply re-uses fMicros, eliminating the extra copy operation.
55// See MicroProps::processQuantity() for details.
56
57int32_t NumberFormatterImpl::format(DecimalQuantity& inValue, FormattedStringBuilder& outString,
58 UErrorCode& status) const {
59 MicroProps micros;
60 preProcess(inValue, micros, status);
61 if (U_FAILURE(status)) { return 0; }
62 int32_t length = writeNumber(micros, inValue, outString, 0, status);
63 length += writeAffixes(micros, outString, 0, length, status);
64 return length;
65}
66
67void NumberFormatterImpl::preProcess(DecimalQuantity& inValue, MicroProps& microsOut,
68 UErrorCode& status) const {
69 if (U_FAILURE(status)) { return; }
70 if (fMicroPropsGenerator == nullptr) {
71 status = U_INTERNAL_PROGRAM_ERROR;
72 return;
73 }
74 fMicroPropsGenerator->processQuantity(inValue, microsOut, status);
75 microsOut.integerWidth.apply(inValue, status);
76}
77
78MicroProps& NumberFormatterImpl::preProcessUnsafe(DecimalQuantity& inValue, UErrorCode& status) {
79 if (U_FAILURE(status)) {
80 return fMicros; // must always return a value
81 }
82 if (fMicroPropsGenerator == nullptr) {
83 status = U_INTERNAL_PROGRAM_ERROR;
84 return fMicros; // must always return a value
85 }
86 fMicroPropsGenerator->processQuantity(inValue, fMicros, status);
87 fMicros.integerWidth.apply(inValue, status);
88 return fMicros;
89}
90
91int32_t NumberFormatterImpl::getPrefixSuffix(Signum signum, StandardPlural::Form plural,
92 FormattedStringBuilder& outString, UErrorCode& status) const {
93 if (U_FAILURE(status)) { return 0; }
94 // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle, aka pattern modifier).
95 // Safe path: use fImmutablePatternModifier.
96 const Modifier* modifier = fImmutablePatternModifier->getModifier(signum, plural);
97 modifier->apply(outString, 0, 0, status);
98 if (U_FAILURE(status)) { return 0; }
99 return modifier->getPrefixLength();
100}
101
102int32_t NumberFormatterImpl::getPrefixSuffixUnsafe(Signum signum, StandardPlural::Form plural,
103 FormattedStringBuilder& outString, UErrorCode& status) {
104 if (U_FAILURE(status)) { return 0; }
105 // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle, aka pattern modifier).
106 // Unsafe path: use fPatternModifier.
107 fPatternModifier->setNumberProperties(signum, plural);
108 fPatternModifier->apply(outString, 0, 0, status);
109 if (U_FAILURE(status)) { return 0; }
110 return fPatternModifier->getPrefixLength();
111}
112
113NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, bool safe, UErrorCode& status) {
114 fMicroPropsGenerator = macrosToMicroGenerator(macros, safe, status);
115}
116
117//////////
118
119const MicroPropsGenerator*
120NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, UErrorCode& status) {
121 if (U_FAILURE(status)) { return nullptr; }
122 const MicroPropsGenerator* chain = &fMicros;
123
124 // Check that macros is error-free before continuing.
125 if (macros.copyErrorTo(status)) {
126 return nullptr;
127 }
128
129 // TODO: Accept currency symbols from DecimalFormatSymbols?
130
131 // Pre-compute a few values for efficiency.
132 bool isCurrency = utils::unitIsCurrency(macros.unit);
133 bool isNoUnit = utils::unitIsNoUnit(macros.unit);
134 bool isPercent = utils::unitIsPercent(macros.unit);
135 bool isPermille = utils::unitIsPermille(macros.unit);
136 bool isAccounting =
137 macros.sign == UNUM_SIGN_ACCOUNTING || macros.sign == UNUM_SIGN_ACCOUNTING_ALWAYS ||
138 macros.sign == UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO;
139 CurrencyUnit currency(u"", status);
140 if (isCurrency) {
141 currency = CurrencyUnit(macros.unit, status); // Restore CurrencyUnit from MeasureUnit
142 }
143 UNumberUnitWidth unitWidth = UNUM_UNIT_WIDTH_SHORT;
144 if (macros.unitWidth != UNUM_UNIT_WIDTH_COUNT) {
145 unitWidth = macros.unitWidth;
146 }
147 bool isCldrUnit = !isCurrency && !isNoUnit &&
148 (unitWidth == UNUM_UNIT_WIDTH_FULL_NAME || !(isPercent || isPermille));
149
150 // Select the numbering system.
151 LocalPointer<const NumberingSystem> nsLocal;
152 const NumberingSystem* ns;
153 if (macros.symbols.isNumberingSystem()) {
154 ns = macros.symbols.getNumberingSystem();
155 } else {
156 // TODO: Is there a way to avoid creating the NumberingSystem object?
157 ns = NumberingSystem::createInstance(macros.locale, status);
158 // Give ownership to the function scope.
159 nsLocal.adoptInstead(ns);
160 }
161 const char* nsName = U_SUCCESS(status) ? ns->getName() : "latn";
162 uprv_strncpy(fMicros.nsName, nsName, 8);
163 fMicros.nsName[8] = 0; // guarantee NUL-terminated
164
165 // Resolve the symbols. Do this here because currency may need to customize them.
166 if (macros.symbols.isDecimalFormatSymbols()) {
167 fMicros.symbols = macros.symbols.getDecimalFormatSymbols();
168 } else {
169 LocalPointer<DecimalFormatSymbols> newSymbols(
170 new DecimalFormatSymbols(macros.locale, *ns, status), status);
171 if (U_FAILURE(status)) {
172 return nullptr;
173 }
174 if (isCurrency) {
175 newSymbols->setCurrency(currency.getISOCurrency(), status);
176 if (U_FAILURE(status)) {
177 return nullptr;
178 }
179 }
180 fMicros.symbols = newSymbols.getAlias();
181 fSymbols.adoptInstead(newSymbols.orphan());
182 }
183
184 // Load and parse the pattern string. It is used for grouping sizes and affixes only.
185 // If we are formatting currency, check for a currency-specific pattern.
186 const char16_t* pattern = nullptr;
187 if (isCurrency && fMicros.symbols->getCurrencyPattern() != nullptr) {
188 pattern = fMicros.symbols->getCurrencyPattern();
189 }
190 if (pattern == nullptr) {
191 CldrPatternStyle patternStyle;
192 if (isCldrUnit) {
193 patternStyle = CLDR_PATTERN_STYLE_DECIMAL;
194 } else if (isPercent || isPermille) {
195 patternStyle = CLDR_PATTERN_STYLE_PERCENT;
196 } else if (!isCurrency || unitWidth == UNUM_UNIT_WIDTH_FULL_NAME) {
197 patternStyle = CLDR_PATTERN_STYLE_DECIMAL;
198 } else if (isAccounting) {
199 // NOTE: Although ACCOUNTING and ACCOUNTING_ALWAYS are only supported in currencies right now,
200 // the API contract allows us to add support to other units in the future.
201 patternStyle = CLDR_PATTERN_STYLE_ACCOUNTING;
202 } else {
203 patternStyle = CLDR_PATTERN_STYLE_CURRENCY;
204 }
205 pattern = utils::getPatternForStyle(macros.locale, nsName, patternStyle, status);
206 if (U_FAILURE(status)) {
207 return nullptr;
208 }
209 }
210 auto patternInfo = new ParsedPatternInfo();
211 if (patternInfo == nullptr) {
212 status = U_MEMORY_ALLOCATION_ERROR;
213 return nullptr;
214 }
215 fPatternInfo.adoptInstead(patternInfo);
216 PatternParser::parseToPatternInfo(UnicodeString(pattern), *patternInfo, status);
217 if (U_FAILURE(status)) {
218 return nullptr;
219 }
220
221 /////////////////////////////////////////////////////////////////////////////////////
222 /// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR ///
223 /////////////////////////////////////////////////////////////////////////////////////
224
225 // Multiplier
226 if (macros.scale.isValid()) {
227 fMicros.helpers.multiplier.setAndChain(macros.scale, chain);
228 chain = &fMicros.helpers.multiplier;
229 }
230
231 // Rounding strategy
232 Precision precision;
233 if (!macros.precision.isBogus()) {
234 precision = macros.precision;
235 } else if (macros.notation.fType == Notation::NTN_COMPACT) {
236 precision = Precision::integer().withMinDigits(2);
237 } else if (isCurrency) {
238 precision = Precision::currency(UCURR_USAGE_STANDARD);
239 } else {
240 precision = Precision::maxFraction(6);
241 }
242 UNumberFormatRoundingMode roundingMode;
243 if (macros.roundingMode != kDefaultMode) {
244 roundingMode = macros.roundingMode;
245 } else {
246 // Temporary until ICU 64
247 roundingMode = precision.fRoundingMode;
248 }
249 fMicros.rounder = {precision, roundingMode, currency, status};
250 if (U_FAILURE(status)) {
251 return nullptr;
252 }
253
254 // Grouping strategy
255 if (!macros.grouper.isBogus()) {
256 fMicros.grouping = macros.grouper;
257 } else if (macros.notation.fType == Notation::NTN_COMPACT) {
258 // Compact notation uses minGrouping by default since ICU 59
259 fMicros.grouping = Grouper::forStrategy(UNUM_GROUPING_MIN2);
260 } else {
261 fMicros.grouping = Grouper::forStrategy(UNUM_GROUPING_AUTO);
262 }
263 fMicros.grouping.setLocaleData(*fPatternInfo, macros.locale);
264
265 // Padding strategy
266 if (!macros.padder.isBogus()) {
267 fMicros.padding = macros.padder;
268 } else {
269 fMicros.padding = Padder::none();
270 }
271
272 // Integer width
273 if (!macros.integerWidth.isBogus()) {
274 fMicros.integerWidth = macros.integerWidth;
275 } else {
276 fMicros.integerWidth = IntegerWidth::standard();
277 }
278
279 // Sign display
280 if (macros.sign != UNUM_SIGN_COUNT) {
281 fMicros.sign = macros.sign;
282 } else {
283 fMicros.sign = UNUM_SIGN_AUTO;
284 }
285
286 // Decimal mark display
287 if (macros.decimal != UNUM_DECIMAL_SEPARATOR_COUNT) {
288 fMicros.decimal = macros.decimal;
289 } else {
290 fMicros.decimal = UNUM_DECIMAL_SEPARATOR_AUTO;
291 }
292
293 // Use monetary separator symbols
294 fMicros.useCurrency = isCurrency;
295
296 // Inner modifier (scientific notation)
297 if (macros.notation.fType == Notation::NTN_SCIENTIFIC) {
298 auto newScientificHandler = new ScientificHandler(&macros.notation, fMicros.symbols, chain);
299 if (newScientificHandler == nullptr) {
300 status = U_MEMORY_ALLOCATION_ERROR;
301 return nullptr;
302 }
303 fScientificHandler.adoptInstead(newScientificHandler);
304 chain = fScientificHandler.getAlias();
305 } else {
306 // No inner modifier required
307 fMicros.modInner = &fMicros.helpers.emptyStrongModifier;
308 }
309
310 // Middle modifier (patterns, positive/negative, currency symbols, percent)
311 auto patternModifier = new MutablePatternModifier(false);
312 if (patternModifier == nullptr) {
313 status = U_MEMORY_ALLOCATION_ERROR;
314 return nullptr;
315 }
316 fPatternModifier.adoptInstead(patternModifier);
317 patternModifier->setPatternInfo(
318 macros.affixProvider != nullptr ? macros.affixProvider
319 : static_cast<const AffixPatternProvider*>(fPatternInfo.getAlias()),
320 kUndefinedField);
321 patternModifier->setPatternAttributes(fMicros.sign, isPermille);
322 if (patternModifier->needsPlurals()) {
323 patternModifier->setSymbols(
324 fMicros.symbols,
325 currency,
326 unitWidth,
327 resolvePluralRules(macros.rules, macros.locale, status),
328 status);
329 } else {
330 patternModifier->setSymbols(fMicros.symbols, currency, unitWidth, nullptr, status);
331 }
332 if (safe) {
333 fImmutablePatternModifier.adoptInstead(patternModifier->createImmutable(status));
334 }
335 if (U_FAILURE(status)) {
336 return nullptr;
337 }
338
339 // Outer modifier (CLDR units and currency long names)
340 if (isCldrUnit) {
341 fLongNameHandler.adoptInstead(
342 LongNameHandler::forMeasureUnit(
343 macros.locale,
344 macros.unit,
345 macros.perUnit,
346 unitWidth,
347 resolvePluralRules(macros.rules, macros.locale, status),
348 chain,
349 status));
350 chain = fLongNameHandler.getAlias();
351 } else if (isCurrency && unitWidth == UNUM_UNIT_WIDTH_FULL_NAME) {
352 fLongNameHandler.adoptInstead(
353 LongNameHandler::forCurrencyLongNames(
354 macros.locale,
355 currency,
356 resolvePluralRules(macros.rules, macros.locale, status),
357 chain,
358 status));
359 chain = fLongNameHandler.getAlias();
360 } else {
361 // No outer modifier required
362 fMicros.modOuter = &fMicros.helpers.emptyWeakModifier;
363 }
364 if (U_FAILURE(status)) {
365 return nullptr;
366 }
367
368 // Compact notation
369 if (macros.notation.fType == Notation::NTN_COMPACT) {
370 CompactType compactType = (isCurrency && unitWidth != UNUM_UNIT_WIDTH_FULL_NAME)
371 ? CompactType::TYPE_CURRENCY : CompactType::TYPE_DECIMAL;
372 auto newCompactHandler = new CompactHandler(
373 macros.notation.fUnion.compactStyle,
374 macros.locale,
375 nsName,
376 compactType,
377 resolvePluralRules(macros.rules, macros.locale, status),
378 patternModifier,
379 safe,
380 chain,
381 status);
382 if (newCompactHandler == nullptr) {
383 status = U_MEMORY_ALLOCATION_ERROR;
384 return nullptr;
385 }
386 fCompactHandler.adoptInstead(newCompactHandler);
387 chain = fCompactHandler.getAlias();
388 }
389 if (U_FAILURE(status)) {
390 return nullptr;
391 }
392
393 // Always add the pattern modifier as the last element of the chain.
394 if (safe) {
395 fImmutablePatternModifier->addToChain(chain);
396 chain = fImmutablePatternModifier.getAlias();
397 } else {
398 patternModifier->addToChain(chain);
399 chain = patternModifier;
400 }
401
402 return chain;
403}
404
405const PluralRules*
406NumberFormatterImpl::resolvePluralRules(const PluralRules* rulesPtr, const Locale& locale,
407 UErrorCode& status) {
408 if (rulesPtr != nullptr) {
409 return rulesPtr;
410 }
411 // Lazily create PluralRules
412 if (fRules.isNull()) {
413 fRules.adoptInstead(PluralRules::forLocale(locale, status));
414 }
415 return fRules.getAlias();
416}
417
418int32_t NumberFormatterImpl::writeAffixes(const MicroProps& micros, FormattedStringBuilder& string,
419 int32_t start, int32_t end, UErrorCode& status) {
420 // Always apply the inner modifier (which is "strong").
421 int32_t length = micros.modInner->apply(string, start, end, status);
422 if (micros.padding.isValid()) {
423 length += micros.padding
424 .padAndApply(*micros.modMiddle, *micros.modOuter, string, start, length + end, status);
425 } else {
426 length += micros.modMiddle->apply(string, start, length + end, status);
427 length += micros.modOuter->apply(string, start, length + end, status);
428 }
429 return length;
430}
431
432int32_t NumberFormatterImpl::writeNumber(const MicroProps& micros, DecimalQuantity& quantity,
433 FormattedStringBuilder& string, int32_t index,
434 UErrorCode& status) {
435 int32_t length = 0;
436 if (quantity.isInfinite()) {
437 length += string.insert(
438 length + index,
439 micros.symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kInfinitySymbol),
440 {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD},
441 status);
442
443 } else if (quantity.isNaN()) {
444 length += string.insert(
445 length + index,
446 micros.symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kNaNSymbol),
447 {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD},
448 status);
449
450 } else {
451 // Add the integer digits
452 length += writeIntegerDigits(micros, quantity, string, length + index, status);
453
454 // Add the decimal point
455 if (quantity.getLowerDisplayMagnitude() < 0 || micros.decimal == UNUM_DECIMAL_SEPARATOR_ALWAYS) {
456 length += string.insert(
457 length + index,
458 micros.useCurrency ? micros.symbols->getSymbol(
459 DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol) : micros
460 .symbols
461 ->getSymbol(
462 DecimalFormatSymbols::ENumberFormatSymbol::kDecimalSeparatorSymbol),
463 {UFIELD_CATEGORY_NUMBER, UNUM_DECIMAL_SEPARATOR_FIELD},
464 status);
465 }
466
467 // Add the fraction digits
468 length += writeFractionDigits(micros, quantity, string, length + index, status);
469
470 if (length == 0) {
471 // Force output of the digit for value 0
472 length += utils::insertDigitFromSymbols(
473 string,
474 index,
475 0,
476 *micros.symbols,
477 {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD},
478 status);
479 }
480 }
481
482 return length;
483}
484
485int32_t NumberFormatterImpl::writeIntegerDigits(const MicroProps& micros, DecimalQuantity& quantity,
486 FormattedStringBuilder& string, int32_t index,
487 UErrorCode& status) {
488 int length = 0;
489 int integerCount = quantity.getUpperDisplayMagnitude() + 1;
490 for (int i = 0; i < integerCount; i++) {
491 // Add grouping separator
492 if (micros.grouping.groupAtPosition(i, quantity)) {
493 length += string.insert(
494 index,
495 micros.useCurrency ? micros.symbols->getSymbol(
496 DecimalFormatSymbols::ENumberFormatSymbol::kMonetaryGroupingSeparatorSymbol)
497 : micros.symbols->getSymbol(
498 DecimalFormatSymbols::ENumberFormatSymbol::kGroupingSeparatorSymbol),
499 {UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD},
500 status);
501 }
502
503 // Get and append the next digit value
504 int8_t nextDigit = quantity.getDigit(i);
505 length += utils::insertDigitFromSymbols(
506 string,
507 index,
508 nextDigit,
509 *micros.symbols,
510 {UFIELD_CATEGORY_NUMBER,
511 UNUM_INTEGER_FIELD},
512 status);
513 }
514 return length;
515}
516
517int32_t NumberFormatterImpl::writeFractionDigits(const MicroProps& micros, DecimalQuantity& quantity,
518 FormattedStringBuilder& string, int32_t index,
519 UErrorCode& status) {
520 int length = 0;
521 int fractionCount = -quantity.getLowerDisplayMagnitude();
522 for (int i = 0; i < fractionCount; i++) {
523 // Get and append the next digit value
524 int8_t nextDigit = quantity.getDigit(-i - 1);
525 length += utils::insertDigitFromSymbols(
526 string,
527 length + index,
528 nextDigit,
529 *micros.symbols,
530 {UFIELD_CATEGORY_NUMBER, UNUM_FRACTION_FIELD},
531 status);
532 }
533 return length;
534}
535
536#endif /* #if !UCONFIG_NO_FORMATTING */
537