number_mapper.cpp (21656B)
1 // © 2018 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 // Allow implicit conversion from char16_t* to UnicodeString for this file: 9 // Helpful in toString methods and elsewhere. 10 #define UNISTR_FROM_STRING_EXPLICIT 11 12 #include "number_mapper.h" 13 #include "number_patternstring.h" 14 #include "unicode/errorcode.h" 15 #include "number_utils.h" 16 17 using namespace icu; 18 using namespace icu::number; 19 using namespace icu::number::impl; 20 21 22 UnlocalizedNumberFormatter NumberPropertyMapper::create(const DecimalFormatProperties& properties, 23 const DecimalFormatSymbols& symbols, 24 DecimalFormatWarehouse& warehouse, 25 UErrorCode& status) { 26 return NumberFormatter::with().macros(oldToNew(properties, symbols, warehouse, nullptr, status)); 27 } 28 29 UnlocalizedNumberFormatter NumberPropertyMapper::create(const DecimalFormatProperties& properties, 30 const DecimalFormatSymbols& symbols, 31 DecimalFormatWarehouse& warehouse, 32 DecimalFormatProperties& exportedProperties, 33 UErrorCode& status) { 34 return NumberFormatter::with().macros( 35 oldToNew( 36 properties, symbols, warehouse, &exportedProperties, status)); 37 } 38 39 MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& properties, 40 const DecimalFormatSymbols& symbols, 41 DecimalFormatWarehouse& warehouse, 42 DecimalFormatProperties* exportedProperties, 43 UErrorCode& status) { 44 MacroProps macros; 45 Locale locale = symbols.getLocale(); 46 47 ///////////// 48 // SYMBOLS // 49 ///////////// 50 51 macros.symbols.setTo(symbols); 52 53 ////////////////// 54 // PLURAL RULES // 55 ////////////////// 56 57 if (!properties.currencyPluralInfo.fPtr.isNull()) { 58 macros.rules = properties.currencyPluralInfo.fPtr->getPluralRules(); 59 } 60 61 ///////////// 62 // AFFIXES // 63 ///////////// 64 65 warehouse.affixProvider.setTo(properties, status); 66 macros.affixProvider = &warehouse.affixProvider.get(); 67 68 /////////// 69 // UNITS // 70 /////////// 71 72 bool useCurrency = ( 73 !properties.currency.isNull() || 74 !properties.currencyPluralInfo.fPtr.isNull() || 75 !properties.currencyUsage.isNull() || 76 warehouse.affixProvider.get().hasCurrencySign()); 77 CurrencyUnit currency; 78 UCurrencyUsage currencyUsage; 79 if (useCurrency) { 80 currency = resolveCurrency(properties, locale, status); 81 currencyUsage = properties.currencyUsage.getOrDefault(UCURR_USAGE_STANDARD); 82 // NOTE: Slicing is OK. 83 macros.unit = currency; // NOLINT 84 } 85 86 /////////////////////// 87 // ROUNDING STRATEGY // 88 /////////////////////// 89 90 int32_t maxInt = properties.maximumIntegerDigits; 91 int32_t minInt = properties.minimumIntegerDigits; 92 int32_t maxFrac = properties.maximumFractionDigits; 93 int32_t minFrac = properties.minimumFractionDigits; 94 int32_t minSig = properties.minimumSignificantDigits; 95 int32_t maxSig = properties.maximumSignificantDigits; 96 double roundingIncrement = properties.roundingIncrement; 97 // Not assigning directly to macros.roundingMode here: we change 98 // roundingMode if and when we also change macros.precision. 99 RoundingMode roundingMode = properties.roundingMode.getOrDefault(UNUM_ROUND_HALFEVEN); 100 bool explicitMinMaxFrac = minFrac != -1 || maxFrac != -1; 101 bool explicitMinMaxSig = minSig != -1 || maxSig != -1; 102 // Resolve min/max frac for currencies, required for the validation logic and for when minFrac or 103 // maxFrac was 104 // set (but not both) on a currency instance. 105 // NOTE: Increments are handled in "Precision.constructCurrency()". 106 if (useCurrency && (minFrac == -1 || maxFrac == -1)) { 107 int32_t digits = ucurr_getDefaultFractionDigitsForUsage( 108 currency.getISOCurrency(), currencyUsage, &status); 109 if (minFrac == -1 && maxFrac == -1) { 110 minFrac = digits; 111 maxFrac = digits; 112 } else if (minFrac == -1) { 113 minFrac = std::min(maxFrac, digits); 114 } else /* if (maxFrac == -1) */ { 115 maxFrac = std::max(minFrac, digits); 116 } 117 } 118 // Validate min/max int/frac. 119 // For backwards compatibility, minimum overrides maximum if the two conflict. 120 if (minInt == 0 && maxFrac != 0) { 121 minFrac = (minFrac < 0 || (minFrac == 0 && maxInt == 0)) ? 1 : minFrac; 122 maxFrac = maxFrac < 0 ? -1 : maxFrac < minFrac ? minFrac : maxFrac; 123 minInt = 0; 124 maxInt = maxInt < 0 ? -1 : maxInt > kMaxIntFracSig ? -1 : maxInt; 125 } else { 126 // Force a digit before the decimal point. 127 minFrac = minFrac < 0 ? 0 : minFrac; 128 maxFrac = maxFrac < 0 ? -1 : maxFrac < minFrac ? minFrac : maxFrac; 129 minInt = minInt <= 0 ? 1 : minInt > kMaxIntFracSig ? 1 : minInt; 130 maxInt = maxInt < 0 ? -1 : maxInt < minInt ? minInt : maxInt > kMaxIntFracSig ? -1 : maxInt; 131 } 132 Precision precision; 133 if (!properties.currencyUsage.isNull()) { 134 U_ASSERT(useCurrency); 135 precision = Precision::constructCurrency(currencyUsage).withCurrency(currency); 136 } else if (roundingIncrement != 0.0) { 137 if (PatternStringUtils::ignoreRoundingIncrement(roundingIncrement, maxFrac)) { 138 precision = Precision::constructFraction(minFrac, maxFrac); 139 } else { 140 // Convert the double increment to an integer increment 141 precision = Precision::increment(roundingIncrement).withMinFraction(minFrac); 142 } 143 } else if (explicitMinMaxSig) { 144 minSig = minSig < 1 ? 1 : minSig > kMaxIntFracSig ? kMaxIntFracSig : minSig; 145 maxSig = maxSig < 0 ? kMaxIntFracSig : maxSig < minSig ? minSig : maxSig > kMaxIntFracSig 146 ? kMaxIntFracSig : maxSig; 147 precision = Precision::constructSignificant(minSig, maxSig); 148 } else if (explicitMinMaxFrac) { 149 precision = Precision::constructFraction(minFrac, maxFrac); 150 } else if (useCurrency) { 151 precision = Precision::constructCurrency(currencyUsage); 152 } 153 if (!precision.isBogus()) { 154 macros.roundingMode = roundingMode; 155 macros.precision = precision; 156 } 157 158 /////////////////// 159 // INTEGER WIDTH // 160 /////////////////// 161 162 macros.integerWidth = IntegerWidth( 163 static_cast<digits_t>(minInt), 164 static_cast<digits_t>(maxInt), 165 properties.formatFailIfMoreThanMaxDigits); 166 167 /////////////////////// 168 // GROUPING STRATEGY // 169 /////////////////////// 170 171 macros.grouper = Grouper::forProperties(properties); 172 173 ///////////// 174 // PADDING // 175 ///////////// 176 177 if (properties.formatWidth > 0) { 178 macros.padder = Padder::forProperties(properties); 179 } 180 181 /////////////////////////////// 182 // DECIMAL MARK ALWAYS SHOWN // 183 /////////////////////////////// 184 185 macros.decimal = properties.decimalSeparatorAlwaysShown ? UNUM_DECIMAL_SEPARATOR_ALWAYS 186 : UNUM_DECIMAL_SEPARATOR_AUTO; 187 188 /////////////////////// 189 // SIGN ALWAYS SHOWN // 190 /////////////////////// 191 192 macros.sign = properties.signAlwaysShown ? UNUM_SIGN_ALWAYS : UNUM_SIGN_AUTO; 193 194 ///////////////////////// 195 // SCIENTIFIC NOTATION // 196 ///////////////////////// 197 198 if (properties.minimumExponentDigits != -1) { 199 // Scientific notation is required. 200 // This whole section feels like a hack, but it is needed for regression tests. 201 // The mapping from property bag to scientific notation is nontrivial due to LDML rules. 202 if (maxInt > 8) { 203 // But #13110: The maximum of 8 digits has unknown origins and is not in the spec. 204 // If maxInt is greater than 8, it is set to minInt, even if minInt is greater than 8. 205 maxInt = minInt; 206 macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt); 207 } else if (maxInt > minInt && minInt > 1) { 208 // Bug #13289: if maxInt > minInt > 1, then minInt should be 1. 209 minInt = 1; 210 macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt); 211 } 212 int engineering = maxInt < 0 ? -1 : maxInt; 213 macros.notation = ScientificNotation( 214 // Engineering interval: 215 static_cast<int8_t>(engineering), 216 // Enforce minimum integer digits (for patterns like "000.00E0"): 217 (engineering == minInt), 218 // Minimum exponent digits: 219 static_cast<digits_t>(properties.minimumExponentDigits), 220 // Exponent sign always shown: 221 properties.exponentSignAlwaysShown ? UNUM_SIGN_ALWAYS : UNUM_SIGN_AUTO); 222 // Scientific notation also involves overriding the rounding mode. 223 // TODO: Overriding here is a bit of a hack. Should this logic go earlier? 224 if (macros.precision.fType == Precision::PrecisionType::RND_FRACTION) { 225 // For the purposes of rounding, get the original min/max int/frac, since the local 226 // variables have been manipulated for display purposes. 227 int maxInt_ = properties.maximumIntegerDigits; 228 int minInt_ = properties.minimumIntegerDigits; 229 int minFrac_ = properties.minimumFractionDigits; 230 int maxFrac_ = properties.maximumFractionDigits; 231 if (minInt_ == 0 && maxFrac_ == 0) { 232 // Patterns like "#E0" and "##E0", which mean no rounding! 233 macros.precision = Precision::unlimited(); 234 } else if (minInt_ == 0 && minFrac_ == 0) { 235 // Patterns like "#.##E0" (no zeros in the mantissa), which mean round to maxFrac+1 236 macros.precision = Precision::constructSignificant(1, maxFrac_ + 1); 237 } else { 238 int maxSig_ = minInt_ + maxFrac_; 239 // Bug #20058: if maxInt_ > minInt_ > 1, then minInt_ should be 1. 240 if (maxInt_ > minInt_ && minInt_ > 1) { 241 minInt_ = 1; 242 } 243 int minSig_ = minInt_ + minFrac_; 244 // To avoid regression, maxSig is not reset when minInt_ set to 1. 245 // TODO: Reset maxSig_ = 1 + minFrac_ to follow the spec. 246 macros.precision = Precision::constructSignificant(minSig_, maxSig_); 247 } 248 macros.roundingMode = roundingMode; 249 } 250 } 251 252 ////////////////////// 253 // COMPACT NOTATION // 254 ////////////////////// 255 256 if (!properties.compactStyle.isNull()) { 257 if (properties.compactStyle.getNoError() == UNumberCompactStyle::UNUM_LONG) { 258 macros.notation = Notation::compactLong(); 259 } else { 260 macros.notation = Notation::compactShort(); 261 } 262 } 263 264 ///////////////// 265 // MULTIPLIERS // 266 ///////////////// 267 268 macros.scale = scaleFromProperties(properties); 269 270 ////////////////////// 271 // PROPERTY EXPORTS // 272 ////////////////////// 273 274 if (exportedProperties != nullptr) { 275 276 exportedProperties->currency = currency; 277 exportedProperties->roundingMode = roundingMode; 278 exportedProperties->minimumIntegerDigits = minInt; 279 exportedProperties->maximumIntegerDigits = maxInt == -1 ? INT32_MAX : maxInt; 280 281 Precision rounding_; 282 if (useCurrency && precision.fType == Precision::PrecisionType::RND_CURRENCY) { 283 rounding_ = precision.withCurrency(currency, status); 284 } else { 285 rounding_ = precision; 286 } 287 int minFrac_ = minFrac; 288 int maxFrac_ = maxFrac; 289 int minSig_ = minSig; 290 int maxSig_ = maxSig; 291 double increment_ = 0.0; 292 if (rounding_.fType == Precision::PrecisionType::RND_FRACTION) { 293 minFrac_ = rounding_.fUnion.fracSig.fMinFrac; 294 maxFrac_ = rounding_.fUnion.fracSig.fMaxFrac; 295 } else if (rounding_.fType == Precision::PrecisionType::RND_INCREMENT 296 || rounding_.fType == Precision::PrecisionType::RND_INCREMENT_ONE 297 || rounding_.fType == Precision::PrecisionType::RND_INCREMENT_FIVE) { 298 minFrac_ = rounding_.fUnion.increment.fMinFrac; 299 // If incrementRounding is used, maxFrac is set equal to minFrac 300 maxFrac_ = rounding_.fUnion.increment.fMinFrac; 301 // Convert the integer increment to a double 302 DecimalQuantity dq; 303 dq.setToLong(rounding_.fUnion.increment.fIncrement); 304 dq.adjustMagnitude(rounding_.fUnion.increment.fIncrementMagnitude); 305 increment_ = dq.toDouble(); 306 } else if (rounding_.fType == Precision::PrecisionType::RND_SIGNIFICANT) { 307 minSig_ = rounding_.fUnion.fracSig.fMinSig; 308 maxSig_ = rounding_.fUnion.fracSig.fMaxSig; 309 } 310 311 exportedProperties->minimumFractionDigits = minFrac_; 312 exportedProperties->maximumFractionDigits = maxFrac_; 313 exportedProperties->minimumSignificantDigits = minSig_; 314 exportedProperties->maximumSignificantDigits = maxSig_; 315 exportedProperties->roundingIncrement = increment_; 316 } 317 318 return macros; 319 } 320 321 322 void PropertiesAffixPatternProvider::setTo(const DecimalFormatProperties& properties, UErrorCode& status) { 323 fBogus = false; 324 325 // There are two ways to set affixes in DecimalFormat: via the pattern string (applyPattern), and via the 326 // explicit setters (setPositivePrefix and friends). The way to resolve the settings is as follows: 327 // 328 // 1) If the explicit setting is present for the field, use it. 329 // 2) Otherwise, follows UTS 35 rules based on the pattern string. 330 // 331 // Importantly, the explicit setters affect only the one field they override. If you set the positive 332 // prefix, that should not affect the negative prefix. 333 334 // Convenience: Extract the properties into local variables. 335 // Variables are named with three chars: [p/n][p/s][o/p] 336 // [p/n] => p for positive, n for negative 337 // [p/s] => p for prefix, s for suffix 338 // [o/p] => o for escaped custom override string, p for pattern string 339 UnicodeString ppo = AffixUtils::escape(properties.positivePrefix); 340 UnicodeString pso = AffixUtils::escape(properties.positiveSuffix); 341 UnicodeString npo = AffixUtils::escape(properties.negativePrefix); 342 UnicodeString nso = AffixUtils::escape(properties.negativeSuffix); 343 const UnicodeString& ppp = properties.positivePrefixPattern; 344 const UnicodeString& psp = properties.positiveSuffixPattern; 345 const UnicodeString& npp = properties.negativePrefixPattern; 346 const UnicodeString& nsp = properties.negativeSuffixPattern; 347 348 if (!properties.positivePrefix.isBogus()) { 349 posPrefix = ppo; 350 } else if (!ppp.isBogus()) { 351 posPrefix = ppp; 352 } else { 353 // UTS 35: Default positive prefix is empty string. 354 posPrefix = u""; 355 } 356 357 if (!properties.positiveSuffix.isBogus()) { 358 posSuffix = pso; 359 } else if (!psp.isBogus()) { 360 posSuffix = psp; 361 } else { 362 // UTS 35: Default positive suffix is empty string. 363 posSuffix = u""; 364 } 365 366 if (!properties.negativePrefix.isBogus()) { 367 negPrefix = npo; 368 } else if (!npp.isBogus()) { 369 negPrefix = npp; 370 } else { 371 // UTS 35: Default negative prefix is "-" with positive prefix. 372 // Important: We prepend the "-" to the pattern, not the override! 373 negPrefix = ppp.isBogus() ? u"-" : u"-" + ppp; 374 } 375 376 if (!properties.negativeSuffix.isBogus()) { 377 negSuffix = nso; 378 } else if (!nsp.isBogus()) { 379 negSuffix = nsp; 380 } else { 381 // UTS 35: Default negative prefix is the positive prefix. 382 negSuffix = psp.isBogus() ? u"" : psp; 383 } 384 385 // For declaring if this is a currency pattern, we need to look at the 386 // original pattern, not at any user-specified overrides. 387 isCurrencyPattern = ( 388 AffixUtils::hasCurrencySymbols(ppp, status) || 389 AffixUtils::hasCurrencySymbols(psp, status) || 390 AffixUtils::hasCurrencySymbols(npp, status) || 391 AffixUtils::hasCurrencySymbols(nsp, status) || 392 properties.currencyAsDecimal); 393 394 fCurrencyAsDecimal = properties.currencyAsDecimal; 395 } 396 397 char16_t PropertiesAffixPatternProvider::charAt(int flags, int i) const { 398 return getStringInternal(flags).charAt(i); 399 } 400 401 int PropertiesAffixPatternProvider::length(int flags) const { 402 return getStringInternal(flags).length(); 403 } 404 405 UnicodeString PropertiesAffixPatternProvider::getString(int32_t flags) const { 406 return getStringInternal(flags); 407 } 408 409 const UnicodeString& PropertiesAffixPatternProvider::getStringInternal(int32_t flags) const { 410 bool prefix = (flags & AFFIX_PREFIX) != 0; 411 bool negative = (flags & AFFIX_NEGATIVE_SUBPATTERN) != 0; 412 if (prefix && negative) { 413 return negPrefix; 414 } else if (prefix) { 415 return posPrefix; 416 } else if (negative) { 417 return negSuffix; 418 } else { 419 return posSuffix; 420 } 421 } 422 423 bool PropertiesAffixPatternProvider::positiveHasPlusSign() const { 424 // TODO: Change the internal APIs to propagate out the error? 425 ErrorCode localStatus; 426 return AffixUtils::containsType(posPrefix, TYPE_PLUS_SIGN, localStatus) || 427 AffixUtils::containsType(posSuffix, TYPE_PLUS_SIGN, localStatus); 428 } 429 430 bool PropertiesAffixPatternProvider::hasNegativeSubpattern() const { 431 return ( 432 (negSuffix != posSuffix) || 433 negPrefix.tempSubString(1) != posPrefix || 434 negPrefix.charAt(0) != u'-' 435 ); 436 } 437 438 bool PropertiesAffixPatternProvider::negativeHasMinusSign() const { 439 ErrorCode localStatus; 440 return AffixUtils::containsType(negPrefix, TYPE_MINUS_SIGN, localStatus) || 441 AffixUtils::containsType(negSuffix, TYPE_MINUS_SIGN, localStatus); 442 } 443 444 bool PropertiesAffixPatternProvider::hasCurrencySign() const { 445 return isCurrencyPattern; 446 } 447 448 bool PropertiesAffixPatternProvider::containsSymbolType(AffixPatternType type, UErrorCode& status) const { 449 return AffixUtils::containsType(posPrefix, type, status) || 450 AffixUtils::containsType(posSuffix, type, status) || 451 AffixUtils::containsType(negPrefix, type, status) || 452 AffixUtils::containsType(negSuffix, type, status); 453 } 454 455 bool PropertiesAffixPatternProvider::hasBody() const { 456 return true; 457 } 458 459 bool PropertiesAffixPatternProvider::currencyAsDecimal() const { 460 return fCurrencyAsDecimal; 461 } 462 463 464 void CurrencyPluralInfoAffixProvider::setTo(const CurrencyPluralInfo& cpi, 465 const DecimalFormatProperties& properties, 466 UErrorCode& status) { 467 // We need to use a PropertiesAffixPatternProvider, not the simpler version ParsedPatternInfo, 468 // because user-specified affix overrides still need to work. 469 fBogus = false; 470 DecimalFormatProperties pluralProperties(properties); 471 for (int32_t plural = 0; plural < StandardPlural::COUNT; plural++) { 472 const char* keyword = StandardPlural::getKeyword(static_cast<StandardPlural::Form>(plural)); 473 UnicodeString patternString; 474 patternString = cpi.getCurrencyPluralPattern(keyword, patternString); 475 PatternParser::parseToExistingProperties( 476 patternString, 477 pluralProperties, 478 IGNORE_ROUNDING_NEVER, 479 status); 480 affixesByPlural[plural].setTo(pluralProperties, status); 481 } 482 } 483 484 char16_t CurrencyPluralInfoAffixProvider::charAt(int32_t flags, int32_t i) const { 485 int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK); 486 return affixesByPlural[pluralOrdinal].charAt(flags, i); 487 } 488 489 int32_t CurrencyPluralInfoAffixProvider::length(int32_t flags) const { 490 int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK); 491 return affixesByPlural[pluralOrdinal].length(flags); 492 } 493 494 UnicodeString CurrencyPluralInfoAffixProvider::getString(int32_t flags) const { 495 int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK); 496 return affixesByPlural[pluralOrdinal].getString(flags); 497 } 498 499 bool CurrencyPluralInfoAffixProvider::positiveHasPlusSign() const { 500 return affixesByPlural[StandardPlural::OTHER].positiveHasPlusSign(); 501 } 502 503 bool CurrencyPluralInfoAffixProvider::hasNegativeSubpattern() const { 504 return affixesByPlural[StandardPlural::OTHER].hasNegativeSubpattern(); 505 } 506 507 bool CurrencyPluralInfoAffixProvider::negativeHasMinusSign() const { 508 return affixesByPlural[StandardPlural::OTHER].negativeHasMinusSign(); 509 } 510 511 bool CurrencyPluralInfoAffixProvider::hasCurrencySign() const { 512 return affixesByPlural[StandardPlural::OTHER].hasCurrencySign(); 513 } 514 515 bool CurrencyPluralInfoAffixProvider::containsSymbolType(AffixPatternType type, UErrorCode& status) const { 516 return affixesByPlural[StandardPlural::OTHER].containsSymbolType(type, status); 517 } 518 519 bool CurrencyPluralInfoAffixProvider::hasBody() const { 520 return affixesByPlural[StandardPlural::OTHER].hasBody(); 521 } 522 523 bool CurrencyPluralInfoAffixProvider::currencyAsDecimal() const { 524 return affixesByPlural[StandardPlural::OTHER].currencyAsDecimal(); 525 } 526 527 528 #endif /* #if !UCONFIG_NO_FORMATTING */