diff --git a/icu4c/source/common/cmemory.h b/icu4c/source/common/cmemory.h index d3d60633a8f..9f8953d073e 100644 --- a/icu4c/source/common/cmemory.h +++ b/icu4c/source/common/cmemory.h @@ -384,7 +384,7 @@ public: */ inline T *orphanOrClone(int32_t length, int32_t &resultCapacity); - protected: + protected: // TODO(icu-units#64): make these private again if possible? T *ptr; int32_t capacity; diff --git a/icu4c/source/i18n/i18n.vcxproj b/icu4c/source/i18n/i18n.vcxproj index cef0ad344ee..9df696fc72f 100644 --- a/icu4c/source/i18n/i18n.vcxproj +++ b/icu4c/source/i18n/i18n.vcxproj @@ -219,6 +219,7 @@ + @@ -487,13 +488,14 @@ - + + diff --git a/icu4c/source/i18n/i18n.vcxproj.filters b/icu4c/source/i18n/i18n.vcxproj.filters index 4e65f29a2c4..83409f013d2 100644 --- a/icu4c/source/i18n/i18n.vcxproj.filters +++ b/icu4c/source/i18n/i18n.vcxproj.filters @@ -591,6 +591,9 @@ formatting + + formatting + formatting @@ -606,6 +609,9 @@ formatting + + formatting + formatting @@ -917,6 +923,9 @@ formatting + + formatting + formatting @@ -932,6 +941,9 @@ formatting + + formatting + formatting diff --git a/icu4c/source/i18n/number_fluent.cpp b/icu4c/source/i18n/number_fluent.cpp index 579f3062142..8569a36e5b2 100644 --- a/icu4c/source/i18n/number_fluent.cpp +++ b/icu4c/source/i18n/number_fluent.cpp @@ -520,123 +520,6 @@ LocalizedNumberFormatter UnlocalizedNumberFormatter::locale(const Locale& locale return LocalizedNumberFormatter(std::move(fMacros), locale); } -SymbolsWrapper::SymbolsWrapper(const SymbolsWrapper& other) { - doCopyFrom(other); -} - -SymbolsWrapper::SymbolsWrapper(SymbolsWrapper&& src) U_NOEXCEPT { - doMoveFrom(std::move(src)); -} - -SymbolsWrapper& SymbolsWrapper::operator=(const SymbolsWrapper& other) { - if (this == &other) { - return *this; - } - doCleanup(); - doCopyFrom(other); - return *this; -} - -SymbolsWrapper& SymbolsWrapper::operator=(SymbolsWrapper&& src) U_NOEXCEPT { - if (this == &src) { - return *this; - } - doCleanup(); - doMoveFrom(std::move(src)); - return *this; -} - -SymbolsWrapper::~SymbolsWrapper() { - doCleanup(); -} - -void SymbolsWrapper::setTo(const DecimalFormatSymbols& dfs) { - doCleanup(); - fType = SYMPTR_DFS; - fPtr.dfs = new DecimalFormatSymbols(dfs); -} - -void SymbolsWrapper::setTo(const NumberingSystem* ns) { - doCleanup(); - fType = SYMPTR_NS; - fPtr.ns = ns; -} - -void SymbolsWrapper::doCopyFrom(const SymbolsWrapper& other) { - fType = other.fType; - switch (fType) { - case SYMPTR_NONE: - // No action necessary - break; - case SYMPTR_DFS: - // Memory allocation failures are exposed in copyErrorTo() - if (other.fPtr.dfs != nullptr) { - fPtr.dfs = new DecimalFormatSymbols(*other.fPtr.dfs); - } else { - fPtr.dfs = nullptr; - } - break; - case SYMPTR_NS: - // Memory allocation failures are exposed in copyErrorTo() - if (other.fPtr.ns != nullptr) { - fPtr.ns = new NumberingSystem(*other.fPtr.ns); - } else { - fPtr.ns = nullptr; - } - break; - } -} - -void SymbolsWrapper::doMoveFrom(SymbolsWrapper&& src) { - fType = src.fType; - switch (fType) { - case SYMPTR_NONE: - // No action necessary - break; - case SYMPTR_DFS: - fPtr.dfs = src.fPtr.dfs; - src.fPtr.dfs = nullptr; - break; - case SYMPTR_NS: - fPtr.ns = src.fPtr.ns; - src.fPtr.ns = nullptr; - break; - } -} - -void SymbolsWrapper::doCleanup() { - switch (fType) { - case SYMPTR_NONE: - // No action necessary - break; - case SYMPTR_DFS: - delete fPtr.dfs; - break; - case SYMPTR_NS: - delete fPtr.ns; - break; - } -} - -bool SymbolsWrapper::isDecimalFormatSymbols() const { - return fType == SYMPTR_DFS; -} - -bool SymbolsWrapper::isNumberingSystem() const { - return fType == SYMPTR_NS; -} - -const DecimalFormatSymbols* SymbolsWrapper::getDecimalFormatSymbols() const { - U_ASSERT(fType == SYMPTR_DFS); - return fPtr.dfs; -} - -const NumberingSystem* SymbolsWrapper::getNumberingSystem() const { - U_ASSERT(fType == SYMPTR_NS); - return fPtr.ns; -} - - FormattedNumber LocalizedNumberFormatter::formatInt(int64_t value, UErrorCode& status) const { if (U_FAILURE(status)) { return FormattedNumber(U_ILLEGAL_ARGUMENT_ERROR); } auto results = new UFormattedNumberData(); diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp index b7aee30225f..3f2a00f7e7c 100644 --- a/icu4c/source/i18n/number_formatimpl.cpp +++ b/icu4c/source/i18n/number_formatimpl.cpp @@ -25,9 +25,6 @@ using namespace icu::number; using namespace icu::number::impl; -MicroPropsGenerator::~MicroPropsGenerator() = default; - - NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, UErrorCode& status) : NumberFormatterImpl(macros, true, status) { } @@ -255,16 +252,14 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, precision = Precision::integer().withMinDigits(2); } else if (isCurrency) { precision = Precision::currency(UCURR_USAGE_STANDARD); + } else if (macros.usage.isSet()) { + // Bogus Precision - it will get set in the UsagePrefsHandler instead + precision = Precision(); } else { precision = Precision::maxFraction(6); } UNumberFormatRoundingMode roundingMode; - if (macros.roundingMode != kDefaultMode) { - roundingMode = macros.roundingMode; - } else { - // Temporary until ICU 64 - roundingMode = precision.fRoundingMode; - } + roundingMode = macros.roundingMode; fMicros.rounder = {precision, roundingMode, currency, status}; if (U_FAILURE(status)) { return nullptr; diff --git a/icu4c/source/i18n/number_integerwidth.cpp b/icu4c/source/i18n/number_integerwidth.cpp index d62aef444dc..10b853423c8 100644 --- a/icu4c/source/i18n/number_integerwidth.cpp +++ b/icu4c/source/i18n/number_integerwidth.cpp @@ -40,6 +40,9 @@ IntegerWidth IntegerWidth::truncateAt(int32_t maxInt) { } void IntegerWidth::apply(impl::DecimalQuantity& quantity, UErrorCode& status) const { + if (U_FAILURE(status)) { + return; + } if (fHasError) { status = U_ILLEGAL_ARGUMENT_ERROR; } else if (fUnion.minMaxInt.fMaxInt == -1) { diff --git a/icu4c/source/i18n/number_longnames.cpp b/icu4c/source/i18n/number_longnames.cpp index 8ef926e634a..6874469b2de 100644 --- a/icu4c/source/i18n/number_longnames.cpp +++ b/icu4c/source/i18n/number_longnames.cpp @@ -572,6 +572,9 @@ void LongNameMultiplexer::processQuantity(DecimalQuantity &quantity, MicroProps return; } } + if (U_FAILURE(status)) { + return; + } // We shouldn't receive any outputUnit for which we haven't already got a // LongNameHandler: status = U_INTERNAL_PROGRAM_ERROR; diff --git a/icu4c/source/i18n/number_longnames.h b/icu4c/source/i18n/number_longnames.h index 38580a02334..59b41c97c43 100644 --- a/icu4c/source/i18n/number_longnames.h +++ b/icu4c/source/i18n/number_longnames.h @@ -201,7 +201,7 @@ class MixedUnitLongNameHandler : public MicroPropsGenerator, public ModifierStor /** * A MicroPropsGenerator that multiplexes between different LongNameHandlers, * depending on the outputUnit. - * + * * See processQuantity() for the input requirements. */ class LongNameMultiplexer : public MicroPropsGenerator, public UMemory { diff --git a/icu4c/source/i18n/number_mapper.cpp b/icu4c/source/i18n/number_mapper.cpp index ec617438c9a..e2a0d284b7c 100644 --- a/icu4c/source/i18n/number_mapper.cpp +++ b/icu4c/source/i18n/number_mapper.cpp @@ -92,6 +92,8 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert int32_t minSig = properties.minimumSignificantDigits; int32_t maxSig = properties.maximumSignificantDigits; double roundingIncrement = properties.roundingIncrement; + // Not assigning directly to macros.roundingMode here: we change + // roundingMode if and when we also change macros.precision. RoundingMode roundingMode = properties.roundingMode.getOrDefault(UNUM_ROUND_HALFEVEN); bool explicitMinMaxFrac = minFrac != -1 || maxFrac != -1; bool explicitMinMaxSig = minSig != -1 || maxSig != -1; @@ -145,7 +147,7 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert precision = Precision::constructCurrency(currencyUsage); } if (!precision.isBogus()) { - precision.fRoundingMode = roundingMode; + macros.roundingMode = roundingMode; macros.precision = precision; } @@ -239,7 +241,7 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert // TODO: Reset maxSig_ = 1 + minFrac_ to follow the spec. macros.precision = Precision::constructSignificant(minSig_, maxSig_); } - macros.precision.fRoundingMode = roundingMode; + macros.roundingMode = roundingMode; } } diff --git a/icu4c/source/i18n/number_rounding.cpp b/icu4c/source/i18n/number_rounding.cpp index 3ffce673ad0..14896679696 100644 --- a/icu4c/source/i18n/number_rounding.cpp +++ b/icu4c/source/i18n/number_rounding.cpp @@ -5,13 +5,16 @@ #if !UCONFIG_NO_FORMATTING +#include "charstr.h" #include "uassert.h" #include "unicode/numberformatter.h" #include "number_types.h" #include "number_decimalquantity.h" #include "double-conversion.h" #include "number_roundingutils.h" +#include "number_skeletons.h" #include "putilimp.h" +#include "string_segment.h" using namespace icu; using namespace icu::number; @@ -19,6 +22,40 @@ using namespace icu::number::impl; using double_conversion::DoubleToStringConverter; +using icu::StringSegment; + +// Most blueprint_helpers live in number_skeletons.cpp. This one is in +// number_rounding.cpp for dependency reasons. +void blueprint_helpers::parseIncrementOption(const StringSegment &segment, MacroProps ¯os, + UErrorCode &status) { + // Need to do char <-> UChar conversion... + U_ASSERT(U_SUCCESS(status)); + CharString buffer; + SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status); + + // Utilize DecimalQuantity/decNumber to parse this for us. + DecimalQuantity dq; + UErrorCode localStatus = U_ZERO_ERROR; + dq.setToDecNumber({buffer.data(), buffer.length()}, localStatus); + if (U_FAILURE(localStatus)) { + // throw new SkeletonSyntaxException("Invalid rounding increment", segment, e); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return; + } + double increment = dq.toDouble(); + + // We also need to figure out how many digits. Do a brute force string operation. + int decimalOffset = 0; + while (decimalOffset < segment.length() && segment.charAt(decimalOffset) != '.') { + decimalOffset++; + } + if (decimalOffset == segment.length()) { + macros.precision = Precision::increment(increment); + } else { + int32_t fractionLength = segment.length() - decimalOffset - 1; + macros.precision = Precision::increment(increment).withMinFraction(fractionLength); + } +} namespace { @@ -84,7 +121,7 @@ digits_t roundingutils::doubleFractionLength(double input, int8_t* singleDigit) Precision Precision::unlimited() { - return Precision(RND_NONE, {}, kDefaultMode); + return Precision(RND_NONE, {}); } FractionPrecision Precision::integer() { @@ -229,7 +266,7 @@ FractionPrecision Precision::constructFraction(int32_t minFrac, int32_t maxFrac) settings.fMaxSig = -1; PrecisionUnion union_; union_.fracSig = settings; - return {RND_FRACTION, union_, kDefaultMode}; + return {RND_FRACTION, union_}; } Precision Precision::constructSignificant(int32_t minSig, int32_t maxSig) { @@ -240,7 +277,7 @@ Precision Precision::constructSignificant(int32_t minSig, int32_t maxSig) { settings.fMaxSig = static_cast(maxSig); PrecisionUnion union_; union_.fracSig = settings; - return {RND_SIGNIFICANT, union_, kDefaultMode}; + return {RND_SIGNIFICANT, union_}; } Precision @@ -250,7 +287,7 @@ Precision::constructFractionSignificant(const FractionPrecision &base, int32_t m settings.fMaxSig = static_cast(maxSig); PrecisionUnion union_; union_.fracSig = settings; - return {RND_FRACTION_SIGNIFICANT, union_, kDefaultMode}; + return {RND_FRACTION_SIGNIFICANT, union_}; } IncrementPrecision Precision::constructIncrement(double increment, int32_t minFrac) { @@ -270,18 +307,18 @@ IncrementPrecision Precision::constructIncrement(double increment, int32_t minFr // NOTE: In C++, we must return the correct value type with the correct union. // It would be invalid to return a RND_FRACTION here because the methods on the // IncrementPrecision type assume that the union is backed by increment data. - return {RND_INCREMENT_ONE, union_, kDefaultMode}; + return {RND_INCREMENT_ONE, union_}; } else if (singleDigit == 5) { - return {RND_INCREMENT_FIVE, union_, kDefaultMode}; + return {RND_INCREMENT_FIVE, union_}; } else { - return {RND_INCREMENT, union_, kDefaultMode}; + return {RND_INCREMENT, union_}; } } CurrencyPrecision Precision::constructCurrency(UCurrencyUsage usage) { PrecisionUnion union_; union_.currencyUsage = usage; - return {RND_CURRENCY, union_, kDefaultMode}; + return {RND_CURRENCY, union_}; } @@ -341,6 +378,9 @@ RoundingImpl::chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl: /** This is the method that contains the actual rounding logic. */ void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const { + if (U_FAILURE(status)) { + return; + } if (fPassThrough) { return; } diff --git a/icu4c/source/i18n/number_roundingutils.h b/icu4c/source/i18n/number_roundingutils.h index 3e37f319540..841854fab54 100644 --- a/icu4c/source/i18n/number_roundingutils.h +++ b/icu4c/source/i18n/number_roundingutils.h @@ -44,6 +44,9 @@ enum Section { inline bool getRoundingDirection(bool isEven, bool isNegative, Section section, RoundingMode roundingMode, UErrorCode &status) { + if (U_FAILURE(status)) { + return false; + } switch (roundingMode) { case RoundingMode::UNUM_ROUND_UP: // round away from zero @@ -187,6 +190,9 @@ class RoundingImpl { Precision fPrecision; UNumberFormatRoundingMode fRoundingMode; bool fPassThrough = true; // default value + + // Permits access to fPrecision. + friend class UsagePrefsHandler; }; diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp index 8302e8bcedf..2173aac70f6 100644 --- a/icu4c/source/i18n/number_skeletons.cpp +++ b/icu4c/source/i18n/number_skeletons.cpp @@ -152,21 +152,6 @@ UPRV_BLOCK_MACRO_BEGIN { \ } UPRV_BLOCK_MACRO_END -#define SKELETON_UCHAR_TO_CHAR(dest, src, start, end, status) (void)(dest); \ -UPRV_BLOCK_MACRO_BEGIN { \ - UErrorCode conversionStatus = U_ZERO_ERROR; \ - (dest).appendInvariantChars({FALSE, (src).getBuffer() + (start), (end) - (start)}, conversionStatus); \ - if (conversionStatus == U_INVARIANT_CONVERSION_ERROR) { \ - /* Don't propagate the invariant conversion error; it is a skeleton syntax error */ \ - (status) = U_NUMBER_SKELETON_SYNTAX_ERROR; \ - return; \ - } else if (U_FAILURE(conversionStatus)) { \ - (status) = conversionStatus; \ - return; \ - } \ -} UPRV_BLOCK_MACRO_END - - } // anonymous namespace @@ -483,6 +468,7 @@ UnicodeString skeleton::generate(const MacroProps& macros, UErrorCode& status) { MacroProps skeleton::parseSkeleton( const UnicodeString& skeletonString, int32_t& errOffset, UErrorCode& status) { U_ASSERT(U_SUCCESS(status)); + U_ASSERT(kSerializedStemTrie != nullptr); // Add a trailing whitespace to the end of the skeleton string to make code cleaner. UnicodeString tempSkeletonString(skeletonString); @@ -589,6 +575,8 @@ MacroProps skeleton::parseSkeleton( ParseState skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, SeenMacroProps& seen, MacroProps& macros, UErrorCode& status) { + U_ASSERT(U_SUCCESS(status)); + // First check for "blueprint" stems, which start with a "signal char" switch (segment.charAt(0)) { case u'.': @@ -767,6 +755,7 @@ skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, Se ParseState skeleton::parseOption(ParseState stem, const StringSegment& segment, MacroProps& macros, UErrorCode& status) { + U_ASSERT(U_SUCCESS(status)); ///// Required options: ///// @@ -995,6 +984,7 @@ blueprint_helpers::generateCurrencyOption(const CurrencyUnit& currency, UnicodeS void blueprint_helpers::parseMeasureUnitOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status) { + U_ASSERT(U_SUCCESS(status)); const UnicodeString stemString = segment.toTempUnicodeString(); // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric) @@ -1010,7 +1000,6 @@ void blueprint_helpers::parseMeasureUnitOption(const StringSegment& segment, Mac } // Need to do char <-> UChar conversion... - U_ASSERT(U_SUCCESS(status)); CharString type; SKELETON_UCHAR_TO_CHAR(type, stemString, 0, firstHyphen, status); CharString subType; @@ -1347,36 +1336,8 @@ bool blueprint_helpers::parseFracSigOption(const StringSegment& segment, MacroPr return true; } -void blueprint_helpers::parseIncrementOption(const StringSegment& segment, MacroProps& macros, - UErrorCode& status) { - // Need to do char <-> UChar conversion... - U_ASSERT(U_SUCCESS(status)); - CharString buffer; - SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status); - - // Utilize DecimalQuantity/decNumber to parse this for us. - DecimalQuantity dq; - UErrorCode localStatus = U_ZERO_ERROR; - dq.setToDecNumber({buffer.data(), buffer.length()}, localStatus); - if (U_FAILURE(localStatus)) { - // throw new SkeletonSyntaxException("Invalid rounding increment", segment, e); - status = U_NUMBER_SKELETON_SYNTAX_ERROR; - return; - } - double increment = dq.toDouble(); - - // We also need to figure out how many digits. Do a brute force string operation. - int decimalOffset = 0; - while (decimalOffset < segment.length() && segment.charAt(decimalOffset) != '.') { - decimalOffset++; - } - if (decimalOffset == segment.length()) { - macros.precision = Precision::increment(increment); - } else { - int32_t fractionLength = segment.length() - decimalOffset - 1; - macros.precision = Precision::increment(increment).withMinFraction(fractionLength); - } -} +// blueprint_helpers::parseIncrementOption lives in number_rounding.cpp for +// dependencies reasons. void blueprint_helpers::generateIncrementOption(double increment, int32_t trailingZeros, UnicodeString& sb, UErrorCode&) { @@ -1591,7 +1552,7 @@ bool GeneratorHelpers::perUnit(const MacroProps& macros, UnicodeString& sb, UErr } } -bool GeneratorHelpers::usage(const MacroProps& macros, UnicodeString& sb, UErrorCode&) { +bool GeneratorHelpers::usage(const MacroProps& macros, UnicodeString& sb, UErrorCode& /* status */) { if (macros.usage.fLength > 0) { sb.append(u"usage/", -1); sb.append(UnicodeString(macros.usage.fUsage, -1, US_INV)); diff --git a/icu4c/source/i18n/number_skeletons.h b/icu4c/source/i18n/number_skeletons.h index 1326a2aef70..64cb60f3089 100644 --- a/icu4c/source/i18n/number_skeletons.h +++ b/icu4c/source/i18n/number_skeletons.h @@ -359,6 +359,24 @@ struct SeenMacroProps { bool scale = false; }; +namespace { + +#define SKELETON_UCHAR_TO_CHAR(dest, src, start, end, status) (void)(dest); \ +UPRV_BLOCK_MACRO_BEGIN { \ + UErrorCode conversionStatus = U_ZERO_ERROR; \ + (dest).appendInvariantChars({FALSE, (src).getBuffer() + (start), (end) - (start)}, conversionStatus); \ + if (conversionStatus == U_INVARIANT_CONVERSION_ERROR) { \ + /* Don't propagate the invariant conversion error; it is a skeleton syntax error */ \ + (status) = U_NUMBER_SKELETON_SYNTAX_ERROR; \ + return; \ + } else if (U_FAILURE(conversionStatus)) { \ + (status) = conversionStatus; \ + return; \ + } \ +} UPRV_BLOCK_MACRO_END + +} // namespace + } // namespace impl } // namespace number U_NAMESPACE_END diff --git a/icu4c/source/i18n/number_symbolswrapper.cpp b/icu4c/source/i18n/number_symbolswrapper.cpp new file mode 100644 index 00000000000..5f7648d7039 --- /dev/null +++ b/icu4c/source/i18n/number_symbolswrapper.cpp @@ -0,0 +1,125 @@ +// © 2020 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "number_microprops.h" +#include "unicode/numberformatter.h" + +using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; + +SymbolsWrapper::SymbolsWrapper(const SymbolsWrapper &other) { + doCopyFrom(other); +} + +SymbolsWrapper::SymbolsWrapper(SymbolsWrapper &&src) U_NOEXCEPT { + doMoveFrom(std::move(src)); +} + +SymbolsWrapper &SymbolsWrapper::operator=(const SymbolsWrapper &other) { + if (this == &other) { + return *this; + } + doCleanup(); + doCopyFrom(other); + return *this; +} + +SymbolsWrapper &SymbolsWrapper::operator=(SymbolsWrapper &&src) U_NOEXCEPT { + if (this == &src) { + return *this; + } + doCleanup(); + doMoveFrom(std::move(src)); + return *this; +} + +SymbolsWrapper::~SymbolsWrapper() { + doCleanup(); +} + +void SymbolsWrapper::setTo(const DecimalFormatSymbols &dfs) { + doCleanup(); + fType = SYMPTR_DFS; + fPtr.dfs = new DecimalFormatSymbols(dfs); +} + +void SymbolsWrapper::setTo(const NumberingSystem *ns) { + doCleanup(); + fType = SYMPTR_NS; + fPtr.ns = ns; +} + +void SymbolsWrapper::doCopyFrom(const SymbolsWrapper &other) { + fType = other.fType; + switch (fType) { + case SYMPTR_NONE: + // No action necessary + break; + case SYMPTR_DFS: + // Memory allocation failures are exposed in copyErrorTo() + if (other.fPtr.dfs != nullptr) { + fPtr.dfs = new DecimalFormatSymbols(*other.fPtr.dfs); + } else { + fPtr.dfs = nullptr; + } + break; + case SYMPTR_NS: + // Memory allocation failures are exposed in copyErrorTo() + if (other.fPtr.ns != nullptr) { + fPtr.ns = new NumberingSystem(*other.fPtr.ns); + } else { + fPtr.ns = nullptr; + } + break; + } +} + +void SymbolsWrapper::doMoveFrom(SymbolsWrapper &&src) { + fType = src.fType; + switch (fType) { + case SYMPTR_NONE: + // No action necessary + break; + case SYMPTR_DFS: + fPtr.dfs = src.fPtr.dfs; + src.fPtr.dfs = nullptr; + break; + case SYMPTR_NS: + fPtr.ns = src.fPtr.ns; + src.fPtr.ns = nullptr; + break; + } +} + +void SymbolsWrapper::doCleanup() { + switch (fType) { + case SYMPTR_NONE: + // No action necessary + break; + case SYMPTR_DFS: + delete fPtr.dfs; + break; + case SYMPTR_NS: + delete fPtr.ns; + break; + } +} + +bool SymbolsWrapper::isDecimalFormatSymbols() const { + return fType == SYMPTR_DFS; +} + +bool SymbolsWrapper::isNumberingSystem() const { + return fType == SYMPTR_NS; +} + +const DecimalFormatSymbols *SymbolsWrapper::getDecimalFormatSymbols() const { + U_ASSERT(fType == SYMPTR_DFS); + return fPtr.dfs; +} + +const NumberingSystem *SymbolsWrapper::getNumberingSystem() const { + U_ASSERT(fType == SYMPTR_NS); + return fPtr.ns; +} diff --git a/icu4c/source/i18n/number_types.h b/icu4c/source/i18n/number_types.h index 8180fe55317..8078851ba3f 100644 --- a/icu4c/source/i18n/number_types.h +++ b/icu4c/source/i18n/number_types.h @@ -262,7 +262,7 @@ class U_I18N_API ModifierStore { */ class U_I18N_API MicroPropsGenerator { public: - virtual ~MicroPropsGenerator(); + virtual ~MicroPropsGenerator() = default; /** * Considers the given {@link DecimalQuantity}, optionally mutates it, and diff --git a/icu4c/source/i18n/number_usageprefs.cpp b/icu4c/source/i18n/number_usageprefs.cpp index 223ea12616a..5fcdcc3805f 100644 --- a/icu4c/source/i18n/number_usageprefs.cpp +++ b/icu4c/source/i18n/number_usageprefs.cpp @@ -8,6 +8,7 @@ #include "number_decimalquantity.h" #include "number_microprops.h" #include "number_roundingutils.h" +#include "number_skeletons.h" #include "unicode/char16ptr.h" #include "unicode/currunit.h" #include "unicode/fmtable.h" @@ -19,6 +20,7 @@ using namespace icu::number; using namespace icu::number::impl; +using icu::StringSegment; // Copy constructor Usage::Usage(const Usage &other) : fUsage(nullptr), fLength(other.fLength), fError(other.fError) { @@ -97,6 +99,9 @@ void UsagePrefsHandler::processQuantity(DecimalQuantity &quantity, MicroProps &m quantity.roundToInfinity(); // Enables toDouble const auto routed = fUnitsRouter.route(quantity.toDouble(), status); + if (U_FAILURE(status)) { + return; + } const MaybeStackVector& routedUnits = routed.measures; micros.outputUnit = routed.outputUnit.copy(status).build(status); if (U_FAILURE(status)) { @@ -135,21 +140,37 @@ void UsagePrefsHandler::processQuantity(DecimalQuantity &quantity, MicroProps &m // The last value (potentially the only value) gets passed on via quantity. quantity.setToDouble(routedUnits[routedUnits.length() - 1]->getNumber().getDouble()); - // TODO(units): here we are always overriding Precision. (1) get precision - // from fUnitsRouter, (2) ensure we use the UnitPreference skeleton's - // precision only when there isn't an explicit override we prefer to use. - // This needs to be handled within - // NumberFormatterImpl::macrosToMicroGenerator in number_formatimpl.cpp - // TODO: Use precision from `routed` result. - Precision precision = Precision::integer().withMinDigits(2); - UNumberFormatRoundingMode roundingMode; - // Temporary until ICU 64? - roundingMode = precision.fRoundingMode; - CurrencyUnit currency(u"", status); - micros.rounder = {precision, roundingMode, currency, status}; - if (U_FAILURE(status)) { - return; + UnicodeString precisionSkeleton = routed.precision; + if (micros.rounder.fPrecision.isBogus()) { + if (precisionSkeleton.length() > 0) { + micros.rounder.fPrecision = parseSkeletonToPrecision(precisionSkeleton, status); + } else { + // We use the same rounding mode as COMPACT notation: known to be a + // human-friendly rounding mode: integers, but add a decimal digit + // as needed to ensure we have at least 2 significant digits. + micros.rounder.fPrecision = Precision::integer().withMinDigits(2); + } } } +Precision UsagePrefsHandler::parseSkeletonToPrecision(icu::UnicodeString precisionSkeleton, + UErrorCode status) { + if (U_FAILURE(status)) { + // As a member of UsagePrefsHandler, which is a friend of Precision, we + // get access to the default constructor. + return {}; + } + constexpr int32_t kSkelPrefixLen = 20; + if (!precisionSkeleton.startsWith(UNICODE_STRING_SIMPLE("precision-increment/"))) { + status = U_INVALID_FORMAT_ERROR; + return {}; + } + U_ASSERT(precisionSkeleton[kSkelPrefixLen - 1] == u'/'); + StringSegment segment(precisionSkeleton, false); + segment.adjustOffset(kSkelPrefixLen); + MacroProps macros; + blueprint_helpers::parseIncrementOption(segment, macros, status); + return macros.precision; +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_usageprefs.h b/icu4c/source/i18n/number_usageprefs.h index 616d5973484..4ed58d1643b 100644 --- a/icu4c/source/i18n/number_usageprefs.h +++ b/icu4c/source/i18n/number_usageprefs.h @@ -57,6 +57,8 @@ class U_I18N_API UsagePrefsHandler : public MicroPropsGenerator, public UMemory private: UnitsRouter fUnitsRouter; const MicroPropsGenerator *fParent; + + static Precision parseSkeletonToPrecision(icu::UnicodeString precisionSkeleton, UErrorCode status); }; } // namespace impl diff --git a/icu4c/source/i18n/sources.txt b/icu4c/source/i18n/sources.txt index 5e77d679aac..2d95da6cdf5 100644 --- a/icu4c/source/i18n/sources.txt +++ b/icu4c/source/i18n/sources.txt @@ -123,6 +123,7 @@ number_patternstring.cpp number_rounding.cpp number_scientific.cpp number_skeletons.cpp +number_symbolswrapper.cpp number_usageprefs.cpp number_utils.cpp numfmt.cpp diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index b17e25373e6..b2c22bb135f 100644 --- a/icu4c/source/i18n/unicode/numberformatter.h +++ b/icu4c/source/i18n/unicode/numberformatter.h @@ -707,12 +707,8 @@ class U_I18N_API Precision : public UMemory { typedef PrecisionUnion::FractionSignificantSettings FractionSignificantSettings; typedef PrecisionUnion::IncrementSettings IncrementSettings; - /** The Precision encapsulates the RoundingMode when used within the implementation. */ - UNumberFormatRoundingMode fRoundingMode; - - Precision(const PrecisionType& type, const PrecisionUnion& union_, - UNumberFormatRoundingMode roundingMode) - : fType(type), fUnion(union_), fRoundingMode(roundingMode) {} + Precision(const PrecisionType& type, const PrecisionUnion& union_) + : fType(type), fUnion(union_) {} Precision(UErrorCode errorCode) : fType(RND_ERROR) { fUnion.errorCode = errorCode; @@ -746,8 +742,6 @@ class U_I18N_API Precision : public UMemory { static CurrencyPrecision constructCurrency(UCurrencyUsage usage); - static Precision constructPassThrough(); - // To allow MacroProps/MicroProps to initialize bogus instances: friend struct impl::MacroProps; friend struct impl::MicroProps; @@ -769,9 +763,7 @@ class U_I18N_API Precision : public UMemory { // To allow access to the skeleton generation code: friend class impl::GeneratorHelpers; - // TODO(units): revisit when UnitsRouter is changed: do we still need this - // once Precision is returned by UnitsRouter? For now, we allow access to - // Precision constructor from UsagePrefsHandler: + // To allow access to isBogus and the default (bogus) constructor: friend class impl::UsagePrefsHandler; }; diff --git a/icu4c/source/i18n/unitsrouter.h b/icu4c/source/i18n/unitsrouter.h index 746af892694..1cade00885b 100644 --- a/icu4c/source/i18n/unitsrouter.h +++ b/icu4c/source/i18n/unitsrouter.h @@ -25,7 +25,16 @@ class Measure; namespace units { struct RouteResult : UMemory { + // A list of measures: a single measure for single units, multiple measures + // for mixed units. + // + // TODO(icu-units/icu#21): figure out the right mixed unit API. MaybeStackVector measures; + + // A skeleton string starting with a precision-increment. + // + // TODO(hugovdm): generalise? or narrow down to only a precision-increment? + // or document that other skeleton elements are ignored? UnicodeString precision; // The output unit for this RouteResult. This may be a MIXED unit - for diff --git a/icu4c/source/test/depstest/dependencies.txt b/icu4c/source/test/depstest/dependencies.txt index 09d8ae90fff..44e41e28c57 100644 --- a/icu4c/source/test/depstest/dependencies.txt +++ b/icu4c/source/test/depstest/dependencies.txt @@ -869,7 +869,8 @@ library: i18n dayperiodrules listformatter formatting formattable_cnv regex regex_cnv translit - double_conversion number_representation number_output numberformatter number_skeletons numberparser + double_conversion number_representation number_output numberformatter + number_skeletons number_usageprefs numberparser units_extra unitsformatter universal_time_scale uclean_i18n @@ -989,16 +990,41 @@ group: numberformatter number_decimfmtprops.o number_fluent.o number_formatimpl.o number_grouping.o number_integerwidth.o number_longnames.o - number_mapper.o number_modifiers.o number_multiplier.o + number_mapper.o number_modifiers.o number_notation.o number_padding.o - number_patternmodifier.o number_patternstring.o number_rounding.o - number_scientific.o number_usageprefs.o - currpinf.o dcfmtsym.o numsys.o + number_patternmodifier.o number_patternstring.o + number_scientific.o + currpinf.o numrange_fluent.o numrange_impl.o deps decnumber double_conversion formattable units unitsformatter listformatter number_representation number_output + numsys + number_usageprefs uclean_i18n common + number_symbolswrapper + +group: numsys + dcfmtsym.o + numsys.o + deps + currency + resourcebundle + uclean_i18n + +group: number_usageprefs + number_multiplier.o + number_usageprefs.o + deps + number_rounding + number_symbolswrapper + unitsformatter + +group: number_rounding + number_rounding.o + deps + currency + number_representation group: number_skeletons # Number skeleton support; separated from numberformatter @@ -1007,6 +1033,12 @@ group: number_skeletons numberformatter units_extra +group: number_symbolswrapper + number_symbolswrapper.o + deps + platform + numsys + group: numberparser numparse_affixes.o numparse_compositions.o numparse_currency.o numparse_decimal.o numparse_impl.o numparse_parsednumber.o diff --git a/icu4c/source/test/intltest/measfmttest.cpp b/icu4c/source/test/intltest/measfmttest.cpp index f17647e4245..5dd85cf2124 100644 --- a/icu4c/source/test/intltest/measfmttest.cpp +++ b/icu4c/source/test/intltest/measfmttest.cpp @@ -81,6 +81,7 @@ private: void TestNumericTimeSomeSpecialFormats(); void TestIdentifiers(); void TestInvalidIdentifiers(); + void TestParseToBuiltIn(); void TestCompoundUnitOperations(); void TestDimensionlessBehaviour(); void Test21060_AddressSanitizerProblem(); @@ -205,6 +206,7 @@ void MeasureFormatTest::runIndexedTest( TESTCASE_AUTO(TestNumericTimeSomeSpecialFormats); TESTCASE_AUTO(TestIdentifiers); TESTCASE_AUTO(TestInvalidIdentifiers); + TESTCASE_AUTO(TestParseToBuiltIn); TESTCASE_AUTO(TestCompoundUnitOperations); TESTCASE_AUTO(TestDimensionlessBehaviour); TESTCASE_AUTO(Test21060_AddressSanitizerProblem); @@ -3333,6 +3335,33 @@ void MeasureFormatTest::TestInvalidIdentifiers() { } } +void MeasureFormatTest::TestParseToBuiltIn() { + IcuTestErrorCode status(*this, "TestParseToBuiltIn()"); + const struct TestCase { + const char *identifier; + MeasureUnit expectedBuiltIn; + } cases[] = { + {"meter-per-second-per-second", MeasureUnit::getMeterPerSecondSquared()}, + {"meter-per-second-second", MeasureUnit::getMeterPerSecondSquared()}, + {"centimeter-centimeter", MeasureUnit::getSquareCentimeter()}, + {"square-foot", MeasureUnit::getSquareFoot()}, + {"pow2-inch", MeasureUnit::getSquareInch()}, + {"milligram-per-deciliter", MeasureUnit::getMilligramPerDeciliter()}, + {"pound-force-per-pow2-inch", MeasureUnit::getPoundPerSquareInch()}, + {"yard-pow2-yard", MeasureUnit::getCubicYard()}, + {"square-yard-yard", MeasureUnit::getCubicYard()}, + }; + + for (auto &cas : cases) { + MeasureUnit fromIdent = MeasureUnit::forIdentifier(cas.identifier, status); + status.assertSuccess(); + assertEquals("forIdentifier returns a normal built-in unit when it exists", + cas.expectedBuiltIn.getOffset(), fromIdent.getOffset()); + assertEquals("type", cas.expectedBuiltIn.getType(), fromIdent.getType()); + assertEquals("subType", cas.expectedBuiltIn.getSubtype(), fromIdent.getSubtype()); + } +} + void MeasureFormatTest::TestCompoundUnitOperations() { IcuTestErrorCode status(*this, "TestCompoundUnitOperations"); diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index 9bd2ed2f24e..6659465c2f9 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -49,16 +49,15 @@ class NumberFormatterApiTest : public IntlTestWithFieldPosition { NumberFormatterApiTest(); NumberFormatterApiTest(UErrorCode &status); - void microPropsInternals(void); - void unitPipeline(void); - void notationSimple(); void notationScientific(); void notationCompact(); void unitMeasure(); + void unitPipeline(); void unitCompoundMeasure(); void unitUsage(); void unitUsageErrorCodes(); + void unitUsageSkeletons(); void unitCurrency(); void unitPercent(); void percentParity(); @@ -89,6 +88,7 @@ class NumberFormatterApiTest : public IntlTestWithFieldPosition { void localPointerCAPI(); void toObject(); void toDecimalNumber(); + void microPropsInternals(); void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = 0); diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index e59f7543bcf..f3d5231d905 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -79,6 +79,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha TESTCASE_AUTO(unitCompoundMeasure); TESTCASE_AUTO(unitUsage); TESTCASE_AUTO(unitUsageErrorCodes); + TESTCASE_AUTO(unitUsageSkeletons); TESTCASE_AUTO(unitCurrency); TESTCASE_AUTO(unitPercent); if (!quick) { @@ -694,7 +695,7 @@ void NumberFormatterApiTest::unitUsage() { uTestCase = u"unitUsage() en-ZA road"; formatter = unloc_formatter.locale("en-ZA"); - formattedNum = formatter.formatDouble(300, status); + formattedNum = formatter.formatDouble(321, status); status.errIfFailureAndReset("unitUsage() en-ZA road formatDouble"); assertTrue( uTestCase + ", got outputUnit: \"" + formattedNum.getOutputUnit(status).getIdentifier() + "\"", @@ -711,19 +712,24 @@ void NumberFormatterApiTest::unitUsage() { u"877 km", u"88 km", u"8,8 km", - u"877 m", - u"88 m", - u"8,8 m", + u"900 m", + u"90 m", + u"10 m", u"0 m"); uTestCase = u"unitUsage() en-GB road"; formatter = unloc_formatter.locale("en-GB"); - formattedNum = formatter.formatDouble(300, status); - status.errIfFailureAndReset("unitUsage() en-GB road formatDouble"); - assertTrue( - uTestCase + ", got outputUnit: \"" + formattedNum.getOutputUnit(status).getIdentifier() + "\"", - MeasureUnit::getYard() == formattedNum.getOutputUnit(status)); - assertEquals(uTestCase, "328 yd", formattedNum.toString(status)); + formattedNum = formatter.formatDouble(321, status); + status.errIfFailureAndReset("unitUsage() en-GB road, formatDouble(...)"); + U_ASSERT(status == U_ZERO_ERROR); + assertTrue(UnicodeString("unitUsage() en-GB road, got outputUnit: \"") + + formattedNum.getOutputUnit(status).getIdentifier() + "\"", + MeasureUnit::getYard() == formattedNum.getOutputUnit(status)); + status.errIfFailureAndReset("unitUsage() en-GB road, getOutputUnit(...)"); + U_ASSERT(status == U_ZERO_ERROR); + assertEquals("unitUsage() en-GB road", "350 yd", formattedNum.toString(status)); + status.errIfFailureAndReset("unitUsage() en-GB road, toString(...)"); + U_ASSERT(status == U_ZERO_ERROR); assertFormatDescendingBig( uTestCase.getTerminatedBuffer(), u"measure-unit/length-meter usage/road", @@ -742,12 +748,17 @@ void NumberFormatterApiTest::unitUsage() { uTestCase = u"unitUsage() en-US road"; formatter = unloc_formatter.locale("en-US"); - formattedNum = formatter.formatDouble(300, status); - status.errIfFailureAndReset("unitUsage() en-US road formatDouble"); - assertTrue( - uTestCase + ", got outputUnit: \"" + formattedNum.getOutputUnit(status).getIdentifier() + "\"", - MeasureUnit::getFoot() == formattedNum.getOutputUnit(status)); - assertEquals(uTestCase, "984 ft", formattedNum.toString(status)); + formattedNum = formatter.formatDouble(321, status); + status.errIfFailureAndReset("unitUsage() en-US road, formatDouble(...)"); + U_ASSERT(status == U_ZERO_ERROR); + assertTrue(UnicodeString("unitUsage() en-US road, got outputUnit: \"") + + formattedNum.getOutputUnit(status).getIdentifier() + "\"", + MeasureUnit::getFoot() == formattedNum.getOutputUnit(status)); + status.errIfFailureAndReset("unitUsage() en-US road, getOutputUnit(...)"); + U_ASSERT(status == U_ZERO_ERROR); + assertEquals("unitUsage() en-US road", "1,050 ft", formattedNum.toString(status)); + status.errIfFailureAndReset("unitUsage() en-US road, toString(...)"); + U_ASSERT(status == U_ZERO_ERROR); assertFormatDescendingBig( uTestCase.getTerminatedBuffer(), u"measure-unit/length-meter usage/road", @@ -760,12 +771,11 @@ void NumberFormatterApiTest::unitUsage() { u"54 mi", u"5.4 mi", u"0.54 mi", - u"288 ft", - u"29 ft", + u"300 ft", + u"30 ft", u"0 ft"); unloc_formatter = NumberFormatter::with().usage("person").unit(MeasureUnit::getKilogram()); - uTestCase = u"unitUsage() en-GB person"; formatter = unloc_formatter.locale("en-GB"); formattedNum = formatter.formatDouble(80, status); @@ -838,6 +848,27 @@ void NumberFormatterApiTest::unitUsage() { u"0 pounds, 3.1 ounces", u"0 pounds, 0.31 ounces", u"0 pounds, 0 ounces"); + + assertFormatDescendingBig( + u"Scientific notation with Usage: possible when using a reasonable Precision", + u"scientific @### usage/default measure-unit/area-square-meter unit-width-full-name", + u"scientific @### usage/default unit/square-meter unit-width-full-name", + NumberFormatter::with() + .unit(SQUARE_METER) + .usage("default") + .notation(Notation::scientific()) + .precision(Precision::minMaxSignificantDigits(1, 4)) + .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), + Locale("en-ZA"), + u"8,765E1 square kilometres", + u"8,765E0 square kilometres", + u"8,765E1 hectares", + u"8,765E0 hectares", + u"8,765E3 square metres", + u"8,765E2 square metres", + u"8,765E1 square metres", + u"8,765E0 square metres", + u"0E0 square centimetres"); } void NumberFormatterApiTest::unitUsageErrorCodes() { @@ -861,41 +892,162 @@ void NumberFormatterApiTest::unitUsageErrorCodes() { status.assertSuccess(); } +// Tests for the "skeletons" field in unitPreferenceData, as well as precision +// and notation overrides. +void NumberFormatterApiTest::unitUsageSkeletons() { + IcuTestErrorCode status(*this, "unitUsageSkeletons()"); + + assertFormatSingle( + u"Default >300m road preference skeletons round to 50m", + u"usage/road measure-unit/length-meter", + u"usage/road unit/meter", + NumberFormatter::with().unit(METER).usage("road"), + Locale("en-ZA"), + 321, + u"300 m"); + + assertFormatSingle( + u"Precision can be overridden: override takes precedence", + u"usage/road measure-unit/length-meter @#", + u"usage/road unit/meter @#", + NumberFormatter::with() + .unit(METER) + .usage("road") + .precision(Precision::maxSignificantDigits(2)), + Locale("en-ZA"), + 321, + u"320 m"); + + assertFormatSingle( + u"Compact notation with Usage: bizarre, but possible (short)", + u"compact-short usage/road measure-unit/length-meter", + u"compact-short usage/road unit/meter", + NumberFormatter::with() + .unit(METER) + .usage("road") + .notation(Notation::compactShort()), + Locale("en-ZA"), + 987654321, + u"988K km"); + + assertFormatSingle( + u"Compact notation with Usage: bizarre, but possible (short, precision override)", + u"compact-short usage/road measure-unit/length-meter @#", + u"compact-short usage/road unit/meter @#", + NumberFormatter::with() + .unit(METER) + .usage("road") + .notation(Notation::compactShort()) + .precision(Precision::maxSignificantDigits(2)), + Locale("en-ZA"), + 987654321, + u"990K km"); + + assertFormatSingle( + u"Compact notation with Usage: unusual but possible (long)", + u"compact-long usage/road measure-unit/length-meter @#", + u"compact-long usage/road unit/meter @#", + NumberFormatter::with() + .unit(METER) + .usage("road") + .notation(Notation::compactLong()) + .precision(Precision::maxSignificantDigits(2)), + Locale("en-ZA"), + 987654321, + u"990 thousand km"); + + assertFormatSingle( + u"Compact notation with Usage: unusual but possible (long, precision override)", + u"compact-long usage/road measure-unit/length-meter @#", + u"compact-long usage/road unit/meter @#", + NumberFormatter::with() + .unit(METER) + .usage("road") + .notation(Notation::compactLong()) + .precision(Precision::maxSignificantDigits(2)), + Locale("en-ZA"), + 987654321, + u"990 thousand km"); + + assertFormatSingle( + u"Scientific notation, not recommended, requires precision override for road", + u"scientific usage/road measure-unit/length-meter", + u"scientific usage/road unit/meter", + NumberFormatter::with().unit(METER).usage("road").notation(Notation::scientific()), + Locale("en-ZA"), + 321.45, + // Rounding to the nearest "50" is not exponent-adjusted in scientific notation: + u"0E2 m"); + + assertFormatSingle( + u"Scientific notation with Usage: possible when using a reasonable Precision", + u"scientific usage/road measure-unit/length-meter @###", + u"scientific usage/road unit/meter @###", + NumberFormatter::with() + .unit(METER) + .usage("road") + .notation(Notation::scientific()) + .precision(Precision::maxSignificantDigits(4)), + Locale("en-ZA"), + 321.45, + u"3,215E2 m"); + + assertFormatSingle( + u"Scientific notation with Usage: possible when using a reasonable Precision", + u"scientific usage/default measure-unit/length-astronomical-unit unit-width-full-name", + u"scientific usage/default unit/astronomical-unit unit-width-full-name", + NumberFormatter::with() + .unit(MeasureUnit::forIdentifier("astronomical-unit", status)) + .usage("default") + .notation(Notation::scientific()) + .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME), + Locale("en-ZA"), + 1e20, + u"1,5E28 kilometres"); + + status.assertSuccess(); +} + void NumberFormatterApiTest::unitPipeline() { IcuTestErrorCode status(*this, "unitPipeline()"); + + assertFormatSingle( + u"Built-in unit, meter-per-second", + u"measure-unit/speed-meter-per-second", + u"~unit/meter-per-second", // TODO(icu-units#35): does not normalize as expected + NumberFormatter::with().unit(MeasureUnit::getMeterPerSecond()), + Locale("en-GB"), + 2.4, + u"2.4 m/s"); + + assertFormatSingle( + u"Built-in unit meter-per-second specified as .unit(built-in).perUnit(built-in)", + u"measure-unit/length-meter per-measure-unit/duration-second", + u"unit/meter-per-second", // TODO(icu-units#35): check whether desired behaviour? + NumberFormatter::with().unit(METER).perUnit(SECOND), + Locale("en-GB"), + 2.4, + "2.4 m/s"); + + // TODO(icu-units#59): THIS UNIT TEST DEMONSTRATES UNDESIREABLE BEHAVIOUR! + // When specifying built-in types, one can give both a unit and a perUnit. + // Resolving to a built-in unit does not always work. + // + // (Unit-testing philosophy: leave enabled to demonstrate current behaviour + // and changing behaviour in the future? Comment out to not assert this is + // "correct"?) + assertFormatSingle( + u"DEMONSTRATING BAD BEHAVIOUR, TODO(icu-units#59)", + u"measure-unit/speed-meter-per-second per-measure-unit/duration-second", + u"measure-unit/speed-meter-per-second per-measure-unit/duration-second", + NumberFormatter::with().unit(MeasureUnit::getMeterPerSecond()).perUnit(MeasureUnit::getSecond()), + Locale("en-GB"), + 2.4, + "2.4 m/s/s"); + LocalizedNumberFormatter nf; FormattedNumber num; - // Built-in unit, meter-per-second - nf = NumberFormatter::with().unit(MeasureUnit::getMeterPerSecond()).locale("en-GB"); - assertEquals("meter per second builtin", "2.4 m/s", nf.formatDouble(2.4, status).toString(status)); - status.assertSuccess(); - - // Built-in unit composed of built-in per built-in - nf = NumberFormatter::with().unit(METER).perUnit(SECOND).locale("en-GB"); - assertEquals("meter per second composed", "2.4 m/s", nf.formatDouble(2.4, status).toString(status)); - status.assertSuccess(); - - // "forIdentifier" tries to provide a built-in unit if there is one. - MeasureUnit builtIn = MeasureUnit::getMeterPerSecond(); - MeasureUnit fromIdent = MeasureUnit::forIdentifier("meter-per-second", status); - assertEquals("forIdentifier returns a normal built-in unit when it exists", builtIn.getOffset(), - fromIdent.getOffset()); - - // When specifying built-in types, one can give both a unit and a perUnit. - // Resolving to a built-in unit does not always work though. - nf = NumberFormatter::with() - .unit(MeasureUnit::getMeterPerSecond()) - .perUnit(MeasureUnit::getSecond()) - .locale("en-GB"); - status.assertSuccess(); - num = nf.formatDouble(2.4, status); - // TODO(icu-units#59): since this has succeeded, it needs to continue succeeding? - status.assertSuccess(); - // TODO(icu-units#59): this is undesireable behaviour: - assertEquals("meter per second per second", "2.4 m/s/s", - nf.formatDouble(2.4, status).toString(status)); - // If unit is not a built-in type, perUnit is not allowed nf = NumberFormatter::with() .unit(MeasureUnit::forIdentifier("furlong-pascal", status))