summaryrefslogtreecommitdiffstats
path: root/intl/icu/source/i18n/number_mapper.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'intl/icu/source/i18n/number_mapper.cpp')
-rw-r--r--intl/icu/source/i18n/number_mapper.cpp525
1 files changed, 525 insertions, 0 deletions
diff --git a/intl/icu/source/i18n/number_mapper.cpp b/intl/icu/source/i18n/number_mapper.cpp
new file mode 100644
index 0000000000..2f398d4a93
--- /dev/null
+++ b/intl/icu/source/i18n/number_mapper.cpp
@@ -0,0 +1,525 @@
+// © 2018 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+#include "unicode/utypes.h"
+
+#if !UCONFIG_NO_FORMATTING
+
+// Allow implicit conversion from char16_t* to UnicodeString for this file:
+// Helpful in toString methods and elsewhere.
+#define UNISTR_FROM_STRING_EXPLICIT
+
+#include "number_mapper.h"
+#include "number_patternstring.h"
+#include "unicode/errorcode.h"
+#include "number_utils.h"
+
+using namespace icu;
+using namespace icu::number;
+using namespace icu::number::impl;
+
+
+UnlocalizedNumberFormatter NumberPropertyMapper::create(const DecimalFormatProperties& properties,
+ const DecimalFormatSymbols& symbols,
+ DecimalFormatWarehouse& warehouse,
+ UErrorCode& status) {
+ return NumberFormatter::with().macros(oldToNew(properties, symbols, warehouse, nullptr, status));
+}
+
+UnlocalizedNumberFormatter NumberPropertyMapper::create(const DecimalFormatProperties& properties,
+ const DecimalFormatSymbols& symbols,
+ DecimalFormatWarehouse& warehouse,
+ DecimalFormatProperties& exportedProperties,
+ UErrorCode& status) {
+ return NumberFormatter::with().macros(
+ oldToNew(
+ properties, symbols, warehouse, &exportedProperties, status));
+}
+
+MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& properties,
+ const DecimalFormatSymbols& symbols,
+ DecimalFormatWarehouse& warehouse,
+ DecimalFormatProperties* exportedProperties,
+ UErrorCode& status) {
+ MacroProps macros;
+ Locale locale = symbols.getLocale();
+
+ /////////////
+ // SYMBOLS //
+ /////////////
+
+ macros.symbols.setTo(symbols);
+
+ //////////////////
+ // PLURAL RULES //
+ //////////////////
+
+ if (!properties.currencyPluralInfo.fPtr.isNull()) {
+ macros.rules = properties.currencyPluralInfo.fPtr->getPluralRules();
+ }
+
+ /////////////
+ // AFFIXES //
+ /////////////
+
+ warehouse.affixProvider.setTo(properties, status);
+ macros.affixProvider = &warehouse.affixProvider.get();
+
+ ///////////
+ // UNITS //
+ ///////////
+
+ bool useCurrency = (
+ !properties.currency.isNull() ||
+ !properties.currencyPluralInfo.fPtr.isNull() ||
+ !properties.currencyUsage.isNull() ||
+ warehouse.affixProvider.get().hasCurrencySign());
+ CurrencyUnit currency = resolveCurrency(properties, locale, status);
+ UCurrencyUsage currencyUsage = properties.currencyUsage.getOrDefault(UCURR_USAGE_STANDARD);
+ if (useCurrency) {
+ // NOTE: Slicing is OK.
+ macros.unit = currency; // NOLINT
+ }
+
+ ///////////////////////
+ // ROUNDING STRATEGY //
+ ///////////////////////
+
+ int32_t maxInt = properties.maximumIntegerDigits;
+ int32_t minInt = properties.minimumIntegerDigits;
+ int32_t maxFrac = properties.maximumFractionDigits;
+ int32_t minFrac = properties.minimumFractionDigits;
+ int32_t minSig = properties.minimumSignificantDigits;
+ int32_t maxSig = properties.maximumSignificantDigits;
+ double roundingIncrement = properties.roundingIncrement;
+ // Not assigning directly to macros.roundingMode here: we change
+ // roundingMode if and when we also change macros.precision.
+ RoundingMode roundingMode = properties.roundingMode.getOrDefault(UNUM_ROUND_HALFEVEN);
+ bool explicitMinMaxFrac = minFrac != -1 || maxFrac != -1;
+ bool explicitMinMaxSig = minSig != -1 || maxSig != -1;
+ // Resolve min/max frac for currencies, required for the validation logic and for when minFrac or
+ // maxFrac was
+ // set (but not both) on a currency instance.
+ // NOTE: Increments are handled in "Precision.constructCurrency()".
+ if (useCurrency && (minFrac == -1 || maxFrac == -1)) {
+ int32_t digits = ucurr_getDefaultFractionDigitsForUsage(
+ currency.getISOCurrency(), currencyUsage, &status);
+ if (minFrac == -1 && maxFrac == -1) {
+ minFrac = digits;
+ maxFrac = digits;
+ } else if (minFrac == -1) {
+ minFrac = std::min(maxFrac, digits);
+ } else /* if (maxFrac == -1) */ {
+ maxFrac = std::max(minFrac, digits);
+ }
+ }
+ // Validate min/max int/frac.
+ // For backwards compatibility, minimum overrides maximum if the two conflict.
+ if (minInt == 0 && maxFrac != 0) {
+ minFrac = (minFrac < 0 || (minFrac == 0 && maxInt == 0)) ? 1 : minFrac;
+ maxFrac = maxFrac < 0 ? -1 : maxFrac < minFrac ? minFrac : maxFrac;
+ minInt = 0;
+ maxInt = maxInt < 0 ? -1 : maxInt > kMaxIntFracSig ? -1 : maxInt;
+ } else {
+ // Force a digit before the decimal point.
+ minFrac = minFrac < 0 ? 0 : minFrac;
+ maxFrac = maxFrac < 0 ? -1 : maxFrac < minFrac ? minFrac : maxFrac;
+ minInt = minInt <= 0 ? 1 : minInt > kMaxIntFracSig ? 1 : minInt;
+ maxInt = maxInt < 0 ? -1 : maxInt < minInt ? minInt : maxInt > kMaxIntFracSig ? -1 : maxInt;
+ }
+ Precision precision;
+ if (!properties.currencyUsage.isNull()) {
+ precision = Precision::constructCurrency(currencyUsage).withCurrency(currency);
+ } else if (roundingIncrement != 0.0) {
+ if (PatternStringUtils::ignoreRoundingIncrement(roundingIncrement, maxFrac)) {
+ precision = Precision::constructFraction(minFrac, maxFrac);
+ } else {
+ // Convert the double increment to an integer increment
+ precision = Precision::increment(roundingIncrement).withMinFraction(minFrac);
+ }
+ } else if (explicitMinMaxSig) {
+ minSig = minSig < 1 ? 1 : minSig > kMaxIntFracSig ? kMaxIntFracSig : minSig;
+ maxSig = maxSig < 0 ? kMaxIntFracSig : maxSig < minSig ? minSig : maxSig > kMaxIntFracSig
+ ? kMaxIntFracSig : maxSig;
+ precision = Precision::constructSignificant(minSig, maxSig);
+ } else if (explicitMinMaxFrac) {
+ precision = Precision::constructFraction(minFrac, maxFrac);
+ } else if (useCurrency) {
+ precision = Precision::constructCurrency(currencyUsage);
+ }
+ if (!precision.isBogus()) {
+ macros.roundingMode = roundingMode;
+ macros.precision = precision;
+ }
+
+ ///////////////////
+ // INTEGER WIDTH //
+ ///////////////////
+
+ macros.integerWidth = IntegerWidth(
+ static_cast<digits_t>(minInt),
+ static_cast<digits_t>(maxInt),
+ properties.formatFailIfMoreThanMaxDigits);
+
+ ///////////////////////
+ // GROUPING STRATEGY //
+ ///////////////////////
+
+ macros.grouper = Grouper::forProperties(properties);
+
+ /////////////
+ // PADDING //
+ /////////////
+
+ if (properties.formatWidth > 0) {
+ macros.padder = Padder::forProperties(properties);
+ }
+
+ ///////////////////////////////
+ // DECIMAL MARK ALWAYS SHOWN //
+ ///////////////////////////////
+
+ macros.decimal = properties.decimalSeparatorAlwaysShown ? UNUM_DECIMAL_SEPARATOR_ALWAYS
+ : UNUM_DECIMAL_SEPARATOR_AUTO;
+
+ ///////////////////////
+ // SIGN ALWAYS SHOWN //
+ ///////////////////////
+
+ macros.sign = properties.signAlwaysShown ? UNUM_SIGN_ALWAYS : UNUM_SIGN_AUTO;
+
+ /////////////////////////
+ // SCIENTIFIC NOTATION //
+ /////////////////////////
+
+ if (properties.minimumExponentDigits != -1) {
+ // Scientific notation is required.
+ // This whole section feels like a hack, but it is needed for regression tests.
+ // The mapping from property bag to scientific notation is nontrivial due to LDML rules.
+ if (maxInt > 8) {
+ // But #13110: The maximum of 8 digits has unknown origins and is not in the spec.
+ // If maxInt is greater than 8, it is set to minInt, even if minInt is greater than 8.
+ maxInt = minInt;
+ macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt);
+ } else if (maxInt > minInt && minInt > 1) {
+ // Bug #13289: if maxInt > minInt > 1, then minInt should be 1.
+ minInt = 1;
+ macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt);
+ }
+ int engineering = maxInt < 0 ? -1 : maxInt;
+ macros.notation = ScientificNotation(
+ // Engineering interval:
+ static_cast<int8_t>(engineering),
+ // Enforce minimum integer digits (for patterns like "000.00E0"):
+ (engineering == minInt),
+ // Minimum exponent digits:
+ static_cast<digits_t>(properties.minimumExponentDigits),
+ // Exponent sign always shown:
+ properties.exponentSignAlwaysShown ? UNUM_SIGN_ALWAYS : UNUM_SIGN_AUTO);
+ // Scientific notation also involves overriding the rounding mode.
+ // TODO: Overriding here is a bit of a hack. Should this logic go earlier?
+ if (macros.precision.fType == Precision::PrecisionType::RND_FRACTION) {
+ // For the purposes of rounding, get the original min/max int/frac, since the local
+ // variables have been manipulated for display purposes.
+ int maxInt_ = properties.maximumIntegerDigits;
+ int minInt_ = properties.minimumIntegerDigits;
+ int minFrac_ = properties.minimumFractionDigits;
+ int maxFrac_ = properties.maximumFractionDigits;
+ if (minInt_ == 0 && maxFrac_ == 0) {
+ // Patterns like "#E0" and "##E0", which mean no rounding!
+ macros.precision = Precision::unlimited();
+ } else if (minInt_ == 0 && minFrac_ == 0) {
+ // Patterns like "#.##E0" (no zeros in the mantissa), which mean round to maxFrac+1
+ macros.precision = Precision::constructSignificant(1, maxFrac_ + 1);
+ } else {
+ int maxSig_ = minInt_ + maxFrac_;
+ // Bug #20058: if maxInt_ > minInt_ > 1, then minInt_ should be 1.
+ if (maxInt_ > minInt_ && minInt_ > 1) {
+ minInt_ = 1;
+ }
+ int minSig_ = minInt_ + minFrac_;
+ // To avoid regression, maxSig is not reset when minInt_ set to 1.
+ // TODO: Reset maxSig_ = 1 + minFrac_ to follow the spec.
+ macros.precision = Precision::constructSignificant(minSig_, maxSig_);
+ }
+ macros.roundingMode = roundingMode;
+ }
+ }
+
+ //////////////////////
+ // COMPACT NOTATION //
+ //////////////////////
+
+ if (!properties.compactStyle.isNull()) {
+ if (properties.compactStyle.getNoError() == UNumberCompactStyle::UNUM_LONG) {
+ macros.notation = Notation::compactLong();
+ } else {
+ macros.notation = Notation::compactShort();
+ }
+ }
+
+ /////////////////
+ // MULTIPLIERS //
+ /////////////////
+
+ macros.scale = scaleFromProperties(properties);
+
+ //////////////////////
+ // PROPERTY EXPORTS //
+ //////////////////////
+
+ if (exportedProperties != nullptr) {
+
+ exportedProperties->currency = currency;
+ exportedProperties->roundingMode = roundingMode;
+ exportedProperties->minimumIntegerDigits = minInt;
+ exportedProperties->maximumIntegerDigits = maxInt == -1 ? INT32_MAX : maxInt;
+
+ Precision rounding_;
+ if (precision.fType == Precision::PrecisionType::RND_CURRENCY) {
+ rounding_ = precision.withCurrency(currency, status);
+ } else {
+ rounding_ = precision;
+ }
+ int minFrac_ = minFrac;
+ int maxFrac_ = maxFrac;
+ int minSig_ = minSig;
+ int maxSig_ = maxSig;
+ double increment_ = 0.0;
+ if (rounding_.fType == Precision::PrecisionType::RND_FRACTION) {
+ minFrac_ = rounding_.fUnion.fracSig.fMinFrac;
+ maxFrac_ = rounding_.fUnion.fracSig.fMaxFrac;
+ } else if (rounding_.fType == Precision::PrecisionType::RND_INCREMENT
+ || rounding_.fType == Precision::PrecisionType::RND_INCREMENT_ONE
+ || rounding_.fType == Precision::PrecisionType::RND_INCREMENT_FIVE) {
+ minFrac_ = rounding_.fUnion.increment.fMinFrac;
+ // If incrementRounding is used, maxFrac is set equal to minFrac
+ maxFrac_ = rounding_.fUnion.increment.fMinFrac;
+ // Convert the integer increment to a double
+ DecimalQuantity dq;
+ dq.setToLong(rounding_.fUnion.increment.fIncrement);
+ dq.adjustMagnitude(rounding_.fUnion.increment.fIncrementMagnitude);
+ increment_ = dq.toDouble();
+ } else if (rounding_.fType == Precision::PrecisionType::RND_SIGNIFICANT) {
+ minSig_ = rounding_.fUnion.fracSig.fMinSig;
+ maxSig_ = rounding_.fUnion.fracSig.fMaxSig;
+ }
+
+ exportedProperties->minimumFractionDigits = minFrac_;
+ exportedProperties->maximumFractionDigits = maxFrac_;
+ exportedProperties->minimumSignificantDigits = minSig_;
+ exportedProperties->maximumSignificantDigits = maxSig_;
+ exportedProperties->roundingIncrement = increment_;
+ }
+
+ return macros;
+}
+
+
+void PropertiesAffixPatternProvider::setTo(const DecimalFormatProperties& properties, UErrorCode& status) {
+ fBogus = false;
+
+ // There are two ways to set affixes in DecimalFormat: via the pattern string (applyPattern), and via the
+ // explicit setters (setPositivePrefix and friends). The way to resolve the settings is as follows:
+ //
+ // 1) If the explicit setting is present for the field, use it.
+ // 2) Otherwise, follows UTS 35 rules based on the pattern string.
+ //
+ // Importantly, the explicit setters affect only the one field they override. If you set the positive
+ // prefix, that should not affect the negative prefix.
+
+ // Convenience: Extract the properties into local variables.
+ // Variables are named with three chars: [p/n][p/s][o/p]
+ // [p/n] => p for positive, n for negative
+ // [p/s] => p for prefix, s for suffix
+ // [o/p] => o for escaped custom override string, p for pattern string
+ UnicodeString ppo = AffixUtils::escape(properties.positivePrefix);
+ UnicodeString pso = AffixUtils::escape(properties.positiveSuffix);
+ UnicodeString npo = AffixUtils::escape(properties.negativePrefix);
+ UnicodeString nso = AffixUtils::escape(properties.negativeSuffix);
+ const UnicodeString& ppp = properties.positivePrefixPattern;
+ const UnicodeString& psp = properties.positiveSuffixPattern;
+ const UnicodeString& npp = properties.negativePrefixPattern;
+ const UnicodeString& nsp = properties.negativeSuffixPattern;
+
+ if (!properties.positivePrefix.isBogus()) {
+ posPrefix = ppo;
+ } else if (!ppp.isBogus()) {
+ posPrefix = ppp;
+ } else {
+ // UTS 35: Default positive prefix is empty string.
+ posPrefix = u"";
+ }
+
+ if (!properties.positiveSuffix.isBogus()) {
+ posSuffix = pso;
+ } else if (!psp.isBogus()) {
+ posSuffix = psp;
+ } else {
+ // UTS 35: Default positive suffix is empty string.
+ posSuffix = u"";
+ }
+
+ if (!properties.negativePrefix.isBogus()) {
+ negPrefix = npo;
+ } else if (!npp.isBogus()) {
+ negPrefix = npp;
+ } else {
+ // UTS 35: Default negative prefix is "-" with positive prefix.
+ // Important: We prepend the "-" to the pattern, not the override!
+ negPrefix = ppp.isBogus() ? u"-" : u"-" + ppp;
+ }
+
+ if (!properties.negativeSuffix.isBogus()) {
+ negSuffix = nso;
+ } else if (!nsp.isBogus()) {
+ negSuffix = nsp;
+ } else {
+ // UTS 35: Default negative prefix is the positive prefix.
+ negSuffix = psp.isBogus() ? u"" : psp;
+ }
+
+ // For declaring if this is a currency pattern, we need to look at the
+ // original pattern, not at any user-specified overrides.
+ isCurrencyPattern = (
+ AffixUtils::hasCurrencySymbols(ppp, status) ||
+ AffixUtils::hasCurrencySymbols(psp, status) ||
+ AffixUtils::hasCurrencySymbols(npp, status) ||
+ AffixUtils::hasCurrencySymbols(nsp, status) ||
+ properties.currencyAsDecimal);
+
+ fCurrencyAsDecimal = properties.currencyAsDecimal;
+}
+
+char16_t PropertiesAffixPatternProvider::charAt(int flags, int i) const {
+ return getStringInternal(flags).charAt(i);
+}
+
+int PropertiesAffixPatternProvider::length(int flags) const {
+ return getStringInternal(flags).length();
+}
+
+UnicodeString PropertiesAffixPatternProvider::getString(int32_t flags) const {
+ return getStringInternal(flags);
+}
+
+const UnicodeString& PropertiesAffixPatternProvider::getStringInternal(int32_t flags) const {
+ bool prefix = (flags & AFFIX_PREFIX) != 0;
+ bool negative = (flags & AFFIX_NEGATIVE_SUBPATTERN) != 0;
+ if (prefix && negative) {
+ return negPrefix;
+ } else if (prefix) {
+ return posPrefix;
+ } else if (negative) {
+ return negSuffix;
+ } else {
+ return posSuffix;
+ }
+}
+
+bool PropertiesAffixPatternProvider::positiveHasPlusSign() const {
+ // TODO: Change the internal APIs to propagate out the error?
+ ErrorCode localStatus;
+ return AffixUtils::containsType(posPrefix, TYPE_PLUS_SIGN, localStatus) ||
+ AffixUtils::containsType(posSuffix, TYPE_PLUS_SIGN, localStatus);
+}
+
+bool PropertiesAffixPatternProvider::hasNegativeSubpattern() const {
+ return (
+ (negSuffix != posSuffix) ||
+ negPrefix.tempSubString(1) != posPrefix ||
+ negPrefix.charAt(0) != u'-'
+ );
+}
+
+bool PropertiesAffixPatternProvider::negativeHasMinusSign() const {
+ ErrorCode localStatus;
+ return AffixUtils::containsType(negPrefix, TYPE_MINUS_SIGN, localStatus) ||
+ AffixUtils::containsType(negSuffix, TYPE_MINUS_SIGN, localStatus);
+}
+
+bool PropertiesAffixPatternProvider::hasCurrencySign() const {
+ return isCurrencyPattern;
+}
+
+bool PropertiesAffixPatternProvider::containsSymbolType(AffixPatternType type, UErrorCode& status) const {
+ return AffixUtils::containsType(posPrefix, type, status) ||
+ AffixUtils::containsType(posSuffix, type, status) ||
+ AffixUtils::containsType(negPrefix, type, status) ||
+ AffixUtils::containsType(negSuffix, type, status);
+}
+
+bool PropertiesAffixPatternProvider::hasBody() const {
+ return true;
+}
+
+bool PropertiesAffixPatternProvider::currencyAsDecimal() const {
+ return fCurrencyAsDecimal;
+}
+
+
+void CurrencyPluralInfoAffixProvider::setTo(const CurrencyPluralInfo& cpi,
+ const DecimalFormatProperties& properties,
+ UErrorCode& status) {
+ // We need to use a PropertiesAffixPatternProvider, not the simpler version ParsedPatternInfo,
+ // because user-specified affix overrides still need to work.
+ fBogus = false;
+ DecimalFormatProperties pluralProperties(properties);
+ for (int32_t plural = 0; plural < StandardPlural::COUNT; plural++) {
+ const char* keyword = StandardPlural::getKeyword(static_cast<StandardPlural::Form>(plural));
+ UnicodeString patternString;
+ patternString = cpi.getCurrencyPluralPattern(keyword, patternString);
+ PatternParser::parseToExistingProperties(
+ patternString,
+ pluralProperties,
+ IGNORE_ROUNDING_NEVER,
+ status);
+ affixesByPlural[plural].setTo(pluralProperties, status);
+ }
+}
+
+char16_t CurrencyPluralInfoAffixProvider::charAt(int32_t flags, int32_t i) const {
+ int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
+ return affixesByPlural[pluralOrdinal].charAt(flags, i);
+}
+
+int32_t CurrencyPluralInfoAffixProvider::length(int32_t flags) const {
+ int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
+ return affixesByPlural[pluralOrdinal].length(flags);
+}
+
+UnicodeString CurrencyPluralInfoAffixProvider::getString(int32_t flags) const {
+ int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK);
+ return affixesByPlural[pluralOrdinal].getString(flags);
+}
+
+bool CurrencyPluralInfoAffixProvider::positiveHasPlusSign() const {
+ return affixesByPlural[StandardPlural::OTHER].positiveHasPlusSign();
+}
+
+bool CurrencyPluralInfoAffixProvider::hasNegativeSubpattern() const {
+ return affixesByPlural[StandardPlural::OTHER].hasNegativeSubpattern();
+}
+
+bool CurrencyPluralInfoAffixProvider::negativeHasMinusSign() const {
+ return affixesByPlural[StandardPlural::OTHER].negativeHasMinusSign();
+}
+
+bool CurrencyPluralInfoAffixProvider::hasCurrencySign() const {
+ return affixesByPlural[StandardPlural::OTHER].hasCurrencySign();
+}
+
+bool CurrencyPluralInfoAffixProvider::containsSymbolType(AffixPatternType type, UErrorCode& status) const {
+ return affixesByPlural[StandardPlural::OTHER].containsSymbolType(type, status);
+}
+
+bool CurrencyPluralInfoAffixProvider::hasBody() const {
+ return affixesByPlural[StandardPlural::OTHER].hasBody();
+}
+
+bool CurrencyPluralInfoAffixProvider::currencyAsDecimal() const {
+ return affixesByPlural[StandardPlural::OTHER].currencyAsDecimal();
+}
+
+
+#endif /* #if !UCONFIG_NO_FORMATTING */