From: Shane F. Carr Date: Wed, 23 Nov 2022 22:48:12 +0000 (+0000) Subject: ICU-22093 ICU4C: Add SimpleNumber and SimpleNumberFormatter X-Git-Tag: cldr/2022-12-02~4 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=a2854b615aa3876e15cda03eabbb6faf2e260fc2;p=icu ICU-22093 ICU4C: Add SimpleNumber and SimpleNumberFormatter See #2241 --- diff --git a/icu4c/source/i18n/dcfmtsym.cpp b/icu4c/source/i18n/dcfmtsym.cpp index fa5920aaa50..3b2650c1e37 100644 --- a/icu4c/source/i18n/dcfmtsym.cpp +++ b/icu4c/source/i18n/dcfmtsym.cpp @@ -99,7 +99,7 @@ static const char *gNumberElementKeys[DecimalFormatSymbols::kFormatSymbolCount] // Initializes this with the decimal format symbols in the default locale. DecimalFormatSymbols::DecimalFormatSymbols(UErrorCode& status) - : UObject(), locale(), currPattern(NULL) { + : UObject(), locale() { initialize(locale, status, true); } @@ -107,17 +107,17 @@ DecimalFormatSymbols::DecimalFormatSymbols(UErrorCode& status) // Initializes this with the decimal format symbols in the desired locale. DecimalFormatSymbols::DecimalFormatSymbols(const Locale& loc, UErrorCode& status) - : UObject(), locale(loc), currPattern(NULL) { + : UObject(), locale(loc) { initialize(locale, status); } DecimalFormatSymbols::DecimalFormatSymbols(const Locale& loc, const NumberingSystem& ns, UErrorCode& status) - : UObject(), locale(loc), currPattern(NULL) { + : UObject(), locale(loc) { initialize(locale, status, false, &ns); } DecimalFormatSymbols::DecimalFormatSymbols() - : UObject(), locale(Locale::getRoot()), currPattern(NULL) { + : UObject(), locale(Locale::getRoot()) { *validLocale = *actualLocale = 0; initialize(); } @@ -169,6 +169,7 @@ DecimalFormatSymbols::operator=(const DecimalFormatSymbols& rhs) fIsCustomIntlCurrencySymbol = rhs.fIsCustomIntlCurrencySymbol; fCodePointZero = rhs.fCodePointZero; currPattern = rhs.currPattern; + uprv_strcpy(nsName, rhs.nsName); } return *this; } @@ -383,6 +384,7 @@ DecimalFormatSymbols::initialize(const Locale& loc, UErrorCode& status, } else { nsName = gLatn; } + uprv_strcpy(this->nsName, nsName); // Open resource bundles const char* locStr = loc.getName(); @@ -517,7 +519,7 @@ DecimalFormatSymbols::initialize() { fCodePointZero = 0x30; U_ASSERT(fCodePointZero == fSymbols[kZeroDigitSymbol].char32At(0)); currPattern = nullptr; - + nsName[0] = 0; } void DecimalFormatSymbols::setCurrency(const UChar* currency, UErrorCode& status) { diff --git a/icu4c/source/i18n/formattedval_impl.h b/icu4c/source/i18n/formattedval_impl.h index 2b9a3970d2e..bc03af46274 100644 --- a/icu4c/source/i18n/formattedval_impl.h +++ b/icu4c/source/i18n/formattedval_impl.h @@ -153,6 +153,9 @@ public: virtual ~FormattedValueStringBuilderImpl(); + FormattedValueStringBuilderImpl(FormattedValueStringBuilderImpl&&) = default; + FormattedValueStringBuilderImpl& operator=(FormattedValueStringBuilderImpl&&) = default; + // Implementation of FormattedValue (const): UnicodeString toString(UErrorCode& status) const U_OVERRIDE; diff --git a/icu4c/source/i18n/i18n.vcxproj b/icu4c/source/i18n/i18n.vcxproj index eef2dd190e6..73ab2d4651e 100644 --- a/icu4c/source/i18n/i18n.vcxproj +++ b/icu4c/source/i18n/i18n.vcxproj @@ -202,6 +202,7 @@ + diff --git a/icu4c/source/i18n/i18n.vcxproj.filters b/icu4c/source/i18n/i18n.vcxproj.filters index d2175c7c768..e35edb5c1b1 100644 --- a/icu4c/source/i18n/i18n.vcxproj.filters +++ b/icu4c/source/i18n/i18n.vcxproj.filters @@ -609,6 +609,9 @@ formatting + + formatting + formatting diff --git a/icu4c/source/i18n/i18n_uwp.vcxproj b/icu4c/source/i18n/i18n_uwp.vcxproj index 25c6c333d7b..9afcb5ddb94 100644 --- a/icu4c/source/i18n/i18n_uwp.vcxproj +++ b/icu4c/source/i18n/i18n_uwp.vcxproj @@ -435,6 +435,7 @@ + diff --git a/icu4c/source/i18n/number_capi.cpp b/icu4c/source/i18n/number_capi.cpp index 42bb05c0661..13b0cd0aef3 100644 --- a/icu4c/source/i18n/number_capi.cpp +++ b/icu4c/source/i18n/number_capi.cpp @@ -16,6 +16,8 @@ #include "number_decnum.h" #include "unicode/numberformatter.h" #include "unicode/unumberformatter.h" +#include "unicode/simplenumberformatter.h" +#include "unicode/usimplenumberformatter.h" using namespace icu; using namespace icu::number; @@ -35,6 +37,24 @@ struct UNumberFormatterData : public UMemory, LocalizedNumberFormatter fFormatter; }; +/** + * Implementation class for USimpleNumber. Wraps a SimpleNumberFormatter. + */ +struct USimpleNumberData : public UMemory, + // Magic number as ASCII == "SNM" (SimpleNuMber) + public IcuCApiHelper { + SimpleNumber fNumber; +}; + +/** + * Implementation class for USimpleNumberFormatter. Wraps a SimpleNumberFormatter. + */ +struct USimpleNumberFormatterData : public UMemory, + // Magic number as ASCII == "SNF" (SimpleNumberFormatter) + public IcuCApiHelper { + SimpleNumberFormatter fFormatter; +}; + struct UFormattedNumberImpl; // Magic number as ASCII == "FDN" (FormatteDNumber) @@ -46,6 +66,8 @@ struct UFormattedNumberImpl : public UFormattedValueImpl, public UFormattedNumbe FormattedNumber fImpl; UFormattedNumberData fData; + + void setTo(FormattedNumber value); }; UFormattedNumberImpl::UFormattedNumberImpl() @@ -58,6 +80,10 @@ UFormattedNumberImpl::~UFormattedNumberImpl() { fImpl.fData = nullptr; } +void UFormattedNumberImpl::setTo(FormattedNumber value) { + fData = std::move(*value.fData); +} + } } U_NAMESPACE_END @@ -225,6 +251,144 @@ unumf_close(UNumberFormatter* f) { } +///// SIMPLE NUMBER FORMATTER ///// + +U_CAPI USimpleNumber* U_EXPORT2 +usnum_openForInt64(int64_t value, UErrorCode* ec) { + auto* impl = new USimpleNumberData(); + if (impl == nullptr) { + *ec = U_MEMORY_ALLOCATION_ERROR; + return nullptr; + } + impl->fNumber = SimpleNumber::forInt64(value, *ec); + return impl->exportForC(); +} + +U_CAPI void U_EXPORT2 +usnum_multiplyByPowerOfTen(USimpleNumber* unumber, int32_t power, UErrorCode* ec) { + auto* number = USimpleNumberData::validate(unumber, *ec); + if (U_FAILURE(*ec)) { + return; + } + number->fNumber.multiplyByPowerOfTen(power, *ec); +} + +U_CAPI void U_EXPORT2 +usnum_roundTo(USimpleNumber* unumber, int32_t position, UNumberFormatRoundingMode roundingMode, UErrorCode* ec) { + auto* number = USimpleNumberData::validate(unumber, *ec); + if (U_FAILURE(*ec)) { + return; + } + number->fNumber.roundTo(position, roundingMode, *ec); +} + +U_CAPI void U_EXPORT2 +usnum_setMinimumIntegerDigits(USimpleNumber* unumber, int32_t minimumIntegerDigits, UErrorCode* ec) { + auto* number = USimpleNumberData::validate(unumber, *ec); + if (U_FAILURE(*ec)) { + return; + } + number->fNumber.setMinimumIntegerDigits(minimumIntegerDigits, *ec); +} + +U_CAPI void U_EXPORT2 +usnum_setMinimumFractionDigits(USimpleNumber* unumber, int32_t minimumFractionDigits, UErrorCode* ec) { + auto* number = USimpleNumberData::validate(unumber, *ec); + if (U_FAILURE(*ec)) { + return; + } + number->fNumber.setMinimumFractionDigits(minimumFractionDigits, *ec); +} + +U_CAPI void U_EXPORT2 +usnum_truncateStart(USimpleNumber* unumber, int32_t maximumIntegerDigits, UErrorCode* ec) { + auto* number = USimpleNumberData::validate(unumber, *ec); + if (U_FAILURE(*ec)) { + return; + } + number->fNumber.truncateStart(maximumIntegerDigits, *ec); +} + +U_CAPI void U_EXPORT2 +usnum_setSign(USimpleNumber* unumber, USimpleNumberSign sign, UErrorCode* ec) { + auto* number = USimpleNumberData::validate(unumber, *ec); + if (U_FAILURE(*ec)) { + return; + } + number->fNumber.setSign(sign, *ec); +} + +U_CAPI USimpleNumberFormatter* U_EXPORT2 +usnumf_openForLocale(const char* locale, UErrorCode* ec) { + auto* impl = new USimpleNumberFormatterData(); + if (impl == nullptr) { + *ec = U_MEMORY_ALLOCATION_ERROR; + return nullptr; + } + impl->fFormatter = SimpleNumberFormatter::forLocale(locale, *ec); + return impl->exportForC(); +} + +U_CAPI USimpleNumberFormatter* U_EXPORT2 +usnumf_openForLocaleAndGroupingStrategy( + const char* locale, UNumberGroupingStrategy groupingStrategy, UErrorCode* ec) { + auto* impl = new USimpleNumberFormatterData(); + if (impl == nullptr) { + *ec = U_MEMORY_ALLOCATION_ERROR; + return nullptr; + } + impl->fFormatter = SimpleNumberFormatter::forLocaleAndGroupingStrategy(locale, groupingStrategy, *ec); + return impl->exportForC(); +} + +U_CAPI void U_EXPORT2 +usnumf_formatAndAdoptNumber( + const USimpleNumberFormatter* uformatter, + USimpleNumber* unumber, + UFormattedNumber* uresult, + UErrorCode* ec) { + auto* formatter = USimpleNumberFormatterData::validate(uformatter, *ec); + auto* number = USimpleNumberData::validate(unumber, *ec); + auto* result = UFormattedNumberApiHelper::validate(uresult, *ec); + if (U_FAILURE(*ec)) { + delete number; + return; + } + auto localResult = formatter->fFormatter.format(std::move(number->fNumber), *ec); + result->setTo(std::move(localResult)); + delete number; +} + +U_CAPI void U_EXPORT2 +usnumf_formatInt64( + const USimpleNumberFormatter* uformatter, + int64_t value, + UFormattedNumber* uresult, + UErrorCode* ec) { + auto* formatter = USimpleNumberFormatterData::validate(uformatter, *ec); + auto* result = UFormattedNumberApiHelper::validate(uresult, *ec); + if (U_FAILURE(*ec)) { + return; + } + auto localResult = formatter->fFormatter.formatInt64(value, *ec); + result->setTo(std::move(localResult)); +} + +U_CAPI void U_EXPORT2 +usnum_close(USimpleNumber* unumber) { + UErrorCode localStatus = U_ZERO_ERROR; + const USimpleNumberData* impl = USimpleNumberData::validate(unumber, localStatus); + delete impl; +} + +U_CAPI void U_EXPORT2 +usnumf_close(USimpleNumberFormatter* uformatter) { + UErrorCode localStatus = U_ZERO_ERROR; + const USimpleNumberFormatterData* impl = USimpleNumberFormatterData::validate(uformatter, localStatus); + delete impl; +} + + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp index 4fb190b744b..53bac49a55c 100644 --- a/icu4c/source/i18n/number_formatimpl.cpp +++ b/icu4c/source/i18n/number_formatimpl.cpp @@ -36,7 +36,7 @@ int32_t NumberFormatterImpl::formatStatic(const MacroProps ¯os, UFormattedNu NumberFormatterImpl impl(macros, false, status); MicroProps& micros = impl.preProcessUnsafe(inValue, status); if (U_FAILURE(status)) { return 0; } - int32_t length = writeNumber(micros, inValue, outString, 0, status); + int32_t length = writeNumber(micros.simple, inValue, outString, 0, status); length += writeAffixes(micros, outString, 0, length, status); results->outputUnit = std::move(micros.outputUnit); results->gender = micros.gender; @@ -61,7 +61,7 @@ int32_t NumberFormatterImpl::format(UFormattedNumberData *results, UErrorCode &s MicroProps micros; preProcess(inValue, micros, status); if (U_FAILURE(status)) { return 0; } - int32_t length = writeNumber(micros, inValue, outString, 0, status); + int32_t length = writeNumber(micros.simple, inValue, outString, 0, status); length += writeAffixes(micros, outString, 0, length, status); results->outputUnit = std::move(micros.outputUnit); results->gender = micros.gender; @@ -186,7 +186,7 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, // Resolve the symbols. Do this here because currency may need to customize them. if (macros.symbols.isDecimalFormatSymbols()) { - fMicros.symbols = macros.symbols.getDecimalFormatSymbols(); + fMicros.simple.symbols = macros.symbols.getDecimalFormatSymbols(); } else { LocalPointer newSymbols( new DecimalFormatSymbols(macros.locale, *ns, status), status); @@ -199,15 +199,15 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, return nullptr; } } - fMicros.symbols = newSymbols.getAlias(); + fMicros.simple.symbols = newSymbols.getAlias(); fSymbols.adoptInstead(newSymbols.orphan()); } // Load and parse the pattern string. It is used for grouping sizes and affixes only. // If we are formatting currency, check for a currency-specific pattern. const char16_t* pattern = nullptr; - if (isCurrency && fMicros.symbols->getCurrencyPattern() != nullptr) { - pattern = fMicros.symbols->getCurrencyPattern(); + if (isCurrency && fMicros.simple.symbols->getCurrencyPattern() != nullptr) { + pattern = fMicros.simple.symbols->getCurrencyPattern(); } if (pattern == nullptr) { CldrPatternStyle patternStyle; @@ -291,14 +291,14 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, // Grouping strategy if (!macros.grouper.isBogus()) { - fMicros.grouping = macros.grouper; + fMicros.simple.grouping = macros.grouper; } else if (isCompactNotation) { // Compact notation uses minGrouping by default since ICU 59 - fMicros.grouping = Grouper::forStrategy(UNUM_GROUPING_MIN2); + fMicros.simple.grouping = Grouper::forStrategy(UNUM_GROUPING_MIN2); } else { - fMicros.grouping = Grouper::forStrategy(UNUM_GROUPING_AUTO); + fMicros.simple.grouping = Grouper::forStrategy(UNUM_GROUPING_AUTO); } - fMicros.grouping.setLocaleData(*fPatternInfo, macros.locale); + fMicros.simple.grouping.setLocaleData(*fPatternInfo, macros.locale); // Padding strategy if (!macros.padder.isBogus()) { @@ -323,17 +323,17 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, // Decimal mark display if (macros.decimal != UNUM_DECIMAL_SEPARATOR_COUNT) { - fMicros.decimal = macros.decimal; + fMicros.simple.decimal = macros.decimal; } else { - fMicros.decimal = UNUM_DECIMAL_SEPARATOR_AUTO; + fMicros.simple.decimal = UNUM_DECIMAL_SEPARATOR_AUTO; } // Use monetary separator symbols - fMicros.useCurrency = isCurrency; + fMicros.simple.useCurrency = isCurrency; // Inner modifier (scientific notation) if (macros.notation.fType == Notation::NTN_SCIENTIFIC) { - auto newScientificHandler = new ScientificHandler(¯os.notation, fMicros.symbols, chain); + auto newScientificHandler = new ScientificHandler(¯os.notation, fMicros.simple.symbols, chain); if (newScientificHandler == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; return nullptr; @@ -362,13 +362,13 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, patternModifier->setPatternAttributes(fMicros.sign, isPermille, macros.approximately); if (patternModifier->needsPlurals()) { patternModifier->setSymbols( - fMicros.symbols, + fMicros.simple.symbols, currency, unitWidth, resolvePluralRules(macros.rules, macros.locale, status), status); } else { - patternModifier->setSymbols(fMicros.symbols, currency, unitWidth, nullptr, status); + patternModifier->setSymbols(fMicros.simple.symbols, currency, unitWidth, nullptr, status); } if (safe) { fImmutablePatternModifier.adoptInsteadAndCheckErrorCode(patternModifier->createImmutable(status), @@ -380,7 +380,7 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, // currencyAsDecimal if (affixProvider->currencyAsDecimal()) { - fMicros.currencyAsDecimal = patternModifier->getCurrencySymbolForUnitWidth(status); + fMicros.simple.currencyAsDecimal = patternModifier->getCurrencySymbolForUnitWidth(status); } // Outer modifier (CLDR units and currency long names) @@ -481,8 +481,10 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, } const PluralRules* -NumberFormatterImpl::resolvePluralRules(const PluralRules* rulesPtr, const Locale& locale, - UErrorCode& status) { +NumberFormatterImpl::resolvePluralRules( + const PluralRules* rulesPtr, + const Locale& locale, + UErrorCode& status) { if (rulesPtr != nullptr) { return rulesPtr; } @@ -493,8 +495,12 @@ NumberFormatterImpl::resolvePluralRules(const PluralRules* rulesPtr, const Local return fRules.getAlias(); } -int32_t NumberFormatterImpl::writeAffixes(const MicroProps& micros, FormattedStringBuilder& string, - int32_t start, int32_t end, UErrorCode& status) { +int32_t NumberFormatterImpl::writeAffixes( + const MicroProps& micros, + FormattedStringBuilder& string, + int32_t start, + int32_t end, + UErrorCode& status) { U_ASSERT(micros.modOuter != nullptr); // Always apply the inner modifier (which is "strong"). int32_t length = micros.modInner->apply(string, start, end, status); @@ -508,9 +514,12 @@ int32_t NumberFormatterImpl::writeAffixes(const MicroProps& micros, FormattedStr return length; } -int32_t NumberFormatterImpl::writeNumber(const MicroProps& micros, DecimalQuantity& quantity, - FormattedStringBuilder& string, int32_t index, - UErrorCode& status) { +int32_t NumberFormatterImpl::writeNumber( + const SimpleMicroProps& micros, + DecimalQuantity& quantity, + FormattedStringBuilder& string, + int32_t index, + UErrorCode& status) { int32_t length = 0; if (quantity.isInfinite()) { length += string.insert( @@ -528,7 +537,12 @@ int32_t NumberFormatterImpl::writeNumber(const MicroProps& micros, DecimalQuanti } else { // Add the integer digits - length += writeIntegerDigits(micros, quantity, string, length + index, status); + length += writeIntegerDigits( + micros, + quantity, + string, + length + index, + status); // Add the decimal point if (quantity.getLowerDisplayMagnitude() < 0 || micros.decimal == UNUM_DECIMAL_SEPARATOR_ALWAYS) { @@ -573,9 +587,12 @@ int32_t NumberFormatterImpl::writeNumber(const MicroProps& micros, DecimalQuanti return length; } -int32_t NumberFormatterImpl::writeIntegerDigits(const MicroProps& micros, DecimalQuantity& quantity, - FormattedStringBuilder& string, int32_t index, - UErrorCode& status) { +int32_t NumberFormatterImpl::writeIntegerDigits( + const SimpleMicroProps& micros, + DecimalQuantity& quantity, + FormattedStringBuilder& string, + int32_t index, + UErrorCode& status) { int length = 0; int integerCount = quantity.getUpperDisplayMagnitude() + 1; for (int i = 0; i < integerCount; i++) { @@ -605,9 +622,12 @@ int32_t NumberFormatterImpl::writeIntegerDigits(const MicroProps& micros, Decima return length; } -int32_t NumberFormatterImpl::writeFractionDigits(const MicroProps& micros, DecimalQuantity& quantity, - FormattedStringBuilder& string, int32_t index, - UErrorCode& status) { +int32_t NumberFormatterImpl::writeFractionDigits( + const SimpleMicroProps& micros, + DecimalQuantity& quantity, + FormattedStringBuilder& string, + int32_t index, + UErrorCode& status) { int length = 0; int fractionCount = -quantity.getLowerDisplayMagnitude(); for (int i = 0; i < fractionCount; i++) { diff --git a/icu4c/source/i18n/number_formatimpl.h b/icu4c/source/i18n/number_formatimpl.h index d7be1468b6d..62d53212616 100644 --- a/icu4c/source/i18n/number_formatimpl.h +++ b/icu4c/source/i18n/number_formatimpl.h @@ -79,14 +79,22 @@ class NumberFormatterImpl : public UMemory { * Synthesizes the output string from a MicroProps and DecimalQuantity. * This method formats only the main number, not affixes. */ - static int32_t writeNumber(const MicroProps& micros, DecimalQuantity& quantity, - FormattedStringBuilder& string, int32_t index, UErrorCode& status); + static int32_t writeNumber( + const SimpleMicroProps& micros, + DecimalQuantity& quantity, + FormattedStringBuilder& string, + int32_t index, + UErrorCode& status); /** * Adds the affixes. Intended to be called immediately after formatNumber. */ - static int32_t writeAffixes(const MicroProps& micros, FormattedStringBuilder& string, int32_t start, - int32_t end, UErrorCode& status); + static int32_t writeAffixes( + const MicroProps& micros, + FormattedStringBuilder& string, + int32_t start, + int32_t end, + UErrorCode& status); private: // Head of the MicroPropsGenerator linked list. Subclasses' processQuantity @@ -146,12 +154,20 @@ class NumberFormatterImpl : public UMemory { macrosToMicroGenerator(const MacroProps ¯os, bool safe, UErrorCode &status); static int32_t - writeIntegerDigits(const MicroProps µs, DecimalQuantity &quantity, FormattedStringBuilder &string, - int32_t index, UErrorCode &status); + writeIntegerDigits( + const SimpleMicroProps& micros, + DecimalQuantity &quantity, + FormattedStringBuilder &string, + int32_t index, + UErrorCode &status); static int32_t - writeFractionDigits(const MicroProps µs, DecimalQuantity &quantity, FormattedStringBuilder &string, - int32_t index, UErrorCode &status); + writeFractionDigits( + const SimpleMicroProps& micros, + DecimalQuantity &quantity, + FormattedStringBuilder &string, + int32_t index, + UErrorCode &status); }; } // namespace impl diff --git a/icu4c/source/i18n/number_microprops.h b/icu4c/source/i18n/number_microprops.h index c34e7c17e97..8d213548d1d 100644 --- a/icu4c/source/i18n/number_microprops.h +++ b/icu4c/source/i18n/number_microprops.h @@ -67,33 +67,39 @@ class IntMeasures : public MaybeStackArray { UErrorCode status = U_ZERO_ERROR; }; +struct SimpleMicroProps : public UMemory { + Grouper grouping; + bool useCurrency = false; + UNumberDecimalSeparatorDisplay decimal = UNUM_DECIMAL_SEPARATOR_AUTO; + + // Currency symbol to be used as the decimal separator + UnicodeString currencyAsDecimal = ICU_Utility::makeBogusString(); + + // Note: This struct has no direct ownership of the following pointer. + const DecimalFormatSymbols* symbols = nullptr; +}; + /** * MicroProps is the first MicroPropsGenerator that should be should be called, * producing an initialized MicroProps instance that will be passed on and * modified throughout the rest of the chain of MicroPropsGenerator instances. */ struct MicroProps : public MicroPropsGenerator { + SimpleMicroProps simple; // NOTE: All of these fields are properly initialized in NumberFormatterImpl. RoundingImpl rounder; - Grouper grouping; Padder padding; IntegerWidth integerWidth; UNumberSignDisplay sign; - UNumberDecimalSeparatorDisplay decimal; - bool useCurrency; char nsName[9]; - // Currency symbol to be used as the decimal separator - UnicodeString currencyAsDecimal = ICU_Utility::makeBogusString(); - // No ownership: must point at a string which will outlive MicroProps // instances, e.g. a string with static storage duration, or just a string // that will never be deallocated or modified. const char *gender; // Note: This struct has no direct ownership of the following pointers. - const DecimalFormatSymbols* symbols; // Pointers to Modifiers provided by the number formatting pipeline (when // the value is known): diff --git a/icu4c/source/i18n/number_modifiers.cpp b/icu4c/source/i18n/number_modifiers.cpp index 092b66ff579..d2c58660883 100644 --- a/icu4c/source/i18n/number_modifiers.cpp +++ b/icu4c/source/i18n/number_modifiers.cpp @@ -62,12 +62,21 @@ Modifier::Parameters::Parameters( ModifierStore::~ModifierStore() = default; -AdoptingModifierStore::~AdoptingModifierStore() { +AdoptingSignumModifierStore::~AdoptingSignumModifierStore() { for (const Modifier *mod : mods) { delete mod; } } +AdoptingSignumModifierStore& +AdoptingSignumModifierStore::operator=(AdoptingSignumModifierStore&& other) U_NOEXCEPT { + for (size_t i=0; imods[i] = other.mods[i]; + other.mods[i] = nullptr; + } + return *this; +} + int32_t ConstantAffixModifier::apply(FormattedStringBuilder &output, int leftIndex, int rightIndex, UErrorCode &status) const { diff --git a/icu4c/source/i18n/number_modifiers.h b/icu4c/source/i18n/number_modifiers.h index 09af3f48813..fa8dd52913e 100644 --- a/icu4c/source/i18n/number_modifiers.h +++ b/icu4c/source/i18n/number_modifiers.h @@ -272,13 +272,45 @@ class U_I18N_API EmptyModifier : public Modifier, public UMemory { bool fStrong; }; +/** An adopting Modifier store that varies by signum but not plural form. */ +class U_I18N_API AdoptingSignumModifierStore : public UMemory { + public: + virtual ~AdoptingSignumModifierStore(); + + AdoptingSignumModifierStore() = default; + + // No copying! + AdoptingSignumModifierStore(const AdoptingSignumModifierStore &other) = delete; + AdoptingSignumModifierStore& operator=(const AdoptingSignumModifierStore& other) = delete; + + // Moving is OK + AdoptingSignumModifierStore(AdoptingSignumModifierStore &&other) U_NOEXCEPT { + *this = std::move(other); + } + AdoptingSignumModifierStore& operator=(AdoptingSignumModifierStore&& other) U_NOEXCEPT; + + /** Take ownership of the Modifier and slot it in at the given Signum. */ + void adoptModifier(Signum signum, const Modifier* mod) { + U_ASSERT(mods[signum] == nullptr); + mods[signum] = mod; + } + + inline const Modifier*& operator[](Signum signum) { + return mods[signum]; + } + inline Modifier const* operator[](Signum signum) const { + return mods[signum]; + } + + private: + const Modifier* mods[SIGNUM_COUNT] = {}; +}; + /** * This implementation of ModifierStore adopts Modifier pointers. */ class U_I18N_API AdoptingModifierStore : public ModifierStore, public UMemory { public: - virtual ~AdoptingModifierStore(); - static constexpr StandardPlural::Form DEFAULT_STANDARD_PLURAL = StandardPlural::OTHER; AdoptingModifierStore() = default; @@ -286,46 +318,36 @@ class U_I18N_API AdoptingModifierStore : public ModifierStore, public UMemory { // No copying! AdoptingModifierStore(const AdoptingModifierStore &other) = delete; - /** - * Sets the Modifier with the specified signum and plural form. - */ - void adoptModifier(Signum signum, StandardPlural::Form plural, const Modifier *mod) { - U_ASSERT(mods[getModIndex(signum, plural)] == nullptr); - mods[getModIndex(signum, plural)] = mod; + // Moving is OK + AdoptingModifierStore(AdoptingModifierStore &&other) = default; + + /** Sets the modifiers for a specific plural form. */ + void adoptSignumModifierStore(StandardPlural::Form plural, AdoptingSignumModifierStore other) { + mods[plural] = std::move(other); } - /** - * Sets the Modifier with the specified signum. - * The modifier will apply to all plural forms. - */ - void adoptModifierWithoutPlural(Signum signum, const Modifier *mod) { - U_ASSERT(mods[getModIndex(signum, DEFAULT_STANDARD_PLURAL)] == nullptr); - mods[getModIndex(signum, DEFAULT_STANDARD_PLURAL)] = mod; + /** Sets the modifiers for the default plural form. */ + void adoptSignumModifierStoreNoPlural(AdoptingSignumModifierStore other) { + mods[DEFAULT_STANDARD_PLURAL] = std::move(other); } /** Returns a reference to the modifier; no ownership change. */ const Modifier *getModifier(Signum signum, StandardPlural::Form plural) const U_OVERRIDE { - const Modifier* modifier = mods[getModIndex(signum, plural)]; + const Modifier* modifier = mods[plural][signum]; if (modifier == nullptr && plural != DEFAULT_STANDARD_PLURAL) { - modifier = mods[getModIndex(signum, DEFAULT_STANDARD_PLURAL)]; + modifier = mods[DEFAULT_STANDARD_PLURAL][signum]; } return modifier; } /** Returns a reference to the modifier; no ownership change. */ const Modifier *getModifierWithoutPlural(Signum signum) const { - return mods[getModIndex(signum, DEFAULT_STANDARD_PLURAL)]; + return mods[DEFAULT_STANDARD_PLURAL][signum]; } private: // NOTE: mods is zero-initialized (to nullptr) - const Modifier *mods[4 * StandardPlural::COUNT] = {}; - - inline static int32_t getModIndex(Signum signum, StandardPlural::Form plural) { - U_ASSERT(signum >= 0 && signum < SIGNUM_COUNT); - U_ASSERT(plural >= 0 && plural < StandardPlural::COUNT); - return static_cast(plural) * SIGNUM_COUNT + signum; - } + AdoptingSignumModifierStore mods[StandardPlural::COUNT] = {}; }; } // namespace impl diff --git a/icu4c/source/i18n/number_patternmodifier.cpp b/icu4c/source/i18n/number_patternmodifier.cpp index 088a30ecd7f..6f398c6acf0 100644 --- a/icu4c/source/i18n/number_patternmodifier.cpp +++ b/icu4c/source/i18n/number_patternmodifier.cpp @@ -60,6 +60,21 @@ bool MutablePatternModifier::needsPlurals() const { // Silently ignore any error codes. } +AdoptingSignumModifierStore MutablePatternModifier::createImmutableForPlural(StandardPlural::Form plural, UErrorCode& status) { + AdoptingSignumModifierStore pm; + + setNumberProperties(SIGNUM_POS, plural); + pm.adoptModifier(SIGNUM_POS, createConstantModifier(status)); + setNumberProperties(SIGNUM_NEG_ZERO, plural); + pm.adoptModifier(SIGNUM_NEG_ZERO, createConstantModifier(status)); + setNumberProperties(SIGNUM_POS_ZERO, plural); + pm.adoptModifier(SIGNUM_POS_ZERO, createConstantModifier(status)); + setNumberProperties(SIGNUM_NEG, plural); + pm.adoptModifier(SIGNUM_NEG, createConstantModifier(status)); + + return pm; +} + ImmutablePatternModifier* MutablePatternModifier::createImmutable(UErrorCode& status) { // TODO: Move StandardPlural VALUES to standardplural.h static const StandardPlural::Form STANDARD_PLURAL_VALUES[] = { @@ -79,14 +94,7 @@ ImmutablePatternModifier* MutablePatternModifier::createImmutable(UErrorCode& st if (needsPlurals()) { // Slower path when we require the plural keyword. for (StandardPlural::Form plural : STANDARD_PLURAL_VALUES) { - setNumberProperties(SIGNUM_POS, plural); - pm->adoptModifier(SIGNUM_POS, plural, createConstantModifier(status)); - setNumberProperties(SIGNUM_NEG_ZERO, plural); - pm->adoptModifier(SIGNUM_NEG_ZERO, plural, createConstantModifier(status)); - setNumberProperties(SIGNUM_POS_ZERO, plural); - pm->adoptModifier(SIGNUM_POS_ZERO, plural, createConstantModifier(status)); - setNumberProperties(SIGNUM_NEG, plural); - pm->adoptModifier(SIGNUM_NEG, plural, createConstantModifier(status)); + pm->adoptSignumModifierStore(plural, createImmutableForPlural(plural, status)); } if (U_FAILURE(status)) { delete pm; @@ -95,14 +103,7 @@ ImmutablePatternModifier* MutablePatternModifier::createImmutable(UErrorCode& st return new ImmutablePatternModifier(pm, fRules); // adopts pm } else { // Faster path when plural keyword is not needed. - setNumberProperties(SIGNUM_POS, StandardPlural::Form::COUNT); - pm->adoptModifierWithoutPlural(SIGNUM_POS, createConstantModifier(status)); - setNumberProperties(SIGNUM_NEG_ZERO, StandardPlural::Form::COUNT); - pm->adoptModifierWithoutPlural(SIGNUM_NEG_ZERO, createConstantModifier(status)); - setNumberProperties(SIGNUM_POS_ZERO, StandardPlural::Form::COUNT); - pm->adoptModifierWithoutPlural(SIGNUM_POS_ZERO, createConstantModifier(status)); - setNumberProperties(SIGNUM_NEG, StandardPlural::Form::COUNT); - pm->adoptModifierWithoutPlural(SIGNUM_NEG, createConstantModifier(status)); + pm->adoptSignumModifierStoreNoPlural(createImmutableForPlural(StandardPlural::Form::COUNT, status)); if (U_FAILURE(status)) { delete pm; return nullptr; diff --git a/icu4c/source/i18n/number_patternmodifier.h b/icu4c/source/i18n/number_patternmodifier.h index 4f825e1ed21..45a72669ace 100644 --- a/icu4c/source/i18n/number_patternmodifier.h +++ b/icu4c/source/i18n/number_patternmodifier.h @@ -156,6 +156,9 @@ class U_I18N_API MutablePatternModifier */ bool needsPlurals() const; + /** Creates a quantity-dependent Modifier for the specified plural form. */ + AdoptingSignumModifierStore createImmutableForPlural(StandardPlural::Form plural, UErrorCode& status); + /** * Creates a new quantity-dependent Modifier that behaves the same as the current instance, but which is immutable * and can be saved for future use. The number properties in the current instance are mutated; all other properties diff --git a/icu4c/source/i18n/number_simple.cpp b/icu4c/source/i18n/number_simple.cpp new file mode 100644 index 00000000000..a2af6be42d6 --- /dev/null +++ b/icu4c/source/i18n/number_simple.cpp @@ -0,0 +1,255 @@ +// © 2017 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +#include "unicode/numberformatter.h" +#include "unicode/simplenumberformatter.h" +#include "number_formatimpl.h" +#include "number_utils.h" +#include "number_patternmodifier.h" +#include "number_utypes.h" + +using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; + + +SimpleNumber +SimpleNumber::forInt64(int64_t value, UErrorCode& status) { + if (U_FAILURE(status)) { + return SimpleNumber(); + } + auto results = new UFormattedNumberData(); + if (results == nullptr) { + status = U_MEMORY_ALLOCATION_ERROR; + return SimpleNumber(); + } + results->quantity.setToLong(value); + return SimpleNumber(results, status); +} + +SimpleNumber::SimpleNumber(UFormattedNumberData* data, UErrorCode& status) : fData(data) { + if (U_FAILURE(status)) { + return; + } + if (fData == nullptr) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + if (fData->quantity.isNegative()) { + fSign = UNUM_SIMPLE_NUMBER_MINUS_SIGN; + } else { + fSign = UNUM_SIMPLE_NUMBER_NO_SIGN; + } +} + +void SimpleNumber::cleanup() { + delete fData; + fData = nullptr; +} + +void SimpleNumber::multiplyByPowerOfTen(int32_t power, UErrorCode& status) { + if (U_FAILURE(status)) { + return; + } + if (fData == nullptr) { + status = U_INVALID_STATE_ERROR; + return; + } + fData->quantity.adjustMagnitude(power); +} + +void SimpleNumber::roundTo(int32_t position, UNumberFormatRoundingMode roundingMode, UErrorCode& status) { + if (U_FAILURE(status)) { + return; + } + if (fData == nullptr) { + status = U_INVALID_STATE_ERROR; + return; + } + fData->quantity.roundToMagnitude(position, roundingMode, status); +} + +void SimpleNumber::setMinimumIntegerDigits(uint32_t position, UErrorCode& status) { + if (U_FAILURE(status)) { + return; + } + if (fData == nullptr) { + status = U_INVALID_STATE_ERROR; + return; + } + fData->quantity.setMinInteger(position); +} + +void SimpleNumber::setMinimumFractionDigits(uint32_t position, UErrorCode& status) { + if (U_FAILURE(status)) { + return; + } + if (fData == nullptr) { + status = U_INVALID_STATE_ERROR; + return; + } + fData->quantity.setMinFraction(position); +} + +void SimpleNumber::truncateStart(uint32_t position, UErrorCode& status) { + if (U_FAILURE(status)) { + return; + } + if (fData == nullptr) { + status = U_INVALID_STATE_ERROR; + return; + } + fData->quantity.applyMaxInteger(position); +} + +void SimpleNumber::setSign(USimpleNumberSign sign, UErrorCode& status) { + if (U_FAILURE(status)) { + return; + } + if (fData == nullptr) { + status = U_INVALID_STATE_ERROR; + return; + } + fSign = sign; +} + + +void SimpleNumberFormatter::cleanup() { + delete fOwnedSymbols; + delete fMicros; + delete fPatternModifier; + fOwnedSymbols = nullptr; + fMicros = nullptr; + fPatternModifier = nullptr; +} + +SimpleNumberFormatter SimpleNumberFormatter::forLocale(const icu::Locale &locale, UErrorCode &status) { + return SimpleNumberFormatter::forLocaleAndGroupingStrategy(locale, UNUM_GROUPING_AUTO, status); +} + +SimpleNumberFormatter SimpleNumberFormatter::forLocaleAndGroupingStrategy( + const icu::Locale &locale, + UNumberGroupingStrategy groupingStrategy, + UErrorCode &status) { + SimpleNumberFormatter retval; + retval.fOwnedSymbols = new DecimalFormatSymbols(locale, status); + if (U_FAILURE(status)) { + return retval; + } + if (retval.fOwnedSymbols == nullptr) { + status = U_MEMORY_ALLOCATION_ERROR; + return retval; + } + retval.initialize(locale, *retval.fOwnedSymbols, groupingStrategy, status); + return retval; +} + + +SimpleNumberFormatter SimpleNumberFormatter::forLocaleAndSymbolsAndGroupingStrategy( + const icu::Locale &locale, + const DecimalFormatSymbols &symbols, + UNumberGroupingStrategy groupingStrategy, + UErrorCode &status) { + SimpleNumberFormatter retval; + retval.initialize(locale, symbols, groupingStrategy, status); + return retval; +} + + +void SimpleNumberFormatter::initialize( + const icu::Locale &locale, + const DecimalFormatSymbols &symbols, + UNumberGroupingStrategy groupingStrategy, + UErrorCode &status) { + if (U_FAILURE(status)) { + return; + } + + fMicros = new SimpleMicroProps(); + if (fMicros == nullptr) { + status = U_MEMORY_ALLOCATION_ERROR; + return; + } + fMicros->symbols = &symbols; + + auto pattern = utils::getPatternForStyle( + locale, + symbols.getNumberingSystemName(), + CLDR_PATTERN_STYLE_DECIMAL, + status); + if (U_FAILURE(status)) { + return; + } + + ParsedPatternInfo patternInfo; + PatternParser::parseToPatternInfo(UnicodeString(pattern), patternInfo, status); + if (U_FAILURE(status)) { + return; + } + + auto grouper = Grouper::forStrategy(groupingStrategy); + grouper.setLocaleData(patternInfo, locale); + fMicros->grouping = grouper; + + MutablePatternModifier patternModifier(false); + patternModifier.setPatternInfo(&patternInfo, kUndefinedField); + patternModifier.setPatternAttributes(UNUM_SIGN_EXCEPT_ZERO, false, false); + patternModifier.setSymbols(fMicros->symbols, {}, UNUM_UNIT_WIDTH_SHORT, nullptr, status); + + fPatternModifier = new AdoptingSignumModifierStore(patternModifier.createImmutableForPlural(StandardPlural::COUNT, status)); + + fGroupingStrategy = groupingStrategy; + return; +} + +FormattedNumber SimpleNumberFormatter::format(SimpleNumber value, UErrorCode &status) const { + formatImpl(value.fData, value.fSign, status); + + // Do not save the results object if we encountered a failure. + if (U_SUCCESS(status)) { + auto temp = value.fData; + value.fData = nullptr; + return FormattedNumber(temp); + } else { + return FormattedNumber(status); + } +} + +void SimpleNumberFormatter::formatImpl(UFormattedNumberData* data, USimpleNumberSign sign, UErrorCode &status) const { + if (U_FAILURE(status)) { + return; + } + if (data == nullptr) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + if (fPatternModifier == nullptr || fMicros == nullptr) { + status = U_INVALID_STATE_ERROR; + return; + } + + Signum signum; + if (sign == UNUM_SIMPLE_NUMBER_MINUS_SIGN) { + signum = SIGNUM_NEG; + } else if (sign == UNUM_SIMPLE_NUMBER_PLUS_SIGN) { + signum = SIGNUM_POS; + } else { + signum = SIGNUM_POS_ZERO; + } + + const Modifier* modifier = (*fPatternModifier)[signum]; + auto length = NumberFormatterImpl::writeNumber( + *fMicros, + data->quantity, + data->getStringRef(), + 0, + status); + length += modifier->apply(data->getStringRef(), 0, length, status); + data->getStringRef().writeTerminator(status); +} + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_utypes.h b/icu4c/source/i18n/number_utypes.h index 50c861787f4..0c130401894 100644 --- a/icu4c/source/i18n/number_utypes.h +++ b/icu4c/source/i18n/number_utypes.h @@ -35,6 +35,9 @@ public: UFormattedNumberData() : FormattedValueStringBuilderImpl(kUndefinedField) {} virtual ~UFormattedNumberData(); + UFormattedNumberData(UFormattedNumberData&&) = default; + UFormattedNumberData& operator=(UFormattedNumberData&&) = default; + // The formatted quantity. DecimalQuantity quantity; diff --git a/icu4c/source/i18n/numrange_impl.cpp b/icu4c/source/i18n/numrange_impl.cpp index 06efc7b2815..ffee174dcca 100644 --- a/icu4c/source/i18n/numrange_impl.cpp +++ b/icu4c/source/i18n/numrange_impl.cpp @@ -239,7 +239,7 @@ void NumberRangeFormatterImpl::formatSingleValue(UFormattedNumberRangeData& data UErrorCode& status) const { if (U_FAILURE(status)) { return; } if (fSameFormatters) { - int32_t length = NumberFormatterImpl::writeNumber(micros1, data.quantity1, data.getStringRef(), 0, status); + int32_t length = NumberFormatterImpl::writeNumber(micros1.simple, data.quantity1, data.getStringRef(), 0, status); NumberFormatterImpl::writeAffixes(micros1, data.getStringRef(), 0, length, status); } else { formatRange(data, micros1, micros2, status); @@ -256,7 +256,7 @@ void NumberRangeFormatterImpl::formatApproximately (UFormattedNumberRangeData& d MicroProps microsAppx; data.quantity1.resetExponent(); fApproximatelyFormatter.preProcess(data.quantity1, microsAppx, status); - int32_t length = NumberFormatterImpl::writeNumber(microsAppx, data.quantity1, data.getStringRef(), 0, status); + int32_t length = NumberFormatterImpl::writeNumber(microsAppx.simple, data.quantity1, data.getStringRef(), 0, status); length += microsAppx.modInner->apply(data.getStringRef(), 0, length, status); length += microsAppx.modMiddle->apply(data.getStringRef(), 0, length, status); microsAppx.modOuter->apply(data.getStringRef(), 0, length, status); @@ -384,10 +384,10 @@ void NumberRangeFormatterImpl::formatRange(UFormattedNumberRangeData& data, } } - length1 += NumberFormatterImpl::writeNumber(micros1, data.quantity1, string, UPRV_INDEX_0, status); + length1 += NumberFormatterImpl::writeNumber(micros1.simple, data.quantity1, string, UPRV_INDEX_0, status); // ICU-21684: Write the second number to a temp string to avoid repeated insert operations FormattedStringBuilder tempString; - NumberFormatterImpl::writeNumber(micros2, data.quantity2, tempString, 0, status); + NumberFormatterImpl::writeNumber(micros2.simple, data.quantity2, tempString, 0, status); length2 += string.insert(UPRV_INDEX_2, tempString, status); // TODO: Support padding? diff --git a/icu4c/source/i18n/sources.txt b/icu4c/source/i18n/sources.txt index 69705c87b4e..a1af43b93e8 100644 --- a/icu4c/source/i18n/sources.txt +++ b/icu4c/source/i18n/sources.txt @@ -122,6 +122,7 @@ number_patternmodifier.cpp number_patternstring.cpp number_rounding.cpp number_scientific.cpp +number_simple.cpp number_skeletons.cpp number_symbolswrapper.cpp number_usageprefs.cpp diff --git a/icu4c/source/i18n/unicode/dcfmtsym.h b/icu4c/source/i18n/unicode/dcfmtsym.h index c5f7404416f..c0a94c42bc6 100644 --- a/icu4c/source/i18n/unicode/dcfmtsym.h +++ b/icu4c/source/i18n/unicode/dcfmtsym.h @@ -456,6 +456,12 @@ public: * @internal */ inline const char16_t* getCurrencyPattern(void) const; + + /** + * Returns the numbering system with which this DecimalFormatSymbols was initialized. + * @internal + */ + inline const char* getNumberingSystemName(void) const; #endif /* U_HIDE_INTERNAL_API */ private: @@ -500,12 +506,13 @@ private: char actualLocale[ULOC_FULLNAME_CAPACITY]; char validLocale[ULOC_FULLNAME_CAPACITY]; - const char16_t* currPattern; + const char16_t* currPattern = nullptr; UnicodeString currencySpcBeforeSym[UNUM_CURRENCY_SPACING_COUNT]; UnicodeString currencySpcAfterSym[UNUM_CURRENCY_SPACING_COUNT]; UBool fIsCustomCurrencySymbol; UBool fIsCustomIntlCurrencySymbol; + char nsName[kInternalNumSysNameCapacity+1] = {}; }; // ------------------------------------- @@ -591,6 +598,10 @@ inline const char16_t* DecimalFormatSymbols::getCurrencyPattern() const { return currPattern; } +inline const char* +DecimalFormatSymbols::getNumberingSystemName() const { + return nsName; +} #endif /* U_HIDE_INTERNAL_API */ U_NAMESPACE_END diff --git a/icu4c/source/i18n/unicode/formattednumber.h b/icu4c/source/i18n/unicode/formattednumber.h new file mode 100644 index 00000000000..af72862579d --- /dev/null +++ b/icu4c/source/i18n/unicode/formattednumber.h @@ -0,0 +1,215 @@ +// © 2022 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#ifndef __FORMATTEDNUMBER_H__ +#define __FORMATTEDNUMBER_H__ + +#include "unicode/utypes.h" + +#if U_SHOW_CPLUSPLUS_API + +#if !UCONFIG_NO_FORMATTING + +#include "unicode/uobject.h" +#include "unicode/formattedvalue.h" +#include "unicode/measunit.h" +#include "unicode/udisplayoptions.h" + +/** + * \file + * \brief C API: Formatted number result from various number formatting functions. + * + * See also {@link icu::FormattedValue} for additional things you can do with a FormattedNumber. + */ + +U_NAMESPACE_BEGIN + +class FieldPositionIteratorHandler; + +namespace number { // icu::number + +namespace impl { +class DecimalQuantity; +class UFormattedNumberData; +struct UFormattedNumberImpl; +} // icu::number::impl + + + +/** + * The result of a number formatting operation. This class allows the result to be exported in several data types, + * including a UnicodeString and a FieldPositionIterator. + * + * Instances of this class are immutable and thread-safe. + * + * @stable ICU 60 + */ +class U_I18N_API FormattedNumber : public UMemory, public FormattedValue { + public: + + /** + * Default constructor; makes an empty FormattedNumber. + * @stable ICU 64 + */ + FormattedNumber() + : fData(nullptr), fErrorCode(U_INVALID_STATE_ERROR) {} + + /** + * Move constructor: Leaves the source FormattedNumber in an undefined state. + * @stable ICU 62 + */ + FormattedNumber(FormattedNumber&& src) U_NOEXCEPT; + + /** + * Destruct an instance of FormattedNumber. + * @stable ICU 60 + */ + virtual ~FormattedNumber() U_OVERRIDE; + + /** Copying not supported; use move constructor instead. */ + FormattedNumber(const FormattedNumber&) = delete; + + /** Copying not supported; use move assignment instead. */ + FormattedNumber& operator=(const FormattedNumber&) = delete; + + /** + * Move assignment: Leaves the source FormattedNumber in an undefined state. + * @stable ICU 62 + */ + FormattedNumber& operator=(FormattedNumber&& src) U_NOEXCEPT; + + // Copybrief: this method is older than the parent method + /** + * @copybrief FormattedValue::toString() + * + * For more information, see FormattedValue::toString() + * + * @stable ICU 62 + */ + UnicodeString toString(UErrorCode& status) const U_OVERRIDE; + + // Copydoc: this method is new in ICU 64 + /** @copydoc FormattedValue::toTempString() */ + UnicodeString toTempString(UErrorCode& status) const U_OVERRIDE; + + // Copybrief: this method is older than the parent method + /** + * @copybrief FormattedValue::appendTo() + * + * For more information, see FormattedValue::appendTo() + * + * @stable ICU 62 + */ + Appendable &appendTo(Appendable& appendable, UErrorCode& status) const U_OVERRIDE; + + // Copydoc: this method is new in ICU 64 + /** @copydoc FormattedValue::nextPosition() */ + UBool nextPosition(ConstrainedFieldPosition& cfpos, UErrorCode& status) const U_OVERRIDE; + + /** + * Export the formatted number as a "numeric string" conforming to the + * syntax defined in the Decimal Arithmetic Specification, available at + * http://speleotrove.com/decimal + * + * This endpoint is useful for obtaining the exact number being printed + * after scaling and rounding have been applied by the number formatter. + * + * Example call site: + * + * auto decimalNumber = fn.toDecimalNumber(status); + * + * @tparam StringClass A string class compatible with StringByteSink; + * for example, std::string. + * @param status Set if an error occurs. + * @return A StringClass containing the numeric string. + * @stable ICU 65 + */ + template + inline StringClass toDecimalNumber(UErrorCode& status) const; + + /** + * Gets the resolved output unit. + * + * The output unit is dependent upon the localized preferences for the usage + * specified via NumberFormatterSettings::usage(), and may be a unit with + * UMEASURE_UNIT_MIXED unit complexity (MeasureUnit::getComplexity()), such + * as "foot-and-inch" or "hour-and-minute-and-second". + * + * @return `MeasureUnit`. + * @stable ICU 68 + */ + MeasureUnit getOutputUnit(UErrorCode& status) const; + +#ifndef U_HIDE_DRAFT_API + + /** + * Gets the noun class of the formatted output. Returns `UNDEFINED` when the noun class + * is not supported yet. + * + * @return UDisplayOptionsNounClass + * @draft ICU 72 + */ + UDisplayOptionsNounClass getNounClass(UErrorCode &status) const; + +#endif // U_HIDE_DRAFT_API + +#ifndef U_HIDE_INTERNAL_API + + /** + * Gets the raw DecimalQuantity for plural rule selection. + * @internal + */ + void getDecimalQuantity(impl::DecimalQuantity& output, UErrorCode& status) const; + + /** + * Populates the mutable builder type FieldPositionIteratorHandler. + * @internal + */ + void getAllFieldPositionsImpl(FieldPositionIteratorHandler& fpih, UErrorCode& status) const; + +#endif /* U_HIDE_INTERNAL_API */ + + private: + // Can't use LocalPointer because UFormattedNumberData is forward-declared + impl::UFormattedNumberData *fData; + + // Error code for the terminal methods + UErrorCode fErrorCode; + + /** + * Internal constructor from data type. Adopts the data pointer. + * @internal (private) + */ + explicit FormattedNumber(impl::UFormattedNumberData *results) + : fData(results), fErrorCode(U_ZERO_ERROR) {} + + explicit FormattedNumber(UErrorCode errorCode) + : fData(nullptr), fErrorCode(errorCode) {} + + void toDecimalNumber(ByteSink& sink, UErrorCode& status) const; + + // To give LocalizedNumberFormatter format methods access to this class's constructor: + friend class LocalizedNumberFormatter; + friend class SimpleNumberFormatter; + + // To give C API access to internals + friend struct impl::UFormattedNumberImpl; +}; + +template +StringClass FormattedNumber::toDecimalNumber(UErrorCode& status) const { + StringClass result; + StringByteSink sink(&result); + toDecimalNumber(sink, status); + return result; +} + +} // namespace number +U_NAMESPACE_END + +#endif /* #if !UCONFIG_NO_FORMATTING */ + +#endif /* U_SHOW_CPLUSPLUS_API */ + +#endif // __FORMATTEDNUMBER_H__ + diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index be2056be36d..486af2869fd 100644 --- a/icu4c/source/i18n/unicode/numberformatter.h +++ b/icu4c/source/i18n/unicode/numberformatter.h @@ -16,17 +16,17 @@ #include "unicode/dcfmtsym.h" #include "unicode/displayoptions.h" #include "unicode/fieldpos.h" -#include "unicode/formattedvalue.h" #include "unicode/fpositer.h" #include "unicode/measunit.h" #include "unicode/nounit.h" #include "unicode/parseerr.h" #include "unicode/plurrule.h" #include "unicode/ucurr.h" -#include "unicode/udisplayoptions.h" #include "unicode/unum.h" #include "unicode/unumberformatter.h" #include "unicode/uobject.h" +#include "unicode/unumberoptions.h" +#include "unicode/formattednumber.h" /** * \file @@ -112,6 +112,7 @@ namespace number { // icu::number // Forward declarations: class UnlocalizedNumberFormatter; class LocalizedNumberFormatter; +class SimpleNumberFormatter; class FormattedNumber; class Notation; class ScientificNotation; @@ -166,6 +167,8 @@ struct UFormattedNumberImpl; class MutablePatternModifier; class ImmutablePatternModifier; struct DecimalFormatWarehouse; +struct SimpleMicroProps; +class AdoptingSignumModifierStore; /** * Used for NumberRangeFormatter and implemented in numrange_fluent.cpp. @@ -1436,9 +1439,11 @@ class U_I18N_API Grouper : public UMemory { // To allow MacroProps/MicroProps to initialize empty instances: friend struct MacroProps; friend struct MicroProps; + friend struct SimpleMicroProps; // To allow NumberFormatterImpl to access isBogus() and perform other operations: friend class NumberFormatterImpl; + friend class ::icu::number::SimpleNumberFormatter; // To allow NumberParserImpl to perform setLocaleData(): friend class ::icu::numparse::impl::NumberParserImpl; @@ -2692,173 +2697,6 @@ class U_I18N_API LocalizedNumberFormatter #pragma warning(pop) #endif -/** - * The result of a number formatting operation. This class allows the result to be exported in several data types, - * including a UnicodeString and a FieldPositionIterator. - * - * Instances of this class are immutable and thread-safe. - * - * @stable ICU 60 - */ -class U_I18N_API FormattedNumber : public UMemory, public FormattedValue { - public: - - /** - * Default constructor; makes an empty FormattedNumber. - * @stable ICU 64 - */ - FormattedNumber() - : fData(nullptr), fErrorCode(U_INVALID_STATE_ERROR) {} - - /** - * Move constructor: Leaves the source FormattedNumber in an undefined state. - * @stable ICU 62 - */ - FormattedNumber(FormattedNumber&& src) U_NOEXCEPT; - - /** - * Destruct an instance of FormattedNumber. - * @stable ICU 60 - */ - virtual ~FormattedNumber() U_OVERRIDE; - - /** Copying not supported; use move constructor instead. */ - FormattedNumber(const FormattedNumber&) = delete; - - /** Copying not supported; use move assignment instead. */ - FormattedNumber& operator=(const FormattedNumber&) = delete; - - /** - * Move assignment: Leaves the source FormattedNumber in an undefined state. - * @stable ICU 62 - */ - FormattedNumber& operator=(FormattedNumber&& src) U_NOEXCEPT; - - // Copybrief: this method is older than the parent method - /** - * @copybrief FormattedValue::toString() - * - * For more information, see FormattedValue::toString() - * - * @stable ICU 62 - */ - UnicodeString toString(UErrorCode& status) const U_OVERRIDE; - - // Copydoc: this method is new in ICU 64 - /** @copydoc FormattedValue::toTempString() */ - UnicodeString toTempString(UErrorCode& status) const U_OVERRIDE; - - // Copybrief: this method is older than the parent method - /** - * @copybrief FormattedValue::appendTo() - * - * For more information, see FormattedValue::appendTo() - * - * @stable ICU 62 - */ - Appendable &appendTo(Appendable& appendable, UErrorCode& status) const U_OVERRIDE; - - // Copydoc: this method is new in ICU 64 - /** @copydoc FormattedValue::nextPosition() */ - UBool nextPosition(ConstrainedFieldPosition& cfpos, UErrorCode& status) const U_OVERRIDE; - - /** - * Export the formatted number as a "numeric string" conforming to the - * syntax defined in the Decimal Arithmetic Specification, available at - * http://speleotrove.com/decimal - * - * This endpoint is useful for obtaining the exact number being printed - * after scaling and rounding have been applied by the number formatter. - * - * Example call site: - * - * auto decimalNumber = fn.toDecimalNumber(status); - * - * @tparam StringClass A string class compatible with StringByteSink; - * for example, std::string. - * @param status Set if an error occurs. - * @return A StringClass containing the numeric string. - * @stable ICU 65 - */ - template - inline StringClass toDecimalNumber(UErrorCode& status) const; - - /** - * Gets the resolved output unit. - * - * The output unit is dependent upon the localized preferences for the usage - * specified via NumberFormatterSettings::usage(), and may be a unit with - * UMEASURE_UNIT_MIXED unit complexity (MeasureUnit::getComplexity()), such - * as "foot-and-inch" or "hour-and-minute-and-second". - * - * @return `MeasureUnit`. - * @stable ICU 68 - */ - MeasureUnit getOutputUnit(UErrorCode& status) const; - -#ifndef U_HIDE_DRAFT_API - - /** - * Gets the noun class of the formatted output. Returns `UNDEFINED` when the noun class - * is not supported yet. - * - * @return UDisplayOptionsNounClass - * @draft ICU 72 - */ - UDisplayOptionsNounClass getNounClass(UErrorCode &status) const; - -#endif // U_HIDE_DRAFT_API - -#ifndef U_HIDE_INTERNAL_API - - /** - * Gets the raw DecimalQuantity for plural rule selection. - * @internal - */ - void getDecimalQuantity(impl::DecimalQuantity& output, UErrorCode& status) const; - - /** - * Populates the mutable builder type FieldPositionIteratorHandler. - * @internal - */ - void getAllFieldPositionsImpl(FieldPositionIteratorHandler& fpih, UErrorCode& status) const; - -#endif /* U_HIDE_INTERNAL_API */ - - private: - // Can't use LocalPointer because UFormattedNumberData is forward-declared - const impl::UFormattedNumberData *fData; - - // Error code for the terminal methods - UErrorCode fErrorCode; - - /** - * Internal constructor from data type. Adopts the data pointer. - * @internal (private) - */ - explicit FormattedNumber(impl::UFormattedNumberData *results) - : fData(results), fErrorCode(U_ZERO_ERROR) {} - - explicit FormattedNumber(UErrorCode errorCode) - : fData(nullptr), fErrorCode(errorCode) {} - - void toDecimalNumber(ByteSink& sink, UErrorCode& status) const; - - // To give LocalizedNumberFormatter format methods access to this class's constructor: - friend class LocalizedNumberFormatter; - - // To give C API access to internals - friend struct impl::UFormattedNumberImpl; -}; - -template -StringClass FormattedNumber::toDecimalNumber(UErrorCode& status) const { - StringClass result; - StringByteSink sink(&result); - toDecimalNumber(sink, status); - return result; -} - /** * See the main description in numberformatter.h for documentation and examples. * diff --git a/icu4c/source/i18n/unicode/simplenumberformatter.h b/icu4c/source/i18n/unicode/simplenumberformatter.h new file mode 100644 index 00000000000..45ad42c98e3 --- /dev/null +++ b/icu4c/source/i18n/unicode/simplenumberformatter.h @@ -0,0 +1,329 @@ +// © 2022 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#ifndef __SIMPLENUMBERFORMATTERH__ +#define __SIMPLENUMBERFORMATTERH__ + +#include "unicode/utypes.h" + +#if U_SHOW_CPLUSPLUS_API + +#if !UCONFIG_NO_FORMATTING + +#include "unicode/dcfmtsym.h" +#include "unicode/usimplenumberformatter.h" +#include "unicode/formattednumber.h" + +/** + * \file + * \brief C++ API: Simple number formatting focused on low memory and code size. + * + * These functions render locale-aware number strings but without the bells and whistles found in + * other number formatting APIs such as those in numberformatter.h, like units and currencies. + * + *
+ * SimpleNumberFormatter snf = SimpleNumberFormatter::forLocale("de-CH", status);
+ * FormattedNumber result = snf.formatInt64(-1000007, status);
+ * assertEquals("", u"-1’000’007", result.toString(status));
+ * 
+ */ + +U_NAMESPACE_BEGIN + + +namespace number { // icu::number + + +namespace impl { +class UFormattedNumberData; +struct SimpleMicroProps; +class AdoptingSignumModifierStore; +} // icu::number::impl + + +#ifndef U_HIDE_DRAFT_API + + +/** + * An input type for SimpleNumberFormatter. + * + * This class is mutable and not intended for public subclassing. This class is movable but not copyable. + * + * @draft ICU 73 + */ +class U_I18N_API SimpleNumber : public UMemory { + public: + /** + * Creates a SimpleNumber for an integer. + * + * @draft ICU 73 + */ + static SimpleNumber forInt64(int64_t value, UErrorCode& status); + + /** + * Changes the value of the SimpleNumber by a power of 10. + * + * This function immediately mutates the inner value. + * + * @draft ICU 73 + */ + void multiplyByPowerOfTen(int32_t power, UErrorCode& status); + + /** + * Rounds the value currently stored in the SimpleNumber to the given power of 10. + * + * This function immediately mutates the inner value. + * + * @draft ICU 73 + */ + void roundTo(int32_t power, UNumberFormatRoundingMode roundingMode, UErrorCode& status); + + /** + * Truncates the most significant digits to the given maximum number of integer digits. + * + * This function immediately mutates the inner value. + * + * @draft ICU 73 + */ + void truncateStart(uint32_t maximumIntegerDigits, UErrorCode& status); + + /** + * Pads the beginning of the number with zeros up to the given minimum number of integer digits. + * + * This setting is applied upon formatting the number. + * + * @draft ICU 73 + */ + void setMinimumIntegerDigits(uint32_t minimumIntegerDigits, UErrorCode& status); + + /** + * Pads the end of the number with zeros up to the given minimum number of fraction digits. + * + * This setting is applied upon formatting the number. + * + * @draft ICU 73 + */ + void setMinimumFractionDigits(uint32_t minimumFractionDigits, UErrorCode& status); + + /** + * Sets the sign of the number: an explicit plus sign, explicit minus sign, or no sign. + * + * This setting is applied upon formatting the number. + * + * NOTE: This does not support accounting sign notation. + * + * @draft ICU 73 + */ + void setSign(USimpleNumberSign sign, UErrorCode& status); + + /** + * Creates a new, empty SimpleNumber that does not contain a value. + * + * NOTE: This number will fail to format; use forInt64() to create a SimpleNumber with a value. + * + * @draft ICU 73 + */ + SimpleNumber() = default; + + /** + * Destruct this SimpleNumber, cleaning up any memory it might own. + * + * @draft ICU 73 + */ + ~SimpleNumber() { + cleanup(); + } + + /** + * SimpleNumber move constructor. + * + * @draft ICU 73 + */ + SimpleNumber(SimpleNumber&& other) U_NOEXCEPT { + fData = other.fData; + fSign = other.fSign; + other.fData = nullptr; + } + + /** + * SimpleNumber move assignment. + * + * @draft ICU 73 + */ + SimpleNumber& operator=(SimpleNumber&& other) U_NOEXCEPT { + cleanup(); + fData = other.fData; + fSign = other.fSign; + other.fData = nullptr; + return *this; + } + + private: + SimpleNumber(impl::UFormattedNumberData* data, UErrorCode& status); + SimpleNumber(const SimpleNumber&) = delete; + SimpleNumber& operator=(const SimpleNumber&) = delete; + + void cleanup(); + + impl::UFormattedNumberData* fData = nullptr; + USimpleNumberSign fSign = UNUM_SIMPLE_NUMBER_NO_SIGN; + + friend class SimpleNumberFormatter; +}; + + +/** + * A special NumberFormatter focused on smaller binary size and memory use. + * + * SimpleNumberFormatter is capable of basic number formatting, including grouping separators, + * sign display, and rounding. It is not capable of currencies, compact notation, or units. + * + * This class is immutable and not intended for public subclassing. This class is movable but not copyable. + * + * @draft ICU 73 + */ +class U_I18N_API SimpleNumberFormatter : public UMemory { + public: + /** + * Creates a new SimpleNumberFormatter with all locale defaults. + * + * @draft ICU 73 + */ + static SimpleNumberFormatter forLocale( + const icu::Locale &locale, + UErrorCode &status); + + /** + * Creates a new SimpleNumberFormatter, overriding the grouping strategy. + * + * @draft ICU 73 + */ + static SimpleNumberFormatter forLocaleAndGroupingStrategy( + const icu::Locale &locale, + UNumberGroupingStrategy groupingStrategy, + UErrorCode &status); + + /** + * Creates a new SimpleNumberFormatter, overriding the grouping strategy and symbols. + * + * IMPORTANT: For efficiency, this function borrows the symbols. The symbols MUST remain valid + * for the lifetime of the SimpleNumberFormatter. + * + * @draft ICU 73 + */ + static SimpleNumberFormatter forLocaleAndSymbolsAndGroupingStrategy( + const icu::Locale &locale, + const DecimalFormatSymbols &symbols, + UNumberGroupingStrategy groupingStrategy, + UErrorCode &status); + + /** + * Formats a value using this SimpleNumberFormatter. + * + * The SimpleNumber argument is "consumed". A new SimpleNumber object should be created for + * every formatting operation. + * + * @draft ICU 73 + */ + FormattedNumber format(SimpleNumber value, UErrorCode &status) const; + + /** + * Formats an integer using this SimpleNumberFormatter. + * + * For more control over the formatting, use SimpleNumber. + * + * @draft ICU 73 + */ + FormattedNumber formatInt64(int64_t value, UErrorCode &status) const { + return format(SimpleNumber::forInt64(value, status), status); + } + +#ifndef U_HIDE_INTERNAL_API + /** + * Run the formatter with the internal types. + * @internal + */ + void formatImpl(impl::UFormattedNumberData* data, USimpleNumberSign sign, UErrorCode& status) const; +#endif // U_HIDE_INTERNAL_API + + /** + * Destruct this SimpleNumberFormatter, cleaning up any memory it might own. + * + * @draft ICU 73 + */ + ~SimpleNumberFormatter() { + cleanup(); + } + + /** + * Creates a shell, initialized but non-functional SimpleNumberFormatter. + * + * @draft ICU 73 + */ + SimpleNumberFormatter() = default; + + /** + * SimpleNumberFormatter: Move constructor. + * + * @draft ICU 73 + */ + SimpleNumberFormatter(SimpleNumberFormatter&& other) U_NOEXCEPT { + fGroupingStrategy = other.fGroupingStrategy; + fOwnedSymbols = other.fOwnedSymbols; + fMicros = other.fMicros; + fPatternModifier = other.fPatternModifier; + other.fOwnedSymbols = nullptr; + other.fMicros = nullptr; + other.fPatternModifier = nullptr; + } + + /** + * SimpleNumberFormatter: Move assignment. + * + * @draft ICU 73 + */ + SimpleNumberFormatter& operator=(SimpleNumberFormatter&& other) U_NOEXCEPT { + cleanup(); + fGroupingStrategy = other.fGroupingStrategy; + fOwnedSymbols = other.fOwnedSymbols; + fMicros = other.fMicros; + fPatternModifier = other.fPatternModifier; + other.fOwnedSymbols = nullptr; + other.fMicros = nullptr; + other.fPatternModifier = nullptr; + return *this; + } + + private: + void initialize( + const icu::Locale &locale, + const DecimalFormatSymbols &symbols, + UNumberGroupingStrategy groupingStrategy, + UErrorCode &status); + + void cleanup(); + + SimpleNumberFormatter(const SimpleNumberFormatter&) = delete; + + SimpleNumberFormatter& operator=(const SimpleNumberFormatter&) = delete; + + UNumberGroupingStrategy fGroupingStrategy = UNUM_GROUPING_AUTO; + + // Owned Pointers: + DecimalFormatSymbols* fOwnedSymbols = nullptr; // can be empty + impl::SimpleMicroProps* fMicros = nullptr; + impl::AdoptingSignumModifierStore* fPatternModifier = nullptr; +}; + + +#endif // U_HIDE_DRAFT_API + +} // namespace number +U_NAMESPACE_END + +#endif /* #if !UCONFIG_NO_FORMATTING */ + +#endif /* U_SHOW_CPLUSPLUS_API */ + +#endif // __SIMPLENUMBERFORMATTERH__ + diff --git a/icu4c/source/i18n/unicode/uformattednumber.h b/icu4c/source/i18n/unicode/uformattednumber.h new file mode 100644 index 00000000000..174a040a083 --- /dev/null +++ b/icu4c/source/i18n/unicode/uformattednumber.h @@ -0,0 +1,224 @@ +// © 2022 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#ifndef __UFORMATTEDNUMBER_H__ +#define __UFORMATTEDNUMBER_H__ + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +#include "unicode/ufieldpositer.h" +#include "unicode/uformattedvalue.h" +#include "unicode/umisc.h" + +/** + * \file + * \brief C API: Formatted number result from various number formatting functions. + * + * Create a `UFormattedNumber` to hold the result of a number formatting operation. The same + * `UFormattedNumber` can be reused multiple times. + * + *
+ * LocalUFormattedNumberPointer uresult(unumf_openResult(status));
+ *
+ * // pass uresult.getAlias() to your number formatter
+ *
+ * int32_t length;
+ * const UChar* s = ufmtval_getString(unumf_resultAsValue(uresult.getAlias(), status), &length, status));
+ *
+ * // The string result is in `s` with the given `length` (it is also NUL-terminated).
+ * 
+ */ + + +struct UFormattedNumber; +/** + * C-compatible version of icu::number::FormattedNumber. + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @stable ICU 62 + */ +typedef struct UFormattedNumber UFormattedNumber; + + +/** + * Creates an object to hold the result of a UNumberFormatter + * operation. The object can be used repeatedly; it is cleared whenever + * passed to a format function. + * + * @param ec Set if an error occurs. + * @stable ICU 62 + */ +U_CAPI UFormattedNumber* U_EXPORT2 +unumf_openResult(UErrorCode* ec); + + +/** + * Returns a representation of a UFormattedNumber as a UFormattedValue, + * which can be subsequently passed to any API requiring that type. + * + * The returned object is owned by the UFormattedNumber and is valid + * only as long as the UFormattedNumber is present and unchanged in memory. + * + * You can think of this method as a cast between types. + * + * @param uresult The object containing the formatted string. + * @param ec Set if an error occurs. + * @return A UFormattedValue owned by the input object. + * @stable ICU 64 + */ +U_CAPI const UFormattedValue* U_EXPORT2 +unumf_resultAsValue(const UFormattedNumber* uresult, UErrorCode* ec); + + +/** + * Extracts the result number string out of a UFormattedNumber to a UChar buffer if possible. + * If bufferCapacity is greater than the required length, a terminating NUL is written. + * If bufferCapacity is less than the required length, an error code is set. + * + * Also see ufmtval_getString, which returns a NUL-terminated string: + * + * int32_t len; + * const UChar* str = ufmtval_getString(unumf_resultAsValue(uresult, &ec), &len, &ec); + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @param uresult The object containing the formatted number. + * @param buffer Where to save the string output. + * @param bufferCapacity The number of UChars available in the buffer. + * @param ec Set if an error occurs. + * @return The required length. + * @stable ICU 62 + */ +U_CAPI int32_t U_EXPORT2 +unumf_resultToString(const UFormattedNumber* uresult, UChar* buffer, int32_t bufferCapacity, + UErrorCode* ec); + + +/** + * Determines the start and end indices of the next occurrence of the given field in the + * output string. This allows you to determine the locations of, for example, the integer part, + * fraction part, or symbols. + * + * This is a simpler but less powerful alternative to {@link ufmtval_nextPosition}. + * + * If a field occurs just once, calling this method will find that occurrence and return it. If a + * field occurs multiple times, this method may be called repeatedly with the following pattern: + * + *
+ * UFieldPosition ufpos = {UNUM_GROUPING_SEPARATOR_FIELD, 0, 0};
+ * while (unumf_resultNextFieldPosition(uresult, ufpos, &ec)) {
+ *   // do something with ufpos.
+ * }
+ * 
+ * + * This method is useful if you know which field to query. If you want all available field position + * information, use unumf_resultGetAllFieldPositions(). + * + * NOTE: All fields of the UFieldPosition must be initialized before calling this method. + * + * @param uresult The object containing the formatted number. + * @param ufpos + * Input+output variable. On input, the "field" property determines which field to look up, + * and the "endIndex" property determines where to begin the search. On output, the + * "beginIndex" field is set to the beginning of the first occurrence of the field after the + * input "endIndex", and "endIndex" is set to the end of that occurrence of the field + * (exclusive index). If a field position is not found, the FieldPosition is not changed and + * the method returns false. + * @param ec Set if an error occurs. + * @stable ICU 62 + */ +U_CAPI UBool U_EXPORT2 +unumf_resultNextFieldPosition(const UFormattedNumber* uresult, UFieldPosition* ufpos, UErrorCode* ec); + + +/** + * Populates the given iterator with all fields in the formatted output string. This allows you to + * determine the locations of the integer part, fraction part, and sign. + * + * This is an alternative to the more powerful {@link ufmtval_nextPosition} API. + * + * If you need information on only one field, use {@link ufmtval_nextPosition} or + * {@link unumf_resultNextFieldPosition}. + * + * @param uresult The object containing the formatted number. + * @param ufpositer + * A pointer to a UFieldPositionIterator created by {@link #ufieldpositer_open}. Iteration + * information already present in the UFieldPositionIterator is deleted, and the iterator is reset + * to apply to the fields in the formatted string created by this function call. The field values + * and indexes returned by {@link #ufieldpositer_next} represent fields denoted by + * the UNumberFormatFields enum. Fields are not returned in a guaranteed order. Fields cannot + * overlap, but they may nest. For example, 1234 could format as "1,234" which might consist of a + * grouping separator field for ',' and an integer field encompassing the entire string. + * @param ec Set if an error occurs. + * @stable ICU 62 + */ +U_CAPI void U_EXPORT2 +unumf_resultGetAllFieldPositions(const UFormattedNumber* uresult, UFieldPositionIterator* ufpositer, + UErrorCode* ec); + + +/** + * Extracts the formatted number as a "numeric string" conforming to the + * syntax defined in the Decimal Arithmetic Specification, available at + * http://speleotrove.com/decimal + * + * This endpoint is useful for obtaining the exact number being printed + * after scaling and rounding have been applied by the number formatter. + * + * @param uresult The input object containing the formatted number. + * @param dest the 8-bit char buffer into which the decimal number is placed + * @param destCapacity The size, in chars, of the destination buffer. May be zero + * for precomputing the required size. + * @param ec receives any error status. + * If U_BUFFER_OVERFLOW_ERROR: Returns number of chars for + * preflighting. + * @return Number of chars in the data. Does not include a trailing NUL. + * @stable ICU 68 + */ +U_CAPI int32_t U_EXPORT2 +unumf_resultToDecimalNumber( + const UFormattedNumber* uresult, + char* dest, + int32_t destCapacity, + UErrorCode* ec); + + +/** + * Releases the UFormattedNumber created by unumf_openResult(). + * + * @param uresult An object created by unumf_openResult(). + * @stable ICU 62 + */ +U_CAPI void U_EXPORT2 +unumf_closeResult(UFormattedNumber* uresult); + + +#if U_SHOW_CPLUSPLUS_API +U_NAMESPACE_BEGIN + +/** + * \class LocalUFormattedNumberPointer + * "Smart pointer" class; closes a UFormattedNumber via unumf_closeResult(). + * For most methods see the LocalPointerBase base class. + * + * Usage: + *
+ * LocalUFormattedNumberPointer uformatter(unumf_openResult(...));
+ * // no need to explicitly call unumf_closeResult()
+ * 
+ * + * @see LocalPointerBase + * @see LocalPointer + * @stable ICU 62 + */ +U_DEFINE_LOCAL_OPEN_POINTER(LocalUFormattedNumberPointer, UFormattedNumber, unumf_closeResult); + +U_NAMESPACE_END +#endif // U_SHOW_CPLUSPLUS_API + + +#endif /* #if !UCONFIG_NO_FORMATTING */ +#endif //__UFORMATTEDNUMBER_H__ diff --git a/icu4c/source/i18n/unicode/unum.h b/icu4c/source/i18n/unicode/unum.h index a392afaaed2..0c2e378e6d6 100644 --- a/icu4c/source/i18n/unicode/unum.h +++ b/icu4c/source/i18n/unicode/unum.h @@ -25,6 +25,7 @@ #include "unicode/uformattable.h" #include "unicode/udisplaycontext.h" #include "unicode/ufieldpositer.h" +#include "unicode/unumberoptions.h" #if U_SHOW_CPLUSPLUS_API #include "unicode/localpointer.h" @@ -271,55 +272,6 @@ typedef enum UNumberFormatStyle { UNUM_IGNORE = UNUM_PATTERN_DECIMAL } UNumberFormatStyle; -/** The possible number format rounding modes. - * - *

- * For more detail on rounding modes, see: - * https://unicode-org.github.io/icu/userguide/format_parse/numbers/rounding-modes - * - * @stable ICU 2.0 - */ -typedef enum UNumberFormatRoundingMode { - UNUM_ROUND_CEILING, - UNUM_ROUND_FLOOR, - UNUM_ROUND_DOWN, - UNUM_ROUND_UP, - /** - * Half-even rounding - * @stable, ICU 3.8 - */ - UNUM_ROUND_HALFEVEN, -#ifndef U_HIDE_DEPRECATED_API - /** - * Half-even rounding, misspelled name - * @deprecated, ICU 3.8 - */ - UNUM_FOUND_HALFEVEN = UNUM_ROUND_HALFEVEN, -#endif /* U_HIDE_DEPRECATED_API */ - UNUM_ROUND_HALFDOWN = UNUM_ROUND_HALFEVEN + 1, - UNUM_ROUND_HALFUP, - /** - * ROUND_UNNECESSARY reports an error if formatted result is not exact. - * @stable ICU 4.8 - */ - UNUM_ROUND_UNNECESSARY, - /** - * Rounds ties toward the odd number. - * @stable ICU 69 - */ - UNUM_ROUND_HALF_ODD, - /** - * Rounds ties toward +∞. - * @stable ICU 69 - */ - UNUM_ROUND_HALF_CEILING, - /** - * Rounds ties toward -∞. - * @stable ICU 69 - */ - UNUM_ROUND_HALF_FLOOR, -} UNumberFormatRoundingMode; - /** The possible number format pad positions. * @stable ICU 2.0 */ diff --git a/icu4c/source/i18n/unicode/unumberformatter.h b/icu4c/source/i18n/unicode/unumberformatter.h index 253b30b5342..09fa000b826 100644 --- a/icu4c/source/i18n/unicode/unumberformatter.h +++ b/icu4c/source/i18n/unicode/unumberformatter.h @@ -9,9 +9,8 @@ #if !UCONFIG_NO_FORMATTING #include "unicode/parseerr.h" -#include "unicode/ufieldpositer.h" -#include "unicode/umisc.h" -#include "unicode/uformattedvalue.h" +#include "unicode/unumberoptions.h" +#include "unicode/uformattednumber.h" /** @@ -243,107 +242,6 @@ typedef enum UNumberUnitWidth { UNUM_UNIT_WIDTH_COUNT = 7 } UNumberUnitWidth; -/** - * An enum declaring the strategy for when and how to display grouping separators (i.e., the - * separator, often a comma or period, after every 2-3 powers of ten). The choices are several - * pre-built strategies for different use cases that employ locale data whenever possible. Example - * outputs for 1234 and 1234567 in en-IN: - * - *

    - *
  • OFF: 1234 and 12345 - *
  • MIN2: 1234 and 12,34,567 - *
  • AUTO: 1,234 and 12,34,567 - *
  • ON_ALIGNED: 1,234 and 12,34,567 - *
  • THOUSANDS: 1,234 and 1,234,567 - *
- * - *

- * The default is AUTO, which displays grouping separators unless the locale data says that grouping - * is not customary. To force grouping for all numbers greater than 1000 consistently across locales, - * use ON_ALIGNED. On the other hand, to display grouping less frequently than the default, use MIN2 - * or OFF. See the docs of each option for details. - * - *

- * Note: This enum specifies the strategy for grouping sizes. To set which character to use as the - * grouping separator, use the "symbols" setter. - * - * @stable ICU 63 - */ -typedef enum UNumberGroupingStrategy { - /** - * Do not display grouping separators in any locale. - * - * @stable ICU 61 - */ - UNUM_GROUPING_OFF, - - /** - * Display grouping using locale defaults, except do not show grouping on values smaller than - * 10000 (such that there is a minimum of two digits before the first separator). - * - *

- * Note that locales may restrict grouping separators to be displayed only on 1 million or - * greater (for example, ee and hu) or disable grouping altogether (for example, bg currency). - * - *

- * Locale data is used to determine whether to separate larger numbers into groups of 2 - * (customary in South Asia) or groups of 3 (customary in Europe and the Americas). - * - * @stable ICU 61 - */ - UNUM_GROUPING_MIN2, - - /** - * Display grouping using the default strategy for all locales. This is the default behavior. - * - *

- * Note that locales may restrict grouping separators to be displayed only on 1 million or - * greater (for example, ee and hu) or disable grouping altogether (for example, bg currency). - * - *

- * Locale data is used to determine whether to separate larger numbers into groups of 2 - * (customary in South Asia) or groups of 3 (customary in Europe and the Americas). - * - * @stable ICU 61 - */ - UNUM_GROUPING_AUTO, - - /** - * Always display the grouping separator on values of at least 1000. - * - *

- * This option ignores the locale data that restricts or disables grouping, described in MIN2 and - * AUTO. This option may be useful to normalize the alignment of numbers, such as in a - * spreadsheet. - * - *

- * Locale data is used to determine whether to separate larger numbers into groups of 2 - * (customary in South Asia) or groups of 3 (customary in Europe and the Americas). - * - * @stable ICU 61 - */ - UNUM_GROUPING_ON_ALIGNED, - - /** - * Use the Western defaults: groups of 3 and enabled for all numbers 1000 or greater. Do not use - * locale data for determining the grouping strategy. - * - * @stable ICU 61 - */ - UNUM_GROUPING_THOUSANDS - -#ifndef U_HIDE_INTERNAL_API - , - /** - * One more than the highest UNumberGroupingStrategy value. - * - * @internal ICU 62: The numeric value may change over time; see ICU ticket #12420. - */ - UNUM_GROUPING_COUNT -#endif /* U_HIDE_INTERNAL_API */ - -} UNumberGroupingStrategy; - /** * An enum declaring how to denote positive and negative numbers. Example outputs when formatting * 123, 0, and -123 in en-US: @@ -528,16 +426,6 @@ struct UNumberFormatter; */ typedef struct UNumberFormatter UNumberFormatter; -struct UFormattedNumber; -/** - * C-compatible version of icu::number::FormattedNumber. - * - * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. - * - * @stable ICU 62 - */ -typedef struct UFormattedNumber UFormattedNumber; - /** * Creates a new UNumberFormatter for the given skeleton string and locale. This is currently the only @@ -584,17 +472,6 @@ unumf_openForSkeletonAndLocaleWithError( const UChar* skeleton, int32_t skeletonLen, const char* locale, UParseError* perror, UErrorCode* ec); -/** - * Creates an object to hold the result of a UNumberFormatter - * operation. The object can be used repeatedly; it is cleared whenever - * passed to a format function. - * - * @param ec Set if an error occurs. - * @stable ICU 62 - */ -U_CAPI UFormattedNumber* U_EXPORT2 -unumf_openResult(UErrorCode* ec); - /** * Uses a UNumberFormatter to format an integer to a UFormattedNumber. A string, field position, and other @@ -659,135 +536,6 @@ U_CAPI void U_EXPORT2 unumf_formatDecimal(const UNumberFormatter* uformatter, const char* value, int32_t valueLen, UFormattedNumber* uresult, UErrorCode* ec); -/** - * Returns a representation of a UFormattedNumber as a UFormattedValue, - * which can be subsequently passed to any API requiring that type. - * - * The returned object is owned by the UFormattedNumber and is valid - * only as long as the UFormattedNumber is present and unchanged in memory. - * - * You can think of this method as a cast between types. - * - * @param uresult The object containing the formatted string. - * @param ec Set if an error occurs. - * @return A UFormattedValue owned by the input object. - * @stable ICU 64 - */ -U_CAPI const UFormattedValue* U_EXPORT2 -unumf_resultAsValue(const UFormattedNumber* uresult, UErrorCode* ec); - - -/** - * Extracts the result number string out of a UFormattedNumber to a UChar buffer if possible. - * If bufferCapacity is greater than the required length, a terminating NUL is written. - * If bufferCapacity is less than the required length, an error code is set. - * - * Also see ufmtval_getString, which returns a NUL-terminated string: - * - * int32_t len; - * const UChar* str = ufmtval_getString(unumf_resultAsValue(uresult, &ec), &len, &ec); - * - * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. - * - * @param uresult The object containing the formatted number. - * @param buffer Where to save the string output. - * @param bufferCapacity The number of UChars available in the buffer. - * @param ec Set if an error occurs. - * @return The required length. - * @stable ICU 62 - */ -U_CAPI int32_t U_EXPORT2 -unumf_resultToString(const UFormattedNumber* uresult, UChar* buffer, int32_t bufferCapacity, - UErrorCode* ec); - - -/** - * Determines the start and end indices of the next occurrence of the given field in the - * output string. This allows you to determine the locations of, for example, the integer part, - * fraction part, or symbols. - * - * This is a simpler but less powerful alternative to {@link ufmtval_nextPosition}. - * - * If a field occurs just once, calling this method will find that occurrence and return it. If a - * field occurs multiple times, this method may be called repeatedly with the following pattern: - * - *

- * UFieldPosition ufpos = {UNUM_GROUPING_SEPARATOR_FIELD, 0, 0};
- * while (unumf_resultNextFieldPosition(uresult, ufpos, &ec)) {
- *   // do something with ufpos.
- * }
- * 
- * - * This method is useful if you know which field to query. If you want all available field position - * information, use unumf_resultGetAllFieldPositions(). - * - * NOTE: All fields of the UFieldPosition must be initialized before calling this method. - * - * @param uresult The object containing the formatted number. - * @param ufpos - * Input+output variable. On input, the "field" property determines which field to look up, - * and the "endIndex" property determines where to begin the search. On output, the - * "beginIndex" field is set to the beginning of the first occurrence of the field after the - * input "endIndex", and "endIndex" is set to the end of that occurrence of the field - * (exclusive index). If a field position is not found, the FieldPosition is not changed and - * the method returns false. - * @param ec Set if an error occurs. - * @stable ICU 62 - */ -U_CAPI UBool U_EXPORT2 -unumf_resultNextFieldPosition(const UFormattedNumber* uresult, UFieldPosition* ufpos, UErrorCode* ec); - - -/** - * Populates the given iterator with all fields in the formatted output string. This allows you to - * determine the locations of the integer part, fraction part, and sign. - * - * This is an alternative to the more powerful {@link ufmtval_nextPosition} API. - * - * If you need information on only one field, use {@link ufmtval_nextPosition} or - * {@link unumf_resultNextFieldPosition}. - * - * @param uresult The object containing the formatted number. - * @param ufpositer - * A pointer to a UFieldPositionIterator created by {@link #ufieldpositer_open}. Iteration - * information already present in the UFieldPositionIterator is deleted, and the iterator is reset - * to apply to the fields in the formatted string created by this function call. The field values - * and indexes returned by {@link #ufieldpositer_next} represent fields denoted by - * the UNumberFormatFields enum. Fields are not returned in a guaranteed order. Fields cannot - * overlap, but they may nest. For example, 1234 could format as "1,234" which might consist of a - * grouping separator field for ',' and an integer field encompassing the entire string. - * @param ec Set if an error occurs. - * @stable ICU 62 - */ -U_CAPI void U_EXPORT2 -unumf_resultGetAllFieldPositions(const UFormattedNumber* uresult, UFieldPositionIterator* ufpositer, - UErrorCode* ec); - - -/** - * Extracts the formatted number as a "numeric string" conforming to the - * syntax defined in the Decimal Arithmetic Specification, available at - * http://speleotrove.com/decimal - * - * This endpoint is useful for obtaining the exact number being printed - * after scaling and rounding have been applied by the number formatter. - * - * @param uresult The input object containing the formatted number. - * @param dest the 8-bit char buffer into which the decimal number is placed - * @param destCapacity The size, in chars, of the destination buffer. May be zero - * for precomputing the required size. - * @param ec receives any error status. - * If U_BUFFER_OVERFLOW_ERROR: Returns number of chars for - * preflighting. - * @return Number of chars in the data. Does not include a trailing NUL. - * @stable ICU 68 - */ -U_CAPI int32_t U_EXPORT2 -unumf_resultToDecimalNumber( - const UFormattedNumber* uresult, - char* dest, - int32_t destCapacity, - UErrorCode* ec); /** @@ -800,15 +548,6 @@ U_CAPI void U_EXPORT2 unumf_close(UNumberFormatter* uformatter); -/** - * Releases the UFormattedNumber created by unumf_openResult(). - * - * @param uresult An object created by unumf_openResult(). - * @stable ICU 62 - */ -U_CAPI void U_EXPORT2 -unumf_closeResult(UFormattedNumber* uresult); - #if U_SHOW_CPLUSPLUS_API U_NAMESPACE_BEGIN @@ -830,23 +569,6 @@ U_NAMESPACE_BEGIN */ U_DEFINE_LOCAL_OPEN_POINTER(LocalUNumberFormatterPointer, UNumberFormatter, unumf_close); -/** - * \class LocalUFormattedNumberPointer - * "Smart pointer" class; closes a UFormattedNumber via unumf_closeResult(). - * For most methods see the LocalPointerBase base class. - * - * Usage: - *
- * LocalUFormattedNumberPointer uformatter(unumf_openResult(...));
- * // no need to explicitly call unumf_closeResult()
- * 
- * - * @see LocalPointerBase - * @see LocalPointer - * @stable ICU 62 - */ -U_DEFINE_LOCAL_OPEN_POINTER(LocalUFormattedNumberPointer, UFormattedNumber, unumf_closeResult); - U_NAMESPACE_END #endif // U_SHOW_CPLUSPLUS_API diff --git a/icu4c/source/i18n/unicode/unumberoptions.h b/icu4c/source/i18n/unicode/unumberoptions.h new file mode 100644 index 00000000000..3fa8df51d57 --- /dev/null +++ b/icu4c/source/i18n/unicode/unumberoptions.h @@ -0,0 +1,173 @@ +// © 2017 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#ifndef __UNUMBEROPTIONS_H__ +#define __UNUMBEROPTIONS_H__ + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +/** + * \file + * \brief C API: Header-only input options for various number formatting APIs. + * + * You do not normally need to include this header file directly, because it is included in all + * files that use these enums. + */ + + +/** The possible number format rounding modes. + * + *

+ * For more detail on rounding modes, see: + * https://unicode-org.github.io/icu/userguide/format_parse/numbers/rounding-modes + * + * @stable ICU 2.0 + */ +typedef enum UNumberFormatRoundingMode { + UNUM_ROUND_CEILING, + UNUM_ROUND_FLOOR, + UNUM_ROUND_DOWN, + UNUM_ROUND_UP, + /** + * Half-even rounding + * @stable, ICU 3.8 + */ + UNUM_ROUND_HALFEVEN, +#ifndef U_HIDE_DEPRECATED_API + /** + * Half-even rounding, misspelled name + * @deprecated, ICU 3.8 + */ + UNUM_FOUND_HALFEVEN = UNUM_ROUND_HALFEVEN, +#endif /* U_HIDE_DEPRECATED_API */ + UNUM_ROUND_HALFDOWN = UNUM_ROUND_HALFEVEN + 1, + UNUM_ROUND_HALFUP, + /** + * ROUND_UNNECESSARY reports an error if formatted result is not exact. + * @stable ICU 4.8 + */ + UNUM_ROUND_UNNECESSARY, + /** + * Rounds ties toward the odd number. + * @stable ICU 69 + */ + UNUM_ROUND_HALF_ODD, + /** + * Rounds ties toward +∞. + * @stable ICU 69 + */ + UNUM_ROUND_HALF_CEILING, + /** + * Rounds ties toward -∞. + * @stable ICU 69 + */ + UNUM_ROUND_HALF_FLOOR, +} UNumberFormatRoundingMode; + + +/** + * An enum declaring the strategy for when and how to display grouping separators (i.e., the + * separator, often a comma or period, after every 2-3 powers of ten). The choices are several + * pre-built strategies for different use cases that employ locale data whenever possible. Example + * outputs for 1234 and 1234567 in en-IN: + * + *

    + *
  • OFF: 1234 and 12345 + *
  • MIN2: 1234 and 12,34,567 + *
  • AUTO: 1,234 and 12,34,567 + *
  • ON_ALIGNED: 1,234 and 12,34,567 + *
  • THOUSANDS: 1,234 and 1,234,567 + *
+ * + *

+ * The default is AUTO, which displays grouping separators unless the locale data says that grouping + * is not customary. To force grouping for all numbers greater than 1000 consistently across locales, + * use ON_ALIGNED. On the other hand, to display grouping less frequently than the default, use MIN2 + * or OFF. See the docs of each option for details. + * + *

+ * Note: This enum specifies the strategy for grouping sizes. To set which character to use as the + * grouping separator, use the "symbols" setter. + * + * @stable ICU 63 + */ +typedef enum UNumberGroupingStrategy { + /** + * Do not display grouping separators in any locale. + * + * @stable ICU 61 + */ + UNUM_GROUPING_OFF, + + /** + * Display grouping using locale defaults, except do not show grouping on values smaller than + * 10000 (such that there is a minimum of two digits before the first separator). + * + *

+ * Note that locales may restrict grouping separators to be displayed only on 1 million or + * greater (for example, ee and hu) or disable grouping altogether (for example, bg currency). + * + *

+ * Locale data is used to determine whether to separate larger numbers into groups of 2 + * (customary in South Asia) or groups of 3 (customary in Europe and the Americas). + * + * @stable ICU 61 + */ + UNUM_GROUPING_MIN2, + + /** + * Display grouping using the default strategy for all locales. This is the default behavior. + * + *

+ * Note that locales may restrict grouping separators to be displayed only on 1 million or + * greater (for example, ee and hu) or disable grouping altogether (for example, bg currency). + * + *

+ * Locale data is used to determine whether to separate larger numbers into groups of 2 + * (customary in South Asia) or groups of 3 (customary in Europe and the Americas). + * + * @stable ICU 61 + */ + UNUM_GROUPING_AUTO, + + /** + * Always display the grouping separator on values of at least 1000. + * + *

+ * This option ignores the locale data that restricts or disables grouping, described in MIN2 and + * AUTO. This option may be useful to normalize the alignment of numbers, such as in a + * spreadsheet. + * + *

+ * Locale data is used to determine whether to separate larger numbers into groups of 2 + * (customary in South Asia) or groups of 3 (customary in Europe and the Americas). + * + * @stable ICU 61 + */ + UNUM_GROUPING_ON_ALIGNED, + + /** + * Use the Western defaults: groups of 3 and enabled for all numbers 1000 or greater. Do not use + * locale data for determining the grouping strategy. + * + * @stable ICU 61 + */ + UNUM_GROUPING_THOUSANDS + +#ifndef U_HIDE_INTERNAL_API + , + /** + * One more than the highest UNumberGroupingStrategy value. + * + * @internal ICU 62: The numeric value may change over time; see ICU ticket #12420. + */ + UNUM_GROUPING_COUNT +#endif /* U_HIDE_INTERNAL_API */ + +} UNumberGroupingStrategy; + + +#endif /* #if !UCONFIG_NO_FORMATTING */ +#endif //__UNUMBEROPTIONS_H__ diff --git a/icu4c/source/i18n/unicode/usimplenumberformatter.h b/icu4c/source/i18n/unicode/usimplenumberformatter.h new file mode 100644 index 00000000000..c045d7a9d76 --- /dev/null +++ b/icu4c/source/i18n/unicode/usimplenumberformatter.h @@ -0,0 +1,273 @@ +// © 2022 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#ifndef __USIMPLENUMBERFORMATTER_H__ +#define __USIMPLENUMBERFORMATTER_H__ + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +#include "unicode/uformattednumber.h" +#include "unicode/unumberoptions.h" + +/** + * \file + * \brief C API: Simple number formatting focused on low memory and code size. + * + * These functions render locale-aware number strings but without the bells and whistles found in + * other number formatting APIs such as those in unumberformatter.h, like units and currencies. + * + *

+ * LocalUSimpleNumberFormatterPointer uformatter(usnumf_openForLocale("de-CH", status));
+ * LocalUFormattedNumberPointer uresult(unumf_openResult(status));
+ * usnumf_formatInt64(uformatter.getAlias(), 55, uresult.getAlias(), status);
+ * assertEquals("",
+ *     u"55",
+ *     ufmtval_getString(unumf_resultAsValue(uresult.getAlias(), status), nullptr, status));
+ * 
+ */ + +#ifndef U_HIDE_DRAFT_API + + +/** + * An explicit sign option for a SimpleNumber. + * + * @draft ICU 73 + */ +typedef enum USimpleNumberSign { + /** + * Render a plus sign. + * + * @draft ICU 73 + */ + UNUM_SIMPLE_NUMBER_PLUS_SIGN, + /** + * Render no sign. + * + * @draft ICU 73 + */ + UNUM_SIMPLE_NUMBER_NO_SIGN, + /** + * Render a minus sign. + * + * @draft ICU 73 + */ + UNUM_SIMPLE_NUMBER_MINUS_SIGN, +} USimpleNumberSign; + + +struct USimpleNumber; +/** + * C-compatible version of icu::number::SimpleNumber. + * + * @draft ICU 73 + */ +typedef struct USimpleNumber USimpleNumber; + + +struct USimpleNumberFormatter; +/** + * C-compatible version of icu::number::SimpleNumberFormatter. + * + * @draft ICU 73 + */ +typedef struct USimpleNumberFormatter USimpleNumberFormatter; + + +/** + * Creates a new USimpleNumber to be formatted with a USimpleNumberFormatter. + * + * @draft ICU 73 + */ +U_CAPI USimpleNumber* U_EXPORT2 +usnum_openForInt64(int64_t value, UErrorCode* ec); + + +/** + * Changes the value of the USimpleNumber by a power of 10. + * + * This function immediately mutates the inner value. + * + * @draft ICU 73 + */ +U_CAPI void U_EXPORT2 +usnum_multiplyByPowerOfTen(USimpleNumber* unumber, int32_t power, UErrorCode* ec); + + +/** + * Rounds the value currently stored in the USimpleNumber to the given power of 10. + * + * This function immediately mutates the inner value. + * + * @draft ICU 73 + */ +U_CAPI void U_EXPORT2 +usnum_roundTo(USimpleNumber* unumber, int32_t power, UNumberFormatRoundingMode roundingMode, UErrorCode* ec); + + +/** + * Pads the beginning of the number with zeros up to the given minimum number of integer digits. + * + * This setting is applied upon formatting the number. + * + * @draft ICU 73 + */ +U_CAPI void U_EXPORT2 +usnum_setMinimumIntegerDigits(USimpleNumber* unumber, int32_t minimumIntegerDigits, UErrorCode* ec); + + +/** + * Pads the end of the number with zeros up to the given minimum number of fraction digits. + * + * This setting is applied upon formatting the number. + * + * @draft ICU 73 + */ +U_CAPI void U_EXPORT2 +usnum_setMinimumFractionDigits(USimpleNumber* unumber, int32_t minimumFractionDigits, UErrorCode* ec); + + +/** + * Truncates digits from the beginning of the number to the given maximum number of integer digits. + * + * This function immediately mutates the inner value. + * + * @draft ICU 73 + */ +U_CAPI void U_EXPORT2 +usnum_truncateStart(USimpleNumber* unumber, int32_t maximumIntegerDigits, UErrorCode* ec); + + +/** + * Sets the sign of the number: an explicit plus sign, explicit minus sign, or no sign. + * + * This setting is applied upon formatting the number. + * + * NOTE: This does not support accounting sign notation. + * + * @draft ICU 73 + */ +U_CAPI void U_EXPORT2 +usnum_setSign(USimpleNumber* unumber, USimpleNumberSign sign, UErrorCode* ec); + + +/** + * Creates a new USimpleNumberFormatter with all locale defaults. + * + * @draft ICU 73 + */ +U_CAPI USimpleNumberFormatter* U_EXPORT2 +usnumf_openForLocale(const char* locale, UErrorCode* ec); + + +/** + * Creates a new USimpleNumberFormatter, overriding the grouping strategy. + * + * @draft ICU 73 + */ +U_CAPI USimpleNumberFormatter* U_EXPORT2 +usnumf_openForLocaleAndGroupingStrategy( + const char* locale, UNumberGroupingStrategy groupingStrategy, UErrorCode* ec); + + +/** + * Formats a number using this SimpleNumberFormatter. + * + * The USimpleNumber is adopted and must not be freed after calling this function, + * even if the function sets an error code. If you use LocalUSimpleNumberPointer, + * call `.orphan()` when passing it to this function. + * + * @draft ICU 73 + */ +U_CAPI void U_EXPORT2 +usnumf_formatAndAdoptNumber( + const USimpleNumberFormatter* uformatter, + USimpleNumber* unumber, + UFormattedNumber* uresult, + UErrorCode* ec); + + +/** + * Formats an integer using this SimpleNumberFormatter. + * + * For more control over the formatting, use USimpleNumber. + * + * @draft ICU 73 + */ +U_CAPI void U_EXPORT2 +usnumf_formatInt64( + const USimpleNumberFormatter* uformatter, + int64_t value, + UFormattedNumber* uresult, + UErrorCode* ec); + + +/** + * Frees the memory held by a USimpleNumber. + * + * NOTE: Normally, a USimpleNumber should be adopted by usnumf_formatAndAdoptNumber. + * + * @draft ICU 73 + */ +U_CAPI void U_EXPORT2 +usnum_close(USimpleNumber* unumber); + + +/** + * Frees the memory held by a USimpleNumberFormatter. + * + * @draft ICU 73 + */ +U_CAPI void U_EXPORT2 +usnumf_close(USimpleNumberFormatter* uformatter); + + +#if U_SHOW_CPLUSPLUS_API +U_NAMESPACE_BEGIN + +/** + * \class LocalUSimpleNumberPointer + * "Smart pointer" class; closes a USimpleNumber via usnum_close(). + * For most methods see the LocalPointerBase base class. + * + * NOTE: Normally, a USimpleNumber should be adopted by usnumf_formatAndAdoptNumber. + * If you use LocalUSimpleNumberPointer, call `.orphan()` when passing to that function. + * + * Usage: + *
+ * LocalUSimpleNumberPointer uformatter(usnumf_openForInteger(...));
+ * // no need to explicitly call usnum_close()
+ * 
+ * + * @see LocalPointerBase + * @see LocalPointer + * @draft ICU 73 + */ +U_DEFINE_LOCAL_OPEN_POINTER(LocalUSimpleNumberPointer, USimpleNumber, usnum_close); + +/** + * \class LocalUSimpleNumberFormatterPointer + * "Smart pointer" class; closes a USimpleNumberFormatter via usnumf_close(). + * For most methods see the LocalPointerBase base class. + * + * Usage: + *
+ * LocalUSimpleNumberFormatterPointer uformatter(usnumf_openForLocale(...));
+ * // no need to explicitly call usnumf_close()
+ * 
+ * + * @see LocalPointerBase + * @see LocalPointer + * @draft ICU 73 + */ +U_DEFINE_LOCAL_OPEN_POINTER(LocalUSimpleNumberFormatterPointer, USimpleNumberFormatter, usnumf_close); + +U_NAMESPACE_END +#endif // U_SHOW_CPLUSPLUS_API + +#endif // U_HIDE_DRAFT_API + +#endif /* #if !UCONFIG_NO_FORMATTING */ +#endif //__USIMPLENUMBERFORMATTER_H__ diff --git a/icu4c/source/test/depstest/dependencies.txt b/icu4c/source/test/depstest/dependencies.txt index 878dd95860a..9676ed4856d 100644 --- a/icu4c/source/test/depstest/dependencies.txt +++ b/icu4c/source/test/depstest/dependencies.txt @@ -1007,6 +1007,9 @@ group: numberformatter number_scientific.o currpinf.o numrange_fluent.o numrange_impl.o + # NOTE: This could go into its own dependency block, but it would require + # refactoring more of the dependencies (e.g. removing class methods). + number_simple.o deps decnumber double_conversion formattable units unitsformatter listformatter number_representation number_output diff --git a/icu4c/source/test/intltest/Makefile.in b/icu4c/source/test/intltest/Makefile.in index c4b5f6097bf..8007d3c1880 100644 --- a/icu4c/source/test/intltest/Makefile.in +++ b/icu4c/source/test/intltest/Makefile.in @@ -69,7 +69,8 @@ string_segment_test.o \ numbertest_parse.o numbertest_doubleconversion.o numbertest_skeletons.o \ static_unisets_test.o numfmtdatadriventest.o numbertest_range.o erarulestest.o \ formattedvaluetest.o formatted_string_builder_test.o numbertest_permutation.o \ -units_data_test.o units_router_test.o units_test.o displayoptions_test.o +units_data_test.o units_router_test.o units_test.o displayoptions_test.o \ +numbertest_simple.o DEPS = $(OBJECTS:.o=.d) diff --git a/icu4c/source/test/intltest/intltest.vcxproj b/icu4c/source/test/intltest/intltest.vcxproj index dd3cc6f1cbc..0985ba1e808 100644 --- a/icu4c/source/test/intltest/intltest.vcxproj +++ b/icu4c/source/test/intltest/intltest.vcxproj @@ -177,6 +177,7 @@ + diff --git a/icu4c/source/test/intltest/intltest.vcxproj.filters b/icu4c/source/test/intltest/intltest.vcxproj.filters index ebf3c7ec2aa..ffe9bc1467d 100644 --- a/icu4c/source/test/intltest/intltest.vcxproj.filters +++ b/icu4c/source/test/intltest/intltest.vcxproj.filters @@ -298,6 +298,9 @@ formatting + + formatting + formatting diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index f1d3b50a2a4..84a8e77cda1 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -365,6 +365,18 @@ class NumberRangeFormatterTest : public IntlTestWithFieldPosition { const char16_t* expected); }; +class SimpleNumberFormatterTest : public IntlTestWithFieldPosition { + public: + void testBasic(); + void testWithOptions(); + void testSymbols(); + void testSign(); + void testCopyMove(); + void testCAPI(); + + void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0) override; +}; + class NumberPermutationTest : public IntlTest { public: void testPermutations(); @@ -403,7 +415,8 @@ class NumberTest : public IntlTest { TESTCLASS(7, NumberParserTest); TESTCLASS(8, NumberSkeletonTest); TESTCLASS(9, NumberRangeFormatterTest); - TESTCLASS(10, NumberPermutationTest); + TESTCLASS(10, SimpleNumberFormatterTest); + TESTCLASS(11, NumberPermutationTest); default: name = ""; break; // needed to end loop } } diff --git a/icu4c/source/test/intltest/numbertest_simple.cpp b/icu4c/source/test/intltest/numbertest_simple.cpp new file mode 100644 index 00000000000..e6e42e7fbae --- /dev/null +++ b/icu4c/source/test/intltest/numbertest_simple.cpp @@ -0,0 +1,206 @@ +// © 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 + +#include + +#include "numbertest.h" +#include "unicode/simplenumberformatter.h" + + +void SimpleNumberFormatterTest::runIndexedTest(int32_t index, UBool exec, const char*& name, char*) { + if (exec) { + logln("TestSuite SimpleNumberFormatterTest: "); + } + TESTCASE_AUTO_BEGIN; + TESTCASE_AUTO(testBasic); + TESTCASE_AUTO(testWithOptions); + TESTCASE_AUTO(testSymbols); + TESTCASE_AUTO(testSign); + TESTCASE_AUTO(testCopyMove); + TESTCASE_AUTO(testCAPI); + TESTCASE_AUTO_END; +} + +void SimpleNumberFormatterTest::testBasic() { + IcuTestErrorCode status(*this, "testBasic"); + + SimpleNumberFormatter snf = SimpleNumberFormatter::forLocale("de-CH", status); + FormattedNumber result = snf.formatInt64(-1000007, status); + + static const UFieldPosition expectedFieldPositions[] = { + // field, begin index, end index + {UNUM_SIGN_FIELD, 0, 1}, + {UNUM_GROUPING_SEPARATOR_FIELD, 2, 3}, + {UNUM_GROUPING_SEPARATOR_FIELD, 6, 7}, + {UNUM_INTEGER_FIELD, 1, 10}, + }; + checkFormattedValue( + u"testBasic", + result, + u"-1’000’007", + UFIELD_CATEGORY_NUMBER, + expectedFieldPositions, + UPRV_LENGTHOF(expectedFieldPositions)); +} + +void SimpleNumberFormatterTest::testWithOptions() { + IcuTestErrorCode status(*this, "testWithOptions"); + + SimpleNumber num = SimpleNumber::forInt64(1250000, status); + num.setMinimumIntegerDigits(6, status); + num.setMinimumFractionDigits(2, status); + num.multiplyByPowerOfTen(-2, status); + num.roundTo(3, UNUM_ROUND_HALFUP, status); + num.truncateStart(4, status); + SimpleNumberFormatter snf = SimpleNumberFormatter::forLocale("bn", status); + FormattedNumber result = snf.format(std::move(num), status); + + static const UFieldPosition expectedFieldPositions[] = { + // field, begin index, end index + {UNUM_GROUPING_SEPARATOR_FIELD, 1, 2}, + {UNUM_GROUPING_SEPARATOR_FIELD, 4, 5}, + {UNUM_INTEGER_FIELD, 0, 8}, + {UNUM_DECIMAL_SEPARATOR_FIELD, 8, 9}, + {UNUM_FRACTION_FIELD, 9, 11}, + }; + checkFormattedValue( + u"testWithOptions", + result, + u"০,০৩,০০০.০০", + UFIELD_CATEGORY_NUMBER, + expectedFieldPositions, + UPRV_LENGTHOF(expectedFieldPositions)); +} + +void SimpleNumberFormatterTest::testSymbols() { + IcuTestErrorCode status(*this, "testSymbols"); + + LocalPointer symbols(new DecimalFormatSymbols("bn", status), status); + SimpleNumberFormatter snf = SimpleNumberFormatter::forLocaleAndSymbolsAndGroupingStrategy( + "en-US", + *symbols, + UNUM_GROUPING_ON_ALIGNED, + status + ); + auto result = snf.formatInt64(987654321, status); + + assertEquals("bn symbols with en-US pattern", + u"৯৮৭,৬৫৪,৩২১", + result.toTempString(status)); +} + +void SimpleNumberFormatterTest::testSign() { + IcuTestErrorCode status(*this, "testSign"); + + SimpleNumberFormatter snf = SimpleNumberFormatter::forLocale("und", status); + + struct TestCase { + int64_t input; + USimpleNumberSign sign; + const char16_t* expected; + } cases[] = { + { 1, UNUM_SIMPLE_NUMBER_NO_SIGN, u"1" }, + { 1, UNUM_SIMPLE_NUMBER_PLUS_SIGN, u"+1" }, + { 1, UNUM_SIMPLE_NUMBER_MINUS_SIGN, u"-1" }, + { 0, UNUM_SIMPLE_NUMBER_NO_SIGN, u"0" }, + { 0, UNUM_SIMPLE_NUMBER_PLUS_SIGN, u"+0" }, + { 0, UNUM_SIMPLE_NUMBER_MINUS_SIGN, u"-0" }, + { -1, UNUM_SIMPLE_NUMBER_NO_SIGN, u"1" }, + { -1, UNUM_SIMPLE_NUMBER_PLUS_SIGN, u"+1" }, + { -1, UNUM_SIMPLE_NUMBER_MINUS_SIGN, u"-1" }, + }; + for (auto& cas : cases) { + SimpleNumber num = SimpleNumber::forInt64(cas.input, status); + num.setSign(cas.sign, status); + auto result = snf.format(std::move(num), status); + assertEquals("", cas.expected, result.toTempString(status)); + } +} + +void SimpleNumberFormatterTest::testCopyMove() { + IcuTestErrorCode status(*this, "testCopyMove"); + + SimpleNumberFormatter snf0 = SimpleNumberFormatter::forLocale("und", status); + + SimpleNumber sn0 = SimpleNumber::forInt64(55, status); + SimpleNumber sn1 = std::move(sn0); + + snf0.format(std::move(sn0), status); + status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR, "Use of moved number"); + + assertEquals("Move number constructor", + u"55", + snf0.format(std::move(sn1), status).toTempString(status)); + + SimpleNumber sn2; + snf0.format(std::move(sn2), status); + status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR, "Default constructed number"); + + sn0 = SimpleNumber::forInt64(44, status); + + assertEquals("Move number assignment", + u"44", + snf0.format(std::move(sn0), status).toTempString(status)); + + SimpleNumberFormatter snf1 = std::move(snf0); + + snf0.format(SimpleNumber::forInt64(22, status), status); + status.expectErrorAndReset(U_INVALID_STATE_ERROR, "Use of moved formatter"); + + assertEquals("Move formatter constructor", + u"33", + snf1.format(SimpleNumber::forInt64(33, status), status).toTempString(status)); + + SimpleNumberFormatter snf2; + snf2.format(SimpleNumber::forInt64(22, status), status); + status.expectErrorAndReset(U_INVALID_STATE_ERROR, "Default constructed formatter"); + + snf0 = std::move(snf1); + + assertEquals("Move formatter assignment", + u"22", + snf0.format(SimpleNumber::forInt64(22, status), status).toTempString(status)); + + snf0 = SimpleNumberFormatter::forLocale("de", status); + sn0 = SimpleNumber::forInt64(22, status); + sn0 = SimpleNumber::forInt64(11, status); + + assertEquals("Move assignment with nonempty fields", + u"11", + snf0.format(std::move(sn0), status).toTempString(status)); +} + +void SimpleNumberFormatterTest::testCAPI() { + IcuTestErrorCode status(*this, "testCAPI"); + + LocalUSimpleNumberFormatterPointer uformatter(usnumf_openForLocale("de-CH", status)); + LocalUFormattedNumberPointer uresult(unumf_openResult(status)); + usnumf_formatInt64(uformatter.getAlias(), 55, uresult.getAlias(), status); + assertEquals("", + u"55", + ufmtval_getString(unumf_resultAsValue(uresult.getAlias(), status), nullptr, status)); + + LocalUSimpleNumberPointer unumber(usnum_openForInt64(44, status)); + usnumf_formatAndAdoptNumber(uformatter.getAlias(), unumber.orphan(), uresult.getAlias(), status); + assertEquals("", + u"44", + ufmtval_getString(unumf_resultAsValue(uresult.getAlias(), status), nullptr, status)); + + unumber.adoptInstead(usnum_openForInt64(2335, status)); + usnum_multiplyByPowerOfTen(unumber.getAlias(), -2, status); + usnum_roundTo(unumber.getAlias(), -1, UNUM_ROUND_HALFEVEN, status); + usnum_truncateStart(unumber.getAlias(), 1, status); + usnum_setMinimumFractionDigits(unumber.getAlias(), 3, status); + usnum_setMinimumIntegerDigits(unumber.getAlias(), 3, status); + usnumf_formatAndAdoptNumber(uformatter.getAlias(), unumber.orphan(), uresult.getAlias(), status); + assertEquals("", + u"003.400", + ufmtval_getString(unumf_resultAsValue(uresult.getAlias(), status), nullptr, status)); +} + + +#endif