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 | |
23 | using namespace icu; |
24 | using namespace icu::number; |
25 | using namespace icu::number::impl; |
26 | |
27 | |
28 | MicroPropsGenerator::~MicroPropsGenerator() = default; |
29 | |
30 | |
31 | NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, UErrorCode& status) |
32 | : NumberFormatterImpl(macros, true, status) { |
33 | } |
34 | |
35 | int32_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 | |
45 | int32_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 | |
57 | int32_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 | |
67 | void 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 | |
78 | MicroProps& 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 | |
91 | int32_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 | |
102 | int32_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 | |
113 | NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, bool safe, UErrorCode& status) { |
114 | fMicroPropsGenerator = macrosToMicroGenerator(macros, safe, status); |
115 | } |
116 | |
117 | ////////// |
118 | |
119 | const MicroPropsGenerator* |
120 | NumberFormatterImpl::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(¯os.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 | |
405 | const PluralRules* |
406 | NumberFormatterImpl::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 | |
418 | int32_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 | |
432 | int32_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 | |
485 | int32_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 | |
517 | int32_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 | |