From e3123c83a4cdb73458e38f864bda5d5409308e8d Mon Sep 17 00:00:00 2001 From: Hugo van der Merwe <17109322+hugovdm@users.noreply.github.com> Date: Tue, 11 Aug 2020 17:54:10 +0200 Subject: [PATCH] ICU-20568 Support smart units / unit contexts / preferences Explore Usage-related error codes, address icu-units/icu#36. PR: https://github.com/icu-units/icu/pull/56 Commit: d5d7fdccfef887bb1af180bba3e2a0286dc32135 Implement Precision handling in UsagePrefsHandler::processQuantity PR: https://github.com/icu-units/icu/pull/61 Commit: 16547f32986600a46e4adf20a6870c1708dd1c75 Support Mixed Units in NumberFormatter when using usage() PR: https://github.com/icu-units/icu/pull/52 Commit: cc5a12202133855e15ffba889acffc10aad2d46b For MixedUnits, use the correct ListFormatter styles. PR: https://github.com/icu-units/icu/pull/66 Commit: 77bb747002d36626386f18e45c68c44b276cf575 Read the CLDR testData test files from the new location. PR: https://github.com/icu-units/icu/pull/68 Commit: 6eb992e2b3c0bbe4870b554a5aa855b3636566d4 Fix double-precision maths in unit conversions PR: https://github.com/icu-units/icu/pull/71 Commit: 78e88fbddef0f6817654d58c9c5dfeb6606324b9 Support .unit(MIXED_UNIT) without .usage(...). PR: https://github.com/icu-units/icu/pull/72 Commit: 56ac7959375b8c9363ff022185165e52490c6c00 More commits: - Reorder numbertest_api.cpp tests for consistent order. - NumberFormatterApiTest: fold unitPipeline() into unitCompoundMeasure() - Add some 'template class' instantiations for MSVC. - Make trimField handle all whitespace, improve test messages - Drop templated 'appendAll': it requires copy constructor - Add protected MaybeStackArray::copyFrom() - Add TODO(icu-units#67) and commented-out test case: use kUndefinedField for now - Provide correct output order for units like "inch-and-foot" - MSVC: export MaybeStackVector - Code review feedback: dependencies.txt and doc comments - Consistent naming for code files: units_* --- icu4c/source/common/cmemory.h | 14 + icu4c/source/i18n/formatted_string_builder.h | 1 - icu4c/source/i18n/i18n.vcxproj | 20 +- icu4c/source/i18n/i18n.vcxproj.filters | 28 +- icu4c/source/i18n/i18n_uwp.vcxproj | 16 +- icu4c/source/i18n/number_fluent.cpp | 117 ---- icu4c/source/i18n/number_formatimpl.cpp | 30 +- icu4c/source/i18n/number_formatimpl.h | 5 + icu4c/source/i18n/number_integerwidth.cpp | 3 + icu4c/source/i18n/number_longnames.cpp | 196 +++++- icu4c/source/i18n/number_longnames.h | 151 ++++- icu4c/source/i18n/number_mapper.cpp | 6 +- icu4c/source/i18n/number_microprops.h | 73 ++- icu4c/source/i18n/number_rounding.cpp | 56 +- icu4c/source/i18n/number_roundingutils.h | 9 + icu4c/source/i18n/number_skeletons.cpp | 71 +- icu4c/source/i18n/number_skeletons.h | 22 + icu4c/source/i18n/number_symbolswrapper.cpp | 125 ++++ icu4c/source/i18n/number_types.h | 2 +- icu4c/source/i18n/number_usageprefs.cpp | 149 ++++- icu4c/source/i18n/number_usageprefs.h | 74 ++- icu4c/source/i18n/sources.txt | 9 +- icu4c/source/i18n/unicode/numberformatter.h | 14 +- ...nverter.cpp => units_complexconverter.cpp} | 48 +- ...tsconverter.h => units_complexconverter.h} | 33 +- ...{unitconverter.cpp => units_converter.cpp} | 8 +- .../{unitconverter.h => units_converter.h} | 12 +- .../i18n/{unitsdata.cpp => units_data.cpp} | 7 +- .../source/i18n/{unitsdata.h => units_data.h} | 47 +- .../{unitsrouter.cpp => units_router.cpp} | 20 +- .../i18n/{unitsrouter.h => units_router.h} | 50 +- icu4c/source/test/depstest/dependencies.txt | 51 +- icu4c/source/test/intltest/Makefile.in | 2 +- icu4c/source/test/intltest/intltest.vcxproj | 6 +- .../test/intltest/intltest.vcxproj.filters | 6 +- icu4c/source/test/intltest/measfmttest.cpp | 43 +- icu4c/source/test/intltest/numbertest.h | 3 + icu4c/source/test/intltest/numbertest_api.cpp | 608 ++++++++++++++++-- ...{unitsdatatest.cpp => units_data_test.cpp} | 2 +- ...tsroutertest.cpp => units_router_test.cpp} | 2 +- .../{unitstest.cpp => units_test.cpp} | 143 +++- .../testdata/units/unitPreferencesTest.txt | 453 ------------- .../source/test/testdata/units/unitsTest.txt | 197 ------ 43 files changed, 1833 insertions(+), 1099 deletions(-) create mode 100644 icu4c/source/i18n/number_symbolswrapper.cpp rename icu4c/source/i18n/{complexunitsconverter.cpp => units_complexconverter.cpp} (73%) rename icu4c/source/i18n/{complexunitsconverter.h => units_complexconverter.h} (64%) rename icu4c/source/i18n/{unitconverter.cpp => units_converter.cpp} (98%) rename icu4c/source/i18n/{unitconverter.h => units_converter.h} (96%) rename icu4c/source/i18n/{unitsdata.cpp => units_data.cpp} (98%) rename icu4c/source/i18n/{unitsdata.h => units_data.h} (78%) rename icu4c/source/i18n/{unitsrouter.cpp => units_router.cpp} (87%) rename icu4c/source/i18n/{unitsrouter.h => units_router.h} (70%) rename icu4c/source/test/intltest/{unitsdatatest.cpp => units_data_test.cpp} (99%) rename icu4c/source/test/intltest/{unitsroutertest.cpp => units_router_test.cpp} (97%) rename icu4c/source/test/intltest/{unitstest.cpp => units_test.cpp} (79%) delete mode 100644 icu4c/source/test/testdata/units/unitPreferencesTest.txt delete mode 100644 icu4c/source/test/testdata/units/unitsTest.txt diff --git a/icu4c/source/common/cmemory.h b/icu4c/source/common/cmemory.h index 81728b96c5d..210bc7645e5 100644 --- a/icu4c/source/common/cmemory.h +++ b/icu4c/source/common/cmemory.h @@ -387,6 +387,20 @@ public: * caller becomes responsible for deleting the array */ inline T *orphanOrClone(int32_t length, int32_t &resultCapacity); + +protected: + // Resizes the array to the size of src, then copies the contents of src. + void copyFrom(const MaybeStackArray &src, UErrorCode &status) { + if (U_FAILURE(status)) { + return; + } + if (this->resize(src.capacity, 0) == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + return; + } + uprv_memcpy(this->ptr, src.ptr, (size_t)capacity * sizeof(T)); + } + private: T *ptr; int32_t capacity; diff --git a/icu4c/source/i18n/formatted_string_builder.h b/icu4c/source/i18n/formatted_string_builder.h index 4a886b97821..92bcf07d782 100644 --- a/icu4c/source/i18n/formatted_string_builder.h +++ b/icu4c/source/i18n/formatted_string_builder.h @@ -55,7 +55,6 @@ class U_I18N_API FormattedStringBuilder : public UMemory { // Convention: bottom 4 bits for field, top 4 bits for field category. // Field category 0 implies the number category so that the number field // literals can be directly passed as a Field type. - // See the helper functions in "StringBuilderFieldUtils" below. // Exported as U_I18N_API so it can be used by other exports on Windows. struct U_I18N_API Field { uint8_t bits; diff --git a/icu4c/source/i18n/i18n.vcxproj b/icu4c/source/i18n/i18n.vcxproj index cf5ea8c0b37..de363010e7a 100644 --- a/icu4c/source/i18n/i18n.vcxproj +++ b/icu4c/source/i18n/i18n.vcxproj @@ -202,6 +202,7 @@ + @@ -251,10 +252,10 @@ - - - - + + + + @@ -470,13 +471,14 @@ - + + @@ -490,10 +492,10 @@ - - - - + + + + diff --git a/icu4c/source/i18n/i18n.vcxproj.filters b/icu4c/source/i18n/i18n.vcxproj.filters index 1dc9b5c1d3c..a012465ddab 100644 --- a/icu4c/source/i18n/i18n.vcxproj.filters +++ b/icu4c/source/i18n/i18n.vcxproj.filters @@ -588,6 +588,9 @@ formatting + + formatting + formatting @@ -603,6 +606,9 @@ formatting + + formatting + formatting @@ -651,16 +657,16 @@ formatting - + formatting - + formatting - + formatting - + formatting @@ -914,6 +920,9 @@ formatting + + formatting + formatting @@ -929,6 +938,9 @@ formatting + + formatting + formatting @@ -1007,16 +1019,16 @@ formatting - + formatting - + formatting - + formatting - + formatting diff --git a/icu4c/source/i18n/i18n_uwp.vcxproj b/icu4c/source/i18n/i18n_uwp.vcxproj index 2742efe6cfd..be7f4daa5bd 100644 --- a/icu4c/source/i18n/i18n_uwp.vcxproj +++ b/icu4c/source/i18n/i18n_uwp.vcxproj @@ -483,10 +483,10 @@ - - - - + + + + @@ -723,10 +723,10 @@ - - - - + + + + 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 c047a1ff997..19bde1d8e1f 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) { } @@ -162,6 +159,8 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, || !(isPercent || isPermille) || isCompactNotation ); + bool isMixedUnit = isCldrUnit && (uprv_strcmp(macros.unit.getType(), "") == 0) && + macros.unit.getComplexity(status) == UMEASURE_UNIT_MIXED; // Select the numbering system. LocalPointer nsLocal; @@ -241,7 +240,8 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, // Unit Preferences and Conversions as our first step if (macros.usage.isSet()) { if (!isCldrUnit) { - // We only support "usage" when the input unit is a CLDR Unit. + // We only support "usage" when the input unit is specified, and is + // a CLDR Unit. status = U_ILLEGAL_ARGUMENT_ERROR; return nullptr; } @@ -249,6 +249,10 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, new UsagePrefsHandler(macros.locale, macros.unit, macros.usage.fUsage, chain, status); fUsagePrefsHandler.adoptInsteadAndCheckErrorCode(usagePrefsHandler, status); chain = fUsagePrefsHandler.getAlias(); + } else if (isMixedUnit) { + auto unitConversionHandler = new UnitConversionHandler(macros.unit, chain, status); + fUnitConversionHandler.adoptInsteadAndCheckErrorCode(unitConversionHandler, status); + chain = fUnitConversionHandler.getAlias(); } // Multiplier @@ -265,16 +269,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; @@ -375,6 +377,14 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, resolvePluralRules(macros.rules, macros.locale, status), chain, status), status); chain = fLongNameMultiplexer.getAlias(); + } else if (isMixedUnit) { + fMixedUnitLongNameHandler.adoptInsteadAndCheckErrorCode(new MixedUnitLongNameHandler(), + status); + MixedUnitLongNameHandler::forMeasureUnit( + macros.locale, macros.unit, unitWidth, + resolvePluralRules(macros.rules, macros.locale, status), chain, + fMixedUnitLongNameHandler.getAlias(), status); + chain = fMixedUnitLongNameHandler.getAlias(); } else { fLongNameHandler.adoptInsteadAndCheckErrorCode(new LongNameHandler(), status); LongNameHandler::forMeasureUnit(macros.locale, macros.unit, macros.perUnit, unitWidth, diff --git a/icu4c/source/i18n/number_formatimpl.h b/icu4c/source/i18n/number_formatimpl.h index 062191a0327..5cd549e54a3 100644 --- a/icu4c/source/i18n/number_formatimpl.h +++ b/icu4c/source/i18n/number_formatimpl.h @@ -94,6 +94,7 @@ class NumberFormatterImpl : public UMemory { // Other fields possibly used by the number formatting pipeline: // TODO: Convert more of these LocalPointers to value objects to reduce the number of news? LocalPointer fUsagePrefsHandler; + LocalPointer fUnitConversionHandler; LocalPointer fSymbols; LocalPointer fRules; LocalPointer fPatternInfo; @@ -101,6 +102,10 @@ class NumberFormatterImpl : public UMemory { LocalPointer fPatternModifier; LocalPointer fImmutablePatternModifier; LocalPointer fLongNameHandler; + // TODO: use a common base class that enables fLongNameHandler, + // fLongNameMultiplexer, and fMixedUnitLongNameHandler to be merged into one + // member? + LocalPointer fMixedUnitLongNameHandler; LocalPointer fLongNameMultiplexer; LocalPointer fCompactHandler; 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 7ace7fdc5ed..691fb137f92 100644 --- a/icu4c/source/i18n/number_longnames.cpp +++ b/icu4c/source/i18n/number_longnames.cpp @@ -38,6 +38,7 @@ constexpr int32_t DNAM_INDEX = StandardPlural::Form::COUNT; * `per` forms. */ constexpr int32_t PER_INDEX = StandardPlural::Form::COUNT + 1; +// Number of keys in the array populated by PluralTableSink. constexpr int32_t ARRAY_LENGTH = StandardPlural::Form::COUNT + 2; static int32_t getIndex(const char* pluralKeyword, UErrorCode& status) { @@ -52,6 +53,11 @@ static int32_t getIndex(const char* pluralKeyword, UErrorCode& status) { } } +// Selects a string out of the `strings` array which corresponds to the +// specified plural form, with fallback to the OTHER form. +// +// The `strings` array must have ARRAY_LENGTH items: one corresponding to each +// of the plural forms, plus a display name ("dnam") and a "per" form. static UnicodeString getWithPlural( const UnicodeString* strings, StandardPlural::Form plural, @@ -101,12 +107,18 @@ class PluralTableSink : public ResourceSink { // NOTE: outArray MUST have room for all StandardPlural values. No bounds checking is performed. -// Populates outArray with `locale`-specific values for `unit` through use of -// PluralTableSink, reading from resources *unitsNarrow* and *unitsShort* (for -// width UNUM_UNIT_WIDTH_NARROW), or just *unitsShort* (for width -// UNUM_UNIT_WIDTH_SHORT). For other widths, it would read just "units". -// -// outArray must be of fixed length ARRAY_LENGTH. +/** + * Populates outArray with `locale`-specific values for `unit` through use of + * PluralTableSink. Only the set of basic units are supported! + * + * Reading from resources *unitsNarrow* and *unitsShort* (for width + * UNUM_UNIT_WIDTH_NARROW), or just *unitsShort* (for width + * UNUM_UNIT_WIDTH_SHORT). For other widths, it reads just "units". + * + * @param unit must have a type and subtype (i.e. it must be a unit listed in + * gTypes and gSubTypes in measunit.cpp). + * @param outArray must be of fixed length ARRAY_LENGTH. + */ void getMeasureData(const Locale &locale, const MeasureUnit &unit, const UNumberUnitWidth &width, UnicodeString *outArray, UErrorCode &status) { PluralTableSink sink(outArray); @@ -204,24 +216,26 @@ UnicodeString getPerUnitFormat(const Locale& locale, const UNumberUnitWidth &wid } // namespace -// TODO(units,hugovdm): deal properly with "perUnit" parameter here: void LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef, const MeasureUnit &perUnit, const UNumberUnitWidth &width, const PluralRules *rules, const MicroPropsGenerator *parent, LongNameHandler *fillIn, UErrorCode &status) { - if (fillIn == nullptr) { - status = U_INTERNAL_PROGRAM_ERROR; - return; - } + // Not valid for mixed units that aren't built-in units, and there should + // not be any built-in mixed units! + U_ASSERT(uprv_strlen(unitRef.getType()) > 0 || unitRef.getComplexity(status) != UMEASURE_UNIT_MIXED); + U_ASSERT(fillIn != nullptr); if (uprv_strlen(unitRef.getType()) == 0 || uprv_strlen(perUnit.getType()) == 0) { - // TODO(ICU-20941): Unsanctioned unit. Not yet fully supported. Set an error code. + // TODO(ICU-20941): Unsanctioned unit. Not yet fully supported. Set an + // error code. Once we support not-built-in units here, unitRef may be + // anything, but if not built-in, perUnit has to be "none". status = U_UNSUPPORTED_ERROR; return; } MeasureUnit unit = unitRef; if (uprv_strcmp(perUnit.getType(), "none") != 0) { - // Compound unit: first try to simplify (e.g., meters per second is its own unit). + // Compound unit: first try to simplify (e.g. "meter per second" is a + // built-in unit). bool isResolved = false; MeasureUnit resolved = MeasureUnit::resolveUnitPerUnit(unit, perUnit, &isResolved); if (isResolved) { @@ -244,7 +258,6 @@ void LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitR status); } -// TODO(units,hugovdm): deal properly with "perUnit" parameter here: void LongNameHandler::forCompoundUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit, const UNumberUnitWidth &width, const PluralRules *rules, const MicroPropsGenerator *parent, @@ -390,6 +403,131 @@ const Modifier* LongNameHandler::getModifier(Signum /*signum*/, StandardPlural:: return &fModifiers[plural]; } +void MixedUnitLongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &mixedUnit, + const UNumberUnitWidth &width, const PluralRules *rules, + const MicroPropsGenerator *parent, + MixedUnitLongNameHandler *fillIn, UErrorCode &status) { + U_ASSERT(mixedUnit.getComplexity(status) == UMEASURE_UNIT_MIXED); + U_ASSERT(fillIn != nullptr); + + LocalArray individualUnits = + mixedUnit.splitToSingleUnits(fillIn->fMixedUnitCount, status); + fillIn->fMixedUnitData.adoptInstead(new UnicodeString[fillIn->fMixedUnitCount * ARRAY_LENGTH]); + for (int32_t i = 0; i < fillIn->fMixedUnitCount; i++) { + // Grab data for each of the components. + UnicodeString *unitData = &fillIn->fMixedUnitData[i * ARRAY_LENGTH]; + getMeasureData(loc, individualUnits[i], width, unitData, status); + } + + UListFormatterWidth listWidth = ULISTFMT_WIDTH_SHORT; + if (width == UNUM_UNIT_WIDTH_NARROW) { + listWidth = ULISTFMT_WIDTH_NARROW; + } else if (width == UNUM_UNIT_WIDTH_FULL_NAME) { + // This might be the same as SHORT in most languages: + listWidth = ULISTFMT_WIDTH_WIDE; + } + fillIn->fListFormatter.adoptInsteadAndCheckErrorCode( + ListFormatter::createInstance(loc, ULISTFMT_TYPE_UNITS, listWidth, status), status); + fillIn->rules = rules; + fillIn->parent = parent; + + // We need a localised NumberFormatter for the integers of the bigger units + // (providing Arabic numerals, for example). + fillIn->fIntegerFormatter = NumberFormatter::withLocale(loc); +} + +void MixedUnitLongNameHandler::processQuantity(DecimalQuantity &quantity, MicroProps µs, + UErrorCode &status) const { + U_ASSERT(fMixedUnitCount > 1); + if (parent != nullptr) { + parent->processQuantity(quantity, micros, status); + } + micros.modOuter = getMixedUnitModifier(quantity, micros, status); +} + +const Modifier *MixedUnitLongNameHandler::getMixedUnitModifier(DecimalQuantity &quantity, + MicroProps µs, + UErrorCode &status) const { + // TODO(icu-units#21): mixed units without usage() is not yet supported. + // That should be the only reason why this happens, so delete this whole if + // once fixed: + if (micros.mixedMeasuresCount == 0) { + status = U_UNSUPPORTED_ERROR; + return µs.helpers.emptyWeakModifier; + } + U_ASSERT(micros.mixedMeasuresCount > 0); + // mixedMeasures does not contain the last value: + U_ASSERT(fMixedUnitCount == micros.mixedMeasuresCount + 1); + U_ASSERT(fListFormatter.isValid()); + + // Algorithm: + // + // For the mixed-units measurement of: "3 yard, 1 foot, 2.6 inch", we should + // find "3 yard" and "1 foot" in micros.mixedMeasures. + // + // Obtain long-names with plural forms corresponding to measure values: + // * {0} yards, {0} foot, {0} inches + // + // Format the integer values appropriately and modify with the format + // strings: + // - 3 yards, 1 foot + // + // Use ListFormatter to combine, with one placeholder: + // - 3 yards, 1 foot and {0} inches + // + // Return a SimpleModifier for this pattern, letting the rest of the + // pipeline take care of the remaining inches. + + LocalArray outputMeasuresList(new UnicodeString[fMixedUnitCount], status); + if (U_FAILURE(status)) { + return µs.helpers.emptyWeakModifier; + } + + for (int32_t i = 0; i < micros.mixedMeasuresCount; i++) { + DecimalQuantity fdec; + fdec.setToLong(micros.mixedMeasures[i]); + StandardPlural::Form pluralForm = utils::getStandardPlural(rules, fdec); + + UnicodeString simpleFormat = + getWithPlural(&fMixedUnitData[i * ARRAY_LENGTH], pluralForm, status); + SimpleFormatter compiledFormatter(simpleFormat, 0, 1, status); + + UnicodeString num; + auto appendable = UnicodeStringAppendable(num); + fIntegerFormatter.formatDecimalQuantity(fdec, status).appendTo(appendable, status); + compiledFormatter.format(num, outputMeasuresList[i], status); + } + + UnicodeString *finalSimpleFormats = &fMixedUnitData[(fMixedUnitCount - 1) * ARRAY_LENGTH]; + StandardPlural::Form finalPlural = utils::getPluralSafe(micros.rounder, rules, quantity, status); + UnicodeString finalSimpleFormat = getWithPlural(finalSimpleFormats, finalPlural, status); + SimpleFormatter finalFormatter(finalSimpleFormat, 0, 1, status); + finalFormatter.format(UnicodeString(u"{0}"), outputMeasuresList[fMixedUnitCount - 1], status); + + // Combine list into a "premixed" pattern + UnicodeString premixedFormatPattern; + fListFormatter->format(outputMeasuresList.getAlias(), fMixedUnitCount, premixedFormatPattern, + status); + SimpleFormatter premixedCompiled(premixedFormatPattern, 0, 1, status); + if (U_FAILURE(status)) { + return µs.helpers.emptyWeakModifier; + } + + // Return a SimpleModifier for the "premixed" pattern + micros.helpers.mixedUnitModifier = + SimpleModifier(premixedCompiled, kUndefinedField, false, {this, SIGNUM_POS_ZERO, finalPlural}); + return µs.helpers.mixedUnitModifier; +} + +const Modifier *MixedUnitLongNameHandler::getModifier(Signum /*signum*/, + StandardPlural::Form /*plural*/) const { + // TODO(units): investigate this method when investigating where + // LongNameHandler::getModifier() gets used. To be sure it remains + // unreachable: + UPRV_UNREACHABLE; + return nullptr; +} + LongNameMultiplexer * LongNameMultiplexer::forMeasureUnits(const Locale &loc, const MaybeStackVector &units, const UNumberUnitWidth &width, const PluralRules *rules, @@ -399,16 +537,23 @@ LongNameMultiplexer::forMeasureUnits(const Locale &loc, const MaybeStackVector 0); + if (result->fHandlers.resize(units.length()) == nullptr) { + status = U_MEMORY_ALLOCATION_ERROR; + return nullptr; + } result->fMeasureUnits.adoptInstead(new MeasureUnit[units.length()]); for (int32_t i = 0, length = units.length(); i < length; i++) { - // Create empty new LongNameHandler: - LongNameHandler *lnh = - result->fLongNameHandlers.emplaceBackAndCheckErrorCode(status); - result->fMeasureUnits[i] = *units[i]; - // Fill in LongNameHandler: - LongNameHandler::forMeasureUnit(loc, *units[i], - MeasureUnit(), // TODO(units): deal with COMPOUND and MIXED units - width, rules, NULL, lnh, status); + const MeasureUnit& unit = *units[i]; + result->fMeasureUnits[i] = unit; + if (unit.getComplexity(status) == UMEASURE_UNIT_MIXED) { + MixedUnitLongNameHandler *mlnh = result->fMixedUnitHandlers.createAndCheckErrorCode(status); + MixedUnitLongNameHandler::forMeasureUnit(loc, unit, width, rules, NULL, mlnh, status); + result->fHandlers[i] = mlnh; + } else { + LongNameHandler *lnh = result->fLongNameHandlers.createAndCheckErrorCode(status); + LongNameHandler::forMeasureUnit(loc, unit, MeasureUnit(), width, rules, NULL, lnh, status); + result->fHandlers[i] = lnh; + } if (U_FAILURE(status)) { return nullptr; } @@ -424,12 +569,15 @@ void LongNameMultiplexer::processQuantity(DecimalQuantity &quantity, MicroProps fParent->processQuantity(quantity, micros, status); // Call the correct LongNameHandler based on outputUnit - for (int i = 0; i < fLongNameHandlers.length(); i++) { + for (int i = 0; i < fHandlers.getCapacity(); i++) { if (fMeasureUnits[i] == micros.outputUnit) { - fLongNameHandlers[i]->processQuantity(quantity, micros, status); + fHandlers[i]->processQuantity(quantity, micros, status); 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 db08c119cd0..ace517c1f31 100644 --- a/icu4c/source/i18n/number_longnames.h +++ b/icu4c/source/i18n/number_longnames.h @@ -8,6 +8,7 @@ #define __NUMBER_LONGNAMES_H__ #include "cmemory.h" +#include "unicode/listformatter.h" #include "unicode/uversion.h" #include "number_utils.h" #include "number_modifiers.h" @@ -34,17 +35,47 @@ class LongNameHandler : public MicroPropsGenerator, public ModifierStore, public forCurrencyLongNames(const Locale &loc, const CurrencyUnit ¤cy, const PluralRules *rules, const MicroPropsGenerator *parent, UErrorCode &status); + /** + * Construct a localized LongNameHandler for the specified MeasureUnit. + * + * Compound units can be constructed via `unit` and `perUnit`. Both of these + * must then be built-in units. + * + * Mixed units are not supported, use MixedUnitLongNameHandler::forMeasureUnit. + * + * This function uses a fillIn intead of returning a pointer, because we + * want to fill in instances in a MemoryPool (which cannot adopt pointers it + * didn't create itself). + * + * @param loc The desired locale. + * @param unit The measure unit to construct a LongNameHandler for. If + * `perUnit` is also defined, `unit` must not be a mixed unit. + * @param perUnit If `unit` is a mixed unit, `perUnit` must be "none". + * @param width Specifies the desired unit rendering. + * @param rules Does not take ownership. + * @param parent Does not take ownership. + * @param fillIn Required. + */ static void forMeasureUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit, const UNumberUnitWidth &width, const PluralRules *rules, const MicroPropsGenerator *parent, LongNameHandler *fillIn, UErrorCode &status); + /** + * Selects the plural-appropriate Modifier from the set of fModifiers based + * on the plural form. + */ void processQuantity(DecimalQuantity &quantity, MicroProps µs, UErrorCode &status) const U_OVERRIDE; + // TODO(units): investigate whether we might run into Mixed Unit trouble + // with this. This override for ModifierStore::getModifier does not support + // mixed units: investigate under which circumstances it gets called (check + // both ImmutablePatternModifier and in NumberRangeFormatterImpl). const Modifier* getModifier(Signum signum, StandardPlural::Form plural) const U_OVERRIDE; private: + // A set of pre-computed modifiers, one for each plural form. SimpleModifier fModifiers[StandardPlural::Form::COUNT]; // Not owned const PluralRules *rules; @@ -58,34 +89,136 @@ class LongNameHandler : public MicroPropsGenerator, public ModifierStore, public LongNameHandler() : rules(nullptr), parent(nullptr) { } - friend class MemoryPool; // To enable emplaceBack(); + // Enables MemoryPool::emplaceBack(): requires access to + // the private constructors. + friend class MemoryPool; + + // Allow macrosToMicroGenerator to call the private default constructor. friend class NumberFormatterImpl; + // Fills in LongNameHandler fields for formatting compound units identified + // via `unit` and `perUnit`. Both `unit` and `perUnit` need to be built-in + // units (for which data exists). static void forCompoundUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit, const UNumberUnitWidth &width, const PluralRules *rules, const MicroPropsGenerator *parent, LongNameHandler *fillIn, UErrorCode &status); + // Sets fModifiers to use the patterns from `simpleFormats`. void simpleFormatsToModifiers(const UnicodeString *simpleFormats, Field field, UErrorCode &status); + + // Sets fModifiers to a combination of `leadFormats` (one per plural form) + // and `trailFormat` appended to each. + // + // With a leadFormat of "{0}m" and a trailFormat of "{0}/s", it produces a + // pattern of "{0}m/s" by inserting the leadFormat pattern into trailFormat. void multiSimpleFormatsToModifiers(const UnicodeString *leadFormats, UnicodeString trailFormat, Field field, UErrorCode &status); }; -const int MAX_PREFS_COUNT = 10; +// Similar to LongNameHandler, but only for MIXED units. +class MixedUnitLongNameHandler : public MicroPropsGenerator, public ModifierStore, public UMemory { + public: + /** + * Construct a localized MixedUnitLongNameHandler for the specified + * MeasureUnit. It must be a MIXED unit. + * + * This function uses a fillIn intead of returning a pointer, because we + * want to fill in instances in a MemoryPool (which cannot adopt pointers it + * didn't create itself). + * + * @param loc The desired locale. + * @param mixedUnit The mixed measure unit to construct a + * MixedUnitLongNameHandler for. + * @param width Specifies the desired unit rendering. + * @param rules Does not take ownership. + * @param parent Does not take ownership. + * @param fillIn Required. + */ + static void forMeasureUnit(const Locale &loc, const MeasureUnit &mixedUnit, + const UNumberUnitWidth &width, const PluralRules *rules, + const MicroPropsGenerator *parent, MixedUnitLongNameHandler *fillIn, + UErrorCode &status); + + /** + * Produces a plural-appropriate Modifier for a mixed unit: `quantity` is + * taken as the final smallest unit, while the larger unit values must be + * provided via `micros.mixedMeasures`. + */ + void processQuantity(DecimalQuantity &quantity, MicroProps µs, + UErrorCode &status) const U_OVERRIDE; + + // Required for ModifierStore. And ModifierStore is required by + // SimpleModifier constructor's last parameter. We assert his will never get + // called though. + const Modifier *getModifier(Signum signum, StandardPlural::Form plural) const U_OVERRIDE; + + private: + // Not owned + const PluralRules *rules; + // Not owned + const MicroPropsGenerator *parent; + + // Total number of units in the MeasureUnit this LongNameHandler was + // configured for: for "foot-and-inch", this will be 2. (If not a mixed unit, + // this will be 1.) + int32_t fMixedUnitCount = 1; + // If this LongNameHandler is for a mixed unit, this stores unit data for + // each of the individual units. For each unit, it stores ARRAY_LENGTH + // strings, as returned by getMeasureData. (Each unit with index `i` has + // ARRAY_LENGTH strings starting at index `i*ARRAY_LENGTH` in this array.) + LocalArray fMixedUnitData; + // A localized NumberFormatter used to format the integer-valued bigger + // units of Mixed Unit measurements. + LocalizedNumberFormatter fIntegerFormatter; + // A localised list formatter for joining mixed units together. + LocalPointer fListFormatter; + + MixedUnitLongNameHandler(const PluralRules *rules, const MicroPropsGenerator *parent) + : rules(rules), parent(parent) { + } + + MixedUnitLongNameHandler() : rules(nullptr), parent(nullptr) { + } + + // Allow macrosToMicroGenerator to call the private default constructor. + friend class NumberFormatterImpl; + + // Enables MemoryPool::emplaceBack(): requires access to + // the private constructors. + friend class MemoryPool; + + // Fills in LongNameHandler fields for formatting mixed units. Each unit in + // a mixed unit must be a built-in unit. + static void forMixedUnit(const Locale &loc, const MeasureUnit &unit, const UNumberUnitWidth &width, + const PluralRules *rules, const MicroPropsGenerator *parent, + MixedUnitLongNameHandler *fillIn, UErrorCode &status); + + // For a mixed unit, returns a Modifier that takes only one parameter: the + // smallest and final unit of the set. The bigger units' values and labels + // get baked into this Modifier, together with the unit label of the final + // unit. + const Modifier *getMixedUnitModifier(DecimalQuantity &quantity, MicroProps µs, + UErrorCode &status) const; +}; /** * A MicroPropsGenerator that multiplexes between different LongNameHandlers, - * depending on the outputUnit (micros.helpers.outputUnit should be set earlier - * in the chain). + * depending on the outputUnit. + * + * See processQuantity() for the input requirements. */ class LongNameMultiplexer : public MicroPropsGenerator, public UMemory { public: - // FIXME: docstring? + // Produces a multiplexer for LongNameHandlers, one for each unit in + // `units`. An individual unit might be a mixed unit. static LongNameMultiplexer *forMeasureUnits(const Locale &loc, const MaybeStackVector &units, const UNumberUnitWidth &width, const PluralRules *rules, const MicroPropsGenerator *parent, UErrorCode &status); + // The output unit must be provided via `micros.outputUnit`, it must match + // one of the units provided to the factory function. void processQuantity(DecimalQuantity &quantity, MicroProps µs, UErrorCode &status) const U_OVERRIDE; @@ -95,8 +228,14 @@ class LongNameMultiplexer : public MicroPropsGenerator, public UMemory { * earlier MicroPropsGenerators in the chain, LongNameMultiplexer keeps the * parent link, while the LongNameHandlers are given no parents. */ - MaybeStackVector fLongNameHandlers; + MemoryPool fLongNameHandlers; + MemoryPool fMixedUnitHandlers; + // Unowned pointers to instances owned by MaybeStackVectors. + MaybeStackArray fHandlers; + // Each MeasureUnit corresponds to the same-index MicroPropsGenerator + // pointed to in fHandlers. LocalArray fMeasureUnits; + const MicroPropsGenerator *fParent; LongNameMultiplexer(const MicroPropsGenerator *parent) : fParent(parent) { 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_microprops.h b/icu4c/source/i18n/number_microprops.h index cb68c41a744..7258cc2e640 100644 --- a/icu4c/source/i18n/number_microprops.h +++ b/icu4c/source/i18n/number_microprops.h @@ -22,6 +22,51 @@ U_NAMESPACE_BEGIN namespace number { namespace impl { +/** + * A copyable container for the integer values of mixed unit measurements. + * + * If memory allocation fails during copying, no values are copied and status is + * set to U_MEMORY_ALLOCATION_ERROR. + */ +class IntMeasures : public MaybeStackArray { + public: + /** + * Default constructor initializes with internal T[stackCapacity] buffer. + * + * Stack Capacity: most mixed units are expected to consist of two or three + * subunits, so one or two integer measures should be enough. + */ + IntMeasures() : MaybeStackArray() { + } + + /** + * Copy constructor. + * + * If memory allocation fails during copying, no values are copied and + * status is set to U_MEMORY_ALLOCATION_ERROR. + */ + IntMeasures(const IntMeasures &other) : MaybeStackArray() { + this->operator=(other); + } + + // Assignment operator + IntMeasures &operator=(const IntMeasures &rhs) { + if (this == &rhs) { + return *this; + } + copyFrom(rhs, status); + return *this; + } + + /** Move constructor */ + IntMeasures(IntMeasures &&src) = default; + + /** Move assignment */ + IntMeasures &operator=(IntMeasures &&src) = default; + + UErrorCode status = U_ZERO_ERROR; +}; + // TODO(units): generated by MicroPropsGenerator, but inherits from it too. Do // we want to better document why? There's an explanation for processQuantity: // * As MicroProps is the "base instance", this implementation of @@ -41,22 +86,48 @@ struct MicroProps : public MicroPropsGenerator { // 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): + + // A Modifier provided by LongNameHandler, used for currency long names and + // units. If there is no LongNameHandler needed, this should be an + // EmptyModifier. (This is typically the third modifier applied.) const Modifier* modOuter; + // A Modifier for short currencies and compact notation. (This is typically + // the second modifier applied.) const Modifier* modMiddle = nullptr; + // A Modifier provided by ScientificHandler, used for scientific notation. + // This is typically the first modifier applied. const Modifier* modInner; // The following "helper" fields may optionally be used during the MicroPropsGenerator. // They live here to retain memory. struct { + // The ScientificModifier for which ScientificHandler is responsible. + // ScientificHandler::processQuantity() modifies this Modifier. ScientificModifier scientificModifier; + // EmptyModifier used for modOuter EmptyModifier emptyWeakModifier{false}; + // EmptyModifier used for modInner EmptyModifier emptyStrongModifier{true}; MultiplierFormatHandler multiplier; + // A Modifier used for Mixed Units. When formatting mixed units, + // LongNameHandler assigns this Modifier. + SimpleModifier mixedUnitModifier; } helpers; - // The MeasureUnit with which the output measurement is represented. + // The MeasureUnit with which the output is represented. May also have + // UMEASURE_UNIT_MIXED complexity, in which case mixedMeasures comes into + // play. MeasureUnit outputUnit; + // In the case of mixed units, this is the set of integer-only units + // *preceding* the final unit. + IntMeasures mixedMeasures; + // Number of mixedMeasures that have been populated + int32_t mixedMeasuresCount = 0; + MicroProps() = default; MicroProps(const MicroProps& other) = default; 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..c6e5c77d8c8 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,12 @@ class RoundingImpl { Precision fPrecision; UNumberFormatRoundingMode fRoundingMode; bool fPassThrough = true; // default value + + // Permits access to fPrecision. + friend class UsagePrefsHandler; + + // Permits access to fPrecision. + friend class UnitConversionHandler; }; diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp index 7c2a75902e6..7bfcb692b53 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 @@ -480,6 +465,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); @@ -586,6 +572,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'.': @@ -764,6 +752,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: ///// @@ -992,6 +981,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) @@ -1007,7 +997,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; @@ -1069,7 +1058,15 @@ void blueprint_helpers::parseIdentifierUnitOption(const StringSegment& segment, return; } - // TODO(ICU-20941): Clean this up. + // Mixed units can only be represented by a full MeasureUnit instances, so + // we ignore macros.perUnit. + if (fullUnit.complexity == UMEASURE_UNIT_MIXED) { + macros.unit = std::move(fullUnit).build(status); + return; + } + + // TODO(ICU-20941): Clean this up (see also + // https://github.com/icu-units/icu/issues/35). for (int32_t i = 0; i < fullUnit.units.length(); i++) { SingleUnitImpl* subUnit = fullUnit.units[i]; if (subUnit->dimensionality > 0) { @@ -1336,36 +1333,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&) { @@ -1551,10 +1520,14 @@ bool GeneratorHelpers::unit(const MacroProps& macros, UnicodeString& sb, UErrorC } else if (utils::unitIsPermille(macros.unit)) { sb.append(u"permille", -1); return true; - } else { + } else if (uprv_strcmp(macros.unit.getType(), "") != 0) { sb.append(u"measure-unit/", -1); blueprint_helpers::generateMeasureUnitOption(macros.unit, sb, status); return true; + } else { + // TODO(icu-units#35): add support for not-built-in units. + status = U_UNSUPPORTED_ERROR; + return false; } } @@ -1573,7 +1546,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 313c7ac54c2..38dfa19d7cb 100644 --- a/icu4c/source/i18n/number_skeletons.h +++ b/icu4c/source/i18n/number_skeletons.h @@ -246,6 +246,10 @@ void generateMeasureUnitOption(const MeasureUnit& measureUnit, UnicodeString& sb void parseMeasurePerUnitOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); +/** + * Parses unit identifiers like "meter-per-second" and "foot-and-inch", as + * specified via a "unit/" concise skeleton. + */ void parseIdentifierUnitOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); void parseUnitUsageOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); @@ -355,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 8a2669ada2d..cd426f47676 100644 --- a/icu4c/source/i18n/number_usageprefs.cpp +++ b/icu4c/source/i18n/number_usageprefs.cpp @@ -10,6 +10,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" @@ -18,9 +19,13 @@ #include "unicode/platform.h" #include "unicode/unum.h" #include "unicode/urename.h" +#include "units_data.h" +using namespace icu; using namespace icu::number; using namespace icu::number::impl; +using icu::StringSegment; +using icu::units::ConversionRates; // Copy constructor Usage::Usage(const Usage &other) : fUsage(nullptr), fLength(other.fLength), fError(other.fError) { @@ -41,10 +46,7 @@ Usage &Usage::operator=(const Usage &other) { return *this; } -// Move constructor - can it be improved by taking over src's "this" instead of -// copying contents? Swapping pointers makes sense for heap objects but not for -// stack objects. -// *this = std::move(src); +// Move constructor Usage::Usage(Usage &&src) U_NOEXCEPT : fUsage(src.fUsage), fLength(src.fLength), fError(src.fError) { // Take ownership away from src if necessary src.fUsage = nullptr; @@ -84,8 +86,43 @@ void Usage::set(StringPiece value) { fUsage[fLength] = 0; } +void mixedMeasuresToMicros(const MaybeStackVector &measures, DecimalQuantity *quantity, + MicroProps *micros, UErrorCode status) { + micros->mixedMeasuresCount = measures.length() - 1; + if (micros->mixedMeasuresCount > 0) { +#ifdef U_DEBUG + U_ASSERT(micros->outputUnit.getComplexity(status) == UMEASURE_UNIT_MIXED); + U_ASSERT(U_SUCCESS(status)); + // Check that we received measurements with the expected MeasureUnits: + int32_t singleUnitsCount; + LocalArray singleUnits = + micros->outputUnit.splitToSingleUnits(singleUnitsCount, status); + U_ASSERT(U_SUCCESS(status)); + U_ASSERT(measures.length() == singleUnitsCount); + for (int32_t i = 0; i < measures.length(); i++) { + U_ASSERT(measures[i]->getUnit() == singleUnits[i]); + } +#endif + // Mixed units: except for the last value, we pass all values to the + // LongNameHandler via micros->mixedMeasures. + if (micros->mixedMeasures.getCapacity() < micros->mixedMeasuresCount) { + if (micros->mixedMeasures.resize(micros->mixedMeasuresCount) == nullptr) { + status = U_MEMORY_ALLOCATION_ERROR; + return; + } + } + for (int32_t i = 0; i < micros->mixedMeasuresCount; i++) { + micros->mixedMeasures[i] = measures[i]->getNumber().getInt64(); + } + } else { + micros->mixedMeasuresCount = 0; + } + // The last value (potentially the only value) gets passed on via quantity. + quantity->setToDouble(measures[measures.length() - 1]->getNumber().getDouble()); +} + UsagePrefsHandler::UsagePrefsHandler(const Locale &locale, - const MeasureUnit inputUnit, + const MeasureUnit &inputUnit, const StringPiece usage, const MicroPropsGenerator *parent, UErrorCode &status) @@ -102,25 +139,95 @@ void UsagePrefsHandler::processQuantity(DecimalQuantity &quantity, MicroProps &m quantity.roundToInfinity(); // Enables toDouble const auto routed = fUnitsRouter.route(quantity.toDouble(), status); - const auto& routedUnits = routed.measures; - micros.outputUnit = routedUnits[0]->getUnit(); - quantity.setToDouble(routedUnits[0]->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; } + const MaybeStackVector& routedUnits = routed.measures; + micros.outputUnit = routed.outputUnit.copy(status).build(status); + if (U_FAILURE(status)) { + return; + } + + mixedMeasuresToMicros(routedUnits, &quantity, µs, status); + + 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; +} + +UnitConversionHandler::UnitConversionHandler(const MeasureUnit &unit, const MicroPropsGenerator *parent, + UErrorCode &status) + : fOutputUnit(unit), fParent(parent) { + MeasureUnitImpl temp; + const MeasureUnitImpl &outputUnit = MeasureUnitImpl::forMeasureUnit(unit, temp, status); + const MeasureUnitImpl *inputUnit = &outputUnit; + MaybeStackVector singleUnits; + U_ASSERT(outputUnit.complexity == UMEASURE_UNIT_MIXED); + // When we wish to support unit conversion, replace the above assert with this if: + // if (outputUnit.complexity == UMEASURE_UNIT_MIXED) { + { + singleUnits = outputUnit.extractIndividualUnits(status); + U_ASSERT(singleUnits.length() > 0); + inputUnit = singleUnits[0]; + } + // TODO: this should become an initOnce thing? Review with other + // ConversionRates usages. + ConversionRates conversionRates(status); + if (U_FAILURE(status)) { + return; + } + fUnitConverter.adoptInsteadAndCheckErrorCode( + new ComplexUnitsConverter(*inputUnit, outputUnit, conversionRates, status), status); +} + +void UnitConversionHandler::processQuantity(DecimalQuantity &quantity, MicroProps µs, + UErrorCode &status) const { + fParent->processQuantity(quantity, micros, status); + if (U_FAILURE(status)) { + return; + } + quantity.roundToInfinity(); // Enables toDouble + MaybeStackVector measures = fUnitConverter->convert(quantity.toDouble(), status); + micros.outputUnit = fOutputUnit; + if (U_FAILURE(status)) { + return; + } + + mixedMeasuresToMicros(measures, &quantity, µs, status); + + // TODO: add tests to explore behaviour that may suggest a more + // human-centric default rounder? + // if (micros.rounder.fPrecision.isBogus()) { + // micros.rounder.fPrecision = Precision::integer().withMinDigits(2); + // } } #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_usageprefs.h b/icu4c/source/i18n/number_usageprefs.h index fa56c1ea5c7..178b09dcb8e 100644 --- a/icu4c/source/i18n/number_usageprefs.h +++ b/icu4c/source/i18n/number_usageprefs.h @@ -9,18 +9,23 @@ #include "cmemory.h" #include "number_types.h" +#include "unicode/listformatter.h" +#include "unicode/localpointer.h" #include "unicode/locid.h" #include "unicode/measunit.h" #include "unicode/stringpiece.h" #include "unicode/uobject.h" -#include "unitsrouter.h" +#include "units_converter.h" +#include "units_router.h" U_NAMESPACE_BEGIN -namespace number { -namespace impl { +using ::icu::units::ComplexUnitsConverter; using ::icu::units::UnitsRouter; +namespace number { +namespace impl { + /** * A MicroPropsGenerator which uses UnitsRouter to produce output converted to a * MeasureUnit appropriate for a particular localized usage: see @@ -28,12 +33,15 @@ using ::icu::units::UnitsRouter; */ class U_I18N_API UsagePrefsHandler : public MicroPropsGenerator, public UMemory { public: - UsagePrefsHandler(const Locale &locale, const MeasureUnit inputUnit, const StringPiece usage, + UsagePrefsHandler(const Locale &locale, const MeasureUnit &inputUnit, const StringPiece usage, const MicroPropsGenerator *parent, UErrorCode &status); /** - * Obtains the appropriate output value, MeasurementUnit and + * Obtains the appropriate output value, MeasureUnit and * rounding/precision behaviour from the UnitsRouter. + * + * The output unit is passed on to the LongNameHandler via + * micros.outputUnit. */ void processQuantity(DecimalQuantity &quantity, MicroProps µs, UErrorCode &status) const U_OVERRIDE; @@ -52,6 +60,62 @@ 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 +} // namespace number + +// Export explicit template instantiations of LocalPointerBase and LocalPointer. +// This is required when building DLLs for Windows. (See datefmt.h, +// collationiterator.h, erarules.h and others for similar examples.) +// +// Note: These need to be outside of the number::impl namespace, or Clang will +// generate a compile error. +#if U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN +#if defined(_MSC_VER) +// Ignore warning 4661 as LocalPointerBase does not use operator== or operator!= +#pragma warning(push) +#pragma warning(disable: 4661) +#endif +template class U_I18N_API LocalPointerBase; +template class U_I18N_API LocalPointer; +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +#endif + +namespace number { +namespace impl { + +/** + * A MicroPropsGenerator which converts a measurement from a simple MeasureUnit + * to a Mixed MeasureUnit. + */ +class U_I18N_API UnitConversionHandler : public MicroPropsGenerator, public UMemory { + public: + /** + * Constructor. + * + * @param unit Specifies both the input and output MeasureUnit: if it is a + * MIXED unit, the input MeasureUnit will be just the biggest unit of + * the sequence. + * @param parent The parent MicroPropsGenerator. + * @param status Receives status. + */ + UnitConversionHandler(const MeasureUnit &unit, const MicroPropsGenerator *parent, + UErrorCode &status); + + /** + * Obtains the appropriate output values from the Unit Converter. + */ + void processQuantity(DecimalQuantity &quantity, MicroProps µs, + UErrorCode &status) const U_OVERRIDE; + private: + MeasureUnit fOutputUnit; + LocalPointer fUnitConverter; + const MicroPropsGenerator *fParent; }; } // namespace impl diff --git a/icu4c/source/i18n/sources.txt b/icu4c/source/i18n/sources.txt index 8c989edccc8..b26485dcc6c 100644 --- a/icu4c/source/i18n/sources.txt +++ b/icu4c/source/i18n/sources.txt @@ -32,7 +32,6 @@ collationsettings.cpp collationtailoring.cpp collationweights.cpp compactdecimalformat.cpp -complexunitsconverter.cpp coptccal.cpp cpdtrans.cpp csdetect.cpp @@ -122,6 +121,7 @@ number_patternstring.cpp number_rounding.cpp number_scientific.cpp number_skeletons.cpp +number_symbolswrapper.cpp number_usageprefs.cpp number_utils.cpp numfmt.cpp @@ -210,9 +210,10 @@ ulocdata.cpp umsg.cpp unesctrn.cpp uni2name.cpp -unitconverter.cpp -unitsdata.cpp -unitsrouter.cpp +units_data.cpp +units_complexconverter.cpp +units_converter.cpp +units_router.cpp unum.cpp unumsys.cpp upluralrules.cpp diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index bc4326fe928..a4d0724a6b6 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/complexunitsconverter.cpp b/icu4c/source/i18n/units_complexconverter.cpp similarity index 73% rename from icu4c/source/i18n/complexunitsconverter.cpp rename to icu4c/source/i18n/units_complexconverter.cpp index 7ad1c5fcf9e..f145030ff35 100644 --- a/icu4c/source/i18n/complexunitsconverter.cpp +++ b/icu4c/source/i18n/units_complexconverter.cpp @@ -8,14 +8,14 @@ #include #include "cmemory.h" -#include "complexunitsconverter.h" #include "uarrsort.h" #include "uassert.h" #include "unicode/fmtable.h" #include "unicode/localpointer.h" #include "unicode/measunit.h" #include "unicode/measure.h" -#include "unitconverter.h" +#include "units_complexconverter.h" +#include "units_converter.h" U_NAMESPACE_BEGIN namespace units { @@ -30,6 +30,11 @@ ComplexUnitsConverter::ComplexUnitsConverter(const MeasureUnitImpl &inputUnit, U_ASSERT(units_.length() != 0); + // Save the desired order of output units before we sort units_ + for (int32_t i = 0; i < units_.length(); i++) { + outputUnits_.emplaceBackAndCheckErrorCode(status, units_[i]->copy(status).build(status)); + } + // NOTE: // This comparator is used to sort the units in a descending order. Therefore, we return -1 if // the left is bigger than right and so on. @@ -101,13 +106,21 @@ UBool ComplexUnitsConverter::greaterThanOrEqual(double quantity, double limit) c } MaybeStackVector ComplexUnitsConverter::convert(double quantity, UErrorCode &status) const { + // TODO(icu-units#63): test negative numbers! + // TODO(hugovdm): return an error for "foot-and-foot"? MaybeStackVector result; for (int i = 0, n = unitConverters_.length(); i < n; ++i) { quantity = (*unitConverters_[i]).convert(quantity); if (i < n - 1) { - int64_t newQuantity = floor(quantity); - Formattable formattableNewQuantity(newQuantity); + // The double type has 15 decimal digits of precision. For choosing + // whether to use the current unit or the next smaller unit, we + // therefore nudge up the number with which the thresholding + // decision is made. However after the thresholding, we use the + // original values to ensure unbiased accuracy (to the extent of + // double's capabilities). + int64_t roundedQuantity = floor(quantity * (1 + DBL_EPSILON)); + Formattable formattableNewQuantity(roundedQuantity); // NOTE: Measure would own its MeasureUnit. MeasureUnit *type = new MeasureUnit(units_[i]->copy(status).build(status)); @@ -115,7 +128,14 @@ MaybeStackVector ComplexUnitsConverter::convert(double quantity, UError // Keep the residual of the quantity. // For example: `3.6 feet`, keep only `0.6 feet` - quantity -= newQuantity; + // + // When the calculation is near enough +/- DBL_EPSILON, we round to + // zero. (We also ensure no negative values here.) + if ((quantity - roundedQuantity) / quantity < DBL_EPSILON) { + quantity = 0; + } else { + quantity -= roundedQuantity; + } } else { // LAST ELEMENT Formattable formattableQuantity(quantity); @@ -125,6 +145,24 @@ MaybeStackVector ComplexUnitsConverter::convert(double quantity, UError } } + MaybeStackVector orderedResult; + int32_t unitsCount = outputUnits_.length(); + U_ASSERT(unitsCount == units_.length()); + Measure **arr = result.getAlias(); + // O(N^2) is fine: mixed units' unitsCount is usually 2 or 3. + for (int32_t i = 0; i < unitsCount; i++) { + for (int32_t j = i; j < unitsCount; j++) { + // Find the next expected unit, and swap it into place. + if (result[j]->getUnit() == *outputUnits_[i]) { + if (j != i) { + Measure *tmp = arr[j]; + arr[j] = arr[i]; + arr[i] = tmp; + } + } + } + } + return result; } diff --git a/icu4c/source/i18n/complexunitsconverter.h b/icu4c/source/i18n/units_complexconverter.h similarity index 64% rename from icu4c/source/i18n/complexunitsconverter.h rename to icu4c/source/i18n/units_complexconverter.h index 1cf2eb4a95b..fc355d949db 100644 --- a/icu4c/source/i18n/complexunitsconverter.h +++ b/icu4c/source/i18n/units_complexconverter.h @@ -4,18 +4,36 @@ #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING -#ifndef __COMPLEXUNITSCONVERTER_H__ -#define __COMPLEXUNITSCONVERTER_H__ +#ifndef __UNITS_COMPLEXCONVERTER_H__ +#define __UNITS_COMPLEXCONVERTER_H__ #include "cmemory.h" #include "measunit_impl.h" #include "unicode/errorcode.h" #include "unicode/measure.h" -#include "unitconverter.h" -#include "unitsdata.h" +#include "units_converter.h" +#include "units_data.h" U_NAMESPACE_BEGIN +// Export explicit template instantiations of MaybeStackArray, MemoryPool and +// MaybeStackVector. This is required when building DLLs for Windows. (See +// datefmt.h, collationiterator.h, erarules.h and others for similar examples.) +// +// Note: These need to be outside of the units namespace, or Clang will generate +// a compile error. +#if U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN +template class U_I18N_API MaybeStackArray; +template class U_I18N_API MemoryPool; +template class U_I18N_API MaybeStackVector; +template class U_I18N_API MaybeStackArray; +template class U_I18N_API MemoryPool; +template class U_I18N_API MaybeStackVector; +template class U_I18N_API MaybeStackArray; +template class U_I18N_API MemoryPool; +template class U_I18N_API MaybeStackVector; +#endif + namespace units { /** @@ -27,7 +45,7 @@ namespace units { * single unit to another single unit). Therefore, `ComplexUnitsConverter` class contains multiple * instances of the `UnitConverter` to perform the conversion. */ -class U_I18N_API ComplexUnitsConverter : UMemory { +class U_I18N_API ComplexUnitsConverter : public UMemory { public: /** * Constructor of `ComplexUnitsConverter`. @@ -59,12 +77,15 @@ class U_I18N_API ComplexUnitsConverter : UMemory { private: MaybeStackVector unitConverters_; + // Individual units of mixed units, sorted big to small MaybeStackVector units_; + // Individual units of mixed units, sorted in desired output order + MaybeStackVector outputUnits_; }; } // namespace units U_NAMESPACE_END -#endif //__COMPLEXUNITSCONVERTER_H__ +#endif //__UNITS_COMPLEXCONVERTER_H__ #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/unitconverter.cpp b/icu4c/source/i18n/units_converter.cpp similarity index 98% rename from icu4c/source/i18n/unitconverter.cpp rename to icu4c/source/i18n/units_converter.cpp index bf472f58860..34633878872 100644 --- a/icu4c/source/i18n/unitconverter.cpp +++ b/icu4c/source/i18n/units_converter.cpp @@ -13,7 +13,7 @@ #include "unicode/errorcode.h" #include "unicode/localpointer.h" #include "unicode/stringpiece.h" -#include "unitconverter.h" +#include "units_converter.h" #include #include #include @@ -516,11 +516,7 @@ double UnitConverter::convert(double inputValue) const { result = 1.0 / result; } - // TODO: remove the multiplication by 1.000,000,000,001 after using `decNumber` - - // Multiply the result by 1.000,000,000,001 to fix the deterioration from using `double` (the - // deterioration is around 15 to 17 decimal digit). - return result * 1.000000000001; + return result; } } // namespace units diff --git a/icu4c/source/i18n/unitconverter.h b/icu4c/source/i18n/units_converter.h similarity index 96% rename from icu4c/source/i18n/unitconverter.h rename to icu4c/source/i18n/units_converter.h index 3b66dbdfb12..2f5dad88a0b 100644 --- a/icu4c/source/i18n/unitconverter.h +++ b/icu4c/source/i18n/units_converter.h @@ -4,16 +4,16 @@ #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING -#ifndef __UNITCONVERTER_H__ -#define __UNITCONVERTER_H__ +#ifndef __UNITS_CONVERTER_H__ +#define __UNITS_CONVERTER_H__ #include "cmemory.h" #include "measunit_impl.h" #include "unicode/errorcode.h" #include "unicode/stringpiece.h" #include "unicode/uobject.h" -#include "unitconverter.h" -#include "unitsdata.h" +#include "units_converter.h" +#include "units_data.h" U_NAMESPACE_BEGIN namespace units { @@ -81,7 +81,7 @@ void U_I18N_API addSingleFactorConstant(StringPiece baseStr, int32_t power, Sign /** * Represents the conversion rate between `source` and `target`. */ -struct ConversionRate : public UMemory { +struct U_I18N_API ConversionRate : public UMemory { const MeasureUnitImpl source; const MeasureUnitImpl target; double factorNum = 1; @@ -159,6 +159,6 @@ class U_I18N_API UnitConverter : public UMemory { } // namespace units U_NAMESPACE_END -#endif //__UNITCONVERTER_H__ +#endif //__UNITS_CONVERTER_H__ #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/unitsdata.cpp b/icu4c/source/i18n/units_data.cpp similarity index 98% rename from icu4c/source/i18n/unitsdata.cpp rename to icu4c/source/i18n/units_data.cpp index 098c9f560bc..42bd6248b0b 100644 --- a/icu4c/source/i18n/unitsdata.cpp +++ b/icu4c/source/i18n/units_data.cpp @@ -11,7 +11,7 @@ #include "uassert.h" #include "unicode/unistr.h" #include "unicode/ures.h" -#include "unitsdata.h" +#include "units_data.h" #include "uresimp.h" #include "util.h" #include @@ -293,9 +293,7 @@ int32_t getPreferenceMetadataIndex(const MaybeStackVector @@ -26,10 +26,10 @@ namespace units { * Categories are found in `unitQuantities` in the `units` resource (see * `units.txt`). * - * TODO(hugovdm): if we give unitsdata.cpp access to the functionality of - * `extractCompoundBaseUnit` which is currently in unitconverter.cpp, we could + * TODO(hugovdm): if we give units_data.cpp access to the functionality of + * `extractCompoundBaseUnit` which is currently in units_converter.cpp, we could * support all units for which there is a category. Does it make sense to move - * that function to unitsdata.cpp? + * that function to units_data.cpp? */ CharString U_I18N_API getUnitCategory(const char *baseUnitIdentifier, UErrorCode &status); @@ -59,6 +59,22 @@ class U_I18N_API ConversionRateInfo : public UMemory { CharString offset; }; +} // namespace units + +// Export explicit template instantiations of MaybeStackArray, MemoryPool and +// MaybeStackVector. This is required when building DLLs for Windows. (See +// datefmt.h, collationiterator.h, erarules.h and others for similar examples.) +// +// Note: These need to be outside of the units namespace, or Clang will generate +// a compile error. +#if U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN +template class U_I18N_API MaybeStackArray; +template class U_I18N_API MemoryPool; +template class U_I18N_API MaybeStackVector; +#endif + +namespace units { + /** * Returns ConversionRateInfo for all supported conversions. * @@ -136,6 +152,25 @@ class U_I18N_API UnitPreferenceMetadata : public UMemory { bool *foundRegion) const; }; +} // namespace units + +// Export explicit template instantiations of MaybeStackArray, MemoryPool and +// MaybeStackVector. This is required when building DLLs for Windows. (See +// datefmt.h, collationiterator.h, erarules.h and others for similar examples.) +// +// Note: These need to be outside of the units namespace, or Clang will generate +// a compile error. +#if U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN +template class U_I18N_API MaybeStackArray; +template class U_I18N_API MemoryPool; +template class U_I18N_API MaybeStackVector; +template class U_I18N_API MaybeStackArray; +template class U_I18N_API MemoryPool; +template class U_I18N_API MaybeStackVector; +#endif + +namespace units { + /** * Unit Preferences information for various locales and usages. */ @@ -189,6 +224,6 @@ class U_I18N_API UnitPreferences { } // namespace units U_NAMESPACE_END -#endif //__GETUNITSDATA_H__ +#endif //__UNITS_DATA_H__ #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/unitsrouter.cpp b/icu4c/source/i18n/units_router.cpp similarity index 87% rename from icu4c/source/i18n/unitsrouter.cpp rename to icu4c/source/i18n/units_router.cpp index 7381d550c5b..759ae2b744d 100644 --- a/icu4c/source/i18n/unitsrouter.cpp +++ b/icu4c/source/i18n/units_router.cpp @@ -12,8 +12,8 @@ #include "number_decimalquantity.h" #include "resource.h" #include "unicode/measure.h" -#include "unitsdata.h" -#include "unitsrouter.h" +#include "units_data.h" +#include "units_router.h" U_NAMESPACE_BEGIN namespace units { @@ -70,19 +70,19 @@ UnitsRouter::UnitsRouter(MeasureUnit inputUnit, StringPiece region, StringPiece RouteResult UnitsRouter::route(double quantity, UErrorCode &status) const { for (int i = 0, n = converterPreferences_.length(); i < n; i++) { const auto &converterPreference = *converterPreferences_[i]; - - if (converterPreference.converter.greaterThanOrEqual(quantity, converterPreference.limit)) { - return RouteResult(converterPreference.converter.convert(quantity, status), // - converterPreference.precision // - ); + if (converterPreference.converter.greaterThanOrEqual(quantity * (1 + DBL_EPSILON), + converterPreference.limit)) { + return RouteResult(converterPreference.converter.convert(quantity, status), + converterPreference.precision, + converterPreference.targetUnit.copy(status)); } } // In case of the `quantity` does not fit in any converter limit, use the last converter. const auto &lastConverterPreference = (*converterPreferences_[converterPreferences_.length() - 1]); - return RouteResult(lastConverterPreference.converter.convert(quantity, status), // - lastConverterPreference.precision // - ); + return RouteResult(lastConverterPreference.converter.convert(quantity, status), + lastConverterPreference.precision, + lastConverterPreference.targetUnit.copy(status)); } const MaybeStackVector *UnitsRouter::getOutputUnits() const { diff --git a/icu4c/source/i18n/unitsrouter.h b/icu4c/source/i18n/units_router.h similarity index 70% rename from icu4c/source/i18n/unitsrouter.h rename to icu4c/source/i18n/units_router.h index 68284663293..e6b74aa11b4 100644 --- a/icu4c/source/i18n/unitsrouter.h +++ b/icu4c/source/i18n/units_router.h @@ -4,18 +4,18 @@ #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING -#ifndef __UNITSROUTER_H__ -#define __UNITSROUTER_H__ +#ifndef __UNITS_ROUTER_H__ +#define __UNITS_ROUTER_H__ #include #include "cmemory.h" -#include "complexunitsconverter.h" #include "measunit_impl.h" #include "unicode/measunit.h" #include "unicode/stringpiece.h" #include "unicode/uobject.h" -#include "unitsdata.h" +#include "units_complexconverter.h" +#include "units_data.h" U_NAMESPACE_BEGIN @@ -25,11 +25,25 @@ 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; - RouteResult(MaybeStackVector measures, UnicodeString precision) - : measures(std::move(measures)), precision(std::move(precision)) {} + // The output unit for this RouteResult. This may be a MIXED unit - for + // example: "yard-and-foot-and-inch", for which `measures` will have three + // elements. + MeasureUnitImpl outputUnit; + + RouteResult(MaybeStackVector measures, UnicodeString precision, MeasureUnitImpl outputUnit) + : measures(std::move(measures)), precision(std::move(precision)), outputUnit(std::move(outputUnit)) {} }; /** @@ -46,6 +60,10 @@ struct ConverterPreference : UMemory { double limit; UnicodeString precision; + // The output unit for this ConverterPreference. This may be a MIXED unit - + // for example: "yard-and-foot-and-inch". + MeasureUnitImpl targetUnit; + // In case there is no limit, the limit will be -inf. ConverterPreference(const MeasureUnitImpl &source, const MeasureUnitImpl &complexTarget, UnicodeString precision, const ConversionRates &ratesInfo, UErrorCode &status) @@ -56,9 +74,25 @@ struct ConverterPreference : UMemory { double limit, UnicodeString precision, const ConversionRates &ratesInfo, UErrorCode &status) : converter(source, complexTarget, ratesInfo, status), limit(limit), - precision(std::move(precision)) {} + precision(std::move(precision)), targetUnit(complexTarget.copy(status)) {} }; +} // namespace units + +// Export explicit template instantiations of MaybeStackArray, MemoryPool and +// MaybeStackVector. This is required when building DLLs for Windows. (See +// datefmt.h, collationiterator.h, erarules.h and others for similar examples.) +// +// Note: These need to be outside of the units namespace, or Clang will generate +// a compile error. +#if U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN +template class U_I18N_API MaybeStackArray; +template class U_I18N_API MemoryPool; +template class U_I18N_API MaybeStackVector; +#endif + +namespace units { + /** * `UnitsRouter` responsible for converting from a single unit (such as `meter` or `meter-per-second`) to * one of the complex units based on the limits. @@ -114,6 +148,6 @@ class U_I18N_API UnitsRouter { } // namespace units U_NAMESPACE_END -#endif //__UNITSROUTER_H__ +#endif //__UNITS_ROUTER_H__ #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/test/depstest/dependencies.txt b/icu4c/source/test/depstest/dependencies.txt index f746aabce1a..bee376653fb 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 @@ -976,11 +977,9 @@ group: number_output standardplural.o plurrule.o deps # FormattedNumber internals: - number_representation format formatted_value_sbimpl + number_representation format formatted_value_sbimpl units # PluralRules internals: unifiedcache - # Unit Formatting - units group: numberformatter # ICU 60+ NumberFormatter API @@ -989,17 +988,43 @@ 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 - number_representation number_output + listformatter number_representation number_output + numsys + number_usageprefs + number_rounding + number_symbolswrapper uclean_i18n common +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 number_skeletons.o number_capi.o number_asformat.o numrange_capi.o @@ -1007,6 +1032,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 @@ -1076,7 +1107,7 @@ group: units stringenumeration errorcode group: unitsformatter - unitsdata.o unitconverter.o complexunitsconverter.o unitsrouter.o + units_data.o units_converter.o units_complexconverter.o units_router.o deps resourcebundle units_extra double_conversion number_representation formattable sort diff --git a/icu4c/source/test/intltest/Makefile.in b/icu4c/source/test/intltest/Makefile.in index cb356c44db9..13d3ea86dc9 100644 --- a/icu4c/source/test/intltest/Makefile.in +++ b/icu4c/source/test/intltest/Makefile.in @@ -69,7 +69,7 @@ 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 \ -unitsdatatest.o unitstest.o unitsroutertest.o +units_data_test.o units_router_test.o units_test.o DEPS = $(OBJECTS:.o=.d) diff --git a/icu4c/source/test/intltest/intltest.vcxproj b/icu4c/source/test/intltest/intltest.vcxproj index e6cee37ad3d..319c3ab58f6 100644 --- a/icu4c/source/test/intltest/intltest.vcxproj +++ b/icu4c/source/test/intltest/intltest.vcxproj @@ -285,9 +285,9 @@ - - - + + + diff --git a/icu4c/source/test/intltest/intltest.vcxproj.filters b/icu4c/source/test/intltest/intltest.vcxproj.filters index bba4e921982..b7069801c5b 100644 --- a/icu4c/source/test/intltest/intltest.vcxproj.filters +++ b/icu4c/source/test/intltest/intltest.vcxproj.filters @@ -550,13 +550,13 @@ locales & resources - + formatting - + formatting - + formatting diff --git a/icu4c/source/test/intltest/measfmttest.cpp b/icu4c/source/test/intltest/measfmttest.cpp index 03c5fa808d1..938163e301c 100644 --- a/icu4c/source/test/intltest/measfmttest.cpp +++ b/icu4c/source/test/intltest/measfmttest.cpp @@ -82,6 +82,7 @@ private: void TestNumericTimeSomeSpecialFormats(); void TestIdentifiers(); void TestInvalidIdentifiers(); + void TestParseToBuiltIn(); void TestKilogramIdentifier(); void TestCompoundUnitOperations(); void TestDimensionlessBehaviour(); @@ -209,6 +210,7 @@ void MeasureFormatTest::runIndexedTest( TESTCASE_AUTO(TestNumericTimeSomeSpecialFormats); TESTCASE_AUTO(TestIdentifiers); TESTCASE_AUTO(TestInvalidIdentifiers); + TESTCASE_AUTO(TestParseToBuiltIn); TESTCASE_AUTO(TestKilogramIdentifier); TESTCASE_AUTO(TestCompoundUnitOperations); TESTCASE_AUTO(TestDimensionlessBehaviour); @@ -3514,11 +3516,23 @@ void MeasureFormatTest::TestIndividualPluralFallback() { // and falls back to fr for the "other" form. IcuTestErrorCode errorCode(*this, "TestIndividualPluralFallback"); MeasureFormat mf("fr_CA", UMEASFMT_WIDTH_SHORT, errorCode); + if (errorCode.errIfFailureAndReset("MeasureFormat mf(...) failed.")) { + return; + } LocalPointer twoDeg( new Measure(2.0, MeasureUnit::createGenericTemperature(errorCode), errorCode), errorCode); + if (errorCode.errIfFailureAndReset("Creating twoDeg failed.")) { + return; + } UnicodeString expected = UNICODE_STRING_SIMPLE("2\\u00B0").unescape(); UnicodeString actual; - assertEquals("2 deg temp in fr_CA", expected, mf.format(twoDeg.orphan(), actual, errorCode), TRUE); + // Formattable adopts the pointer + mf.format(Formattable(twoDeg.orphan()), actual, errorCode); + if (errorCode.errIfFailureAndReset("mf.format(...) failed.")) { + return; + } + assertEquals("2 deg temp in fr_CA", expected, actual, TRUE); + errorCode.errIfFailureAndReset("mf.format failed"); } void MeasureFormatTest::Test20332_PersonUnits() { @@ -3705,6 +3719,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()); + } +} + // Kilogram is a "base unit", although it's also "gram" with a kilo- prefix. // This tests that it is handled in the preferred manner. void MeasureFormatTest::TestKilogramIdentifier() { diff --git a/icu4c/source/test/intltest/numbertest.h b/icu4c/source/test/intltest/numbertest.h index c96c329ad20..4bae2c1b6bb 100644 --- a/icu4c/source/test/intltest/numbertest.h +++ b/icu4c/source/test/intltest/numbertest.h @@ -55,6 +55,8 @@ class NumberFormatterApiTest : public IntlTestWithFieldPosition { void unitMeasure(); void unitCompoundMeasure(); void unitUsage(); + void unitUsageErrorCodes(); + void unitUsageSkeletons(); void unitCurrency(); void unitPercent(); void percentParity(); @@ -85,6 +87,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 cb30123ca9b..cce8ddfac3f 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -11,12 +11,14 @@ #include #include "unicode/unum.h" #include "unicode/numberformatter.h" +#include "unicode/testlog.h" +#include "unicode/utypes.h" #include "number_asformat.h" #include "number_types.h" #include "number_utils.h" -#include "numbertest.h" -#include "unicode/utypes.h" #include "number_utypes.h" +#include "number_microprops.h" +#include "numbertest.h" using number::impl::UFormattedNumberData; @@ -76,6 +78,8 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha TESTCASE_AUTO(unitMeasure); TESTCASE_AUTO(unitCompoundMeasure); TESTCASE_AUTO(unitUsage); + TESTCASE_AUTO(unitUsageErrorCodes); + TESTCASE_AUTO(unitUsageSkeletons); TESTCASE_AUTO(unitCurrency); TESTCASE_AUTO(unitPercent); if (!quick) { @@ -113,6 +117,7 @@ void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const cha TESTCASE_AUTO(localPointerCAPI); TESTCASE_AUTO(toObject); TESTCASE_AUTO(toDecimalNumber); + TESTCASE_AUTO(microPropsInternals); TESTCASE_AUTO_END; } @@ -520,6 +525,8 @@ void NumberFormatterApiTest::notationCompact() { } void NumberFormatterApiTest::unitMeasure() { + IcuTestErrorCode status(*this, "unitMeasure()"); + assertFormatDescending( u"Meters Short and unit() method", u"measure-unit/length-meter", @@ -686,82 +693,86 @@ void NumberFormatterApiTest::unitMeasure() { Locale("es-MX"), 5, u"5 a\u00F1os"); -} -void NumberFormatterApiTest::unitUsage() { - UnlocalizedNumberFormatter unloc_formatter = - NumberFormatter::with().usage("road").unit(MeasureUnit::getMeter()); + // TODO(icu-units#35): skeleton generation. + assertFormatSingle( + u"Mixed unit", + nullptr, + u"unit/yard-and-foot-and-inch", + NumberFormatter::with() + .unit(MeasureUnit::forIdentifier("yard-and-foot-and-inch", status)), + Locale("en-US"), + 3.65, + "3 yd, 1 ft, 11.4 in"); - IcuTestErrorCode status(*this, "unitUsage()"); + // TODO(icu-units#35): skeleton generation. + assertFormatSingle( + u"Mixed unit, Scientific", + nullptr, + u"unit/yard-and-foot-and-inch E0", + NumberFormatter::with() + .unit(MeasureUnit::forIdentifier("yard-and-foot-and-inch", status)) + .notation(Notation::scientific()), + Locale("en-US"), + 3.65, + "3 yd, 1 ft, 1.14E1 in"); - LocalizedNumberFormatter formatter = unloc_formatter.locale("en-ZA"); - FormattedNumber formattedNum = formatter.formatDouble(300, status); - assertTrue(UnicodeString("unitUsage() en-ZA road, got outputUnit: \"") + - formattedNum.getOutputUnit(status).getIdentifier() + "\"", - MeasureUnit::getMeter() == formattedNum.getOutputUnit(status)); - assertEquals("unitUsage() en-ZA road", "300 m", formattedNum.toString(status)); - assertFormatDescendingBig( - u"unitUsage() en-ZA road", - u"measure-unit/length-meter usage/road", - u"unit/meter usage/road", - unloc_formatter, - Locale("en-ZA"), - u"87\u00A0650 km", - u"8\u00A0765 km", - u"877 km", - u"88 km", - u"8,8 km", - u"877 m", - u"88 m", - u"8,8 m", - u"0 m"); + // TODO(icu-units#35): skeleton generation. + assertFormatSingle( + u"Mixed Unit (Narrow Version)", + nullptr, + u"unit/metric-ton-and-kilogram-and-gram unit-width-narrow", + NumberFormatter::with() + .unit(MeasureUnit::forIdentifier("metric-ton-and-kilogram-and-gram", status)) + .unitWidth(UNUM_UNIT_WIDTH_NARROW), + Locale("en-US"), + 4.28571, + u"4t 285kg 710g"); - formatter = unloc_formatter.locale("en-GB"); - formattedNum = formatter.formatDouble(300, status); - assertTrue(UnicodeString("unitUsage() en-GB road, got outputUnit: \"") + - formattedNum.getOutputUnit(status).getIdentifier() + "\"", - MeasureUnit::getYard() == formattedNum.getOutputUnit(status)); - assertEquals("unitUsage() en-GB road", "328 yd", formattedNum.toString(status)); - assertFormatDescendingBig( - u"unitUsage() en-GB road", - u"measure-unit/length-meter usage/road", - u"unit/meter usage/road", - unloc_formatter, - Locale("en-GB"), - u"54,463 mi", - u"5,446 mi", - u"545 mi", - u"54 mi", - u"5.4 mi", - u"0.54 mi", - u"96 yd", - u"9.6 yd", - u"0 yd"); + // TODO(icu-units#35): skeleton generation. + assertFormatSingle( + u"Mixed Unit (Short Version)", + nullptr, + u"unit/metric-ton-and-kilogram-and-gram unit-width-short", + NumberFormatter::with() + .unit(MeasureUnit::forIdentifier("metric-ton-and-kilogram-and-gram", status)) + .unitWidth(UNUM_UNIT_WIDTH_SHORT), + Locale("en-US"), + 4.28571, + u"4 t, 285 kg, 710 g"); - formatter = unloc_formatter.locale("en-US"); - formattedNum = formatter.formatDouble(300, status); - assertTrue(UnicodeString("unitUsage() en-US road, got outputUnit: \"") + - formattedNum.getOutputUnit(status).getIdentifier() + "\"", - MeasureUnit::getFoot() == formattedNum.getOutputUnit(status)); - assertEquals("unitUsage() en-US road", "984 ft", formattedNum.toString(status)); - assertFormatDescendingBig( - u"unitUsage() en-US road", - u"measure-unit/length-meter usage/road", - u"unit/meter usage/road", - unloc_formatter, + // TODO(icu-units#35): skeleton generation. + assertFormatSingle( + u"Mixed Unit (Full Name Version)", + nullptr, + u"unit/metric-ton-and-kilogram-and-gram unit-width-full-name", + NumberFormatter::with() + .unit(MeasureUnit::forIdentifier("metric-ton-and-kilogram-and-gram", status)) + .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), Locale("en-US"), - u"54,463 mi", - u"5,446 mi", - u"545 mi", - u"54 mi", - u"5.4 mi", - u"0.54 mi", - u"288 ft", - u"29 ft", - u"0 ft"); + 4.28571, + u"4 metric tons, 285 kilograms, 710 grams"); + +// // TODO(icu-units#73): deal with this "1 foot 12 inches" problem. +// // At the time of writing, this test would pass, but is commented out +// // because it reflects undesired behaviour: +// assertFormatSingle( +// u"Demonstrating the \"1 foot 12 inches\" problem", +// nullptr, +// u"unit/foot-and-inch", +// NumberFormatter::with() +// .unit(MeasureUnit::forIdentifier("foot-and-inch", status)) +// .precision(Precision::maxSignificantDigits(4)) +// .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), +// Locale("en-US"), +// 1.9999, +// // This is undesireable but current behaviour: +// u"1 foot, 12 inches"); } void NumberFormatterApiTest::unitCompoundMeasure() { + IcuTestErrorCode status(*this, "unitCompoundMeasure()"); + assertFormatDescending( u"Meters Per Second Short (unit that simplifies) and perUnit method", u"measure-unit/length-meter per-measure-unit/duration-second", @@ -778,6 +789,17 @@ void NumberFormatterApiTest::unitCompoundMeasure() { u"0.008765 m/s", u"0 m/s"); + // TODO(icu-units#35): does not normalize as desired: while "unit/*" does + // get split into unit/perUnit, ".unit(*)" and "measure-unit/*" don't: + assertFormatSingle( + u"Built-in unit, meter-per-second", + u"measure-unit/speed-meter-per-second", + u"~unit/meter-per-second", + NumberFormatter::with().unit(MeasureUnit::getMeterPerSecond()), + Locale("en-GB"), + 2.4, + u"2.4 m/s"); + assertFormatDescending( u"Pounds Per Square Mile Short (secondary unit has per-format) and adoptPerUnit method", u"measure-unit/mass-pound per-measure-unit/area-square-mile", @@ -826,6 +848,427 @@ void NumberFormatterApiTest::unitCompoundMeasure() { // u"0.08765 J/fur", // u"0.008765 J/fur", // u"0 J/fur"); + + // 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: do we leave this enabled to demonstrate current + // behaviour, and changing behaviour in the future? Or comment it out to + // avoid asserting 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"); + + // Testing the rejection of invalid specifications + + // If .unit() is not given a built-in type, .perUnit() is not allowed + // (because .unit is now flexible enough to handle compound units, + // .perUnit() is supported for backward compatibility). + LocalizedNumberFormatter nf = NumberFormatter::with() + .unit(MeasureUnit::forIdentifier("furlong-pascal", status)) + .perUnit(METER) + .locale("en-GB"); + status.assertSuccess(); // Error is only returned once we try to format. + FormattedNumber num = nf.formatDouble(2.4, status); + if (!status.expectErrorAndReset(U_UNSUPPORTED_ERROR)) { + errln(UnicodeString("Expected failure, got: \"") + + nf.formatDouble(2.4, status).toString(status) + "\"."); + status.assertSuccess(); + } + + // .perUnit() may only be passed a built-in type, "square-second" is not a + // built-in type. + nf = NumberFormatter::with() + .unit(MeasureUnit::getMeter()) + .perUnit(MeasureUnit::forIdentifier("square-second", status)) + .locale("en-GB"); + status.assertSuccess(); // Error is only returned once we try to format. + num = nf.formatDouble(2.4, status); + if (!status.expectErrorAndReset(U_UNSUPPORTED_ERROR)) { + errln(UnicodeString("Expected failure, got: \"") + + nf.formatDouble(2.4, status).toString(status) + "\"."); + status.assertSuccess(); + } +} + +void NumberFormatterApiTest::unitUsage() { + IcuTestErrorCode status(*this, "unitUsage()"); + UnlocalizedNumberFormatter unloc_formatter; + LocalizedNumberFormatter formatter; + FormattedNumber formattedNum; + UnicodeString uTestCase; + + unloc_formatter = NumberFormatter::with().usage("road").unit(MeasureUnit::getMeter()); + + uTestCase = u"unitUsage() en-ZA road"; + formatter = unloc_formatter.locale("en-ZA"); + formattedNum = formatter.formatDouble(321, status); + status.errIfFailureAndReset("unitUsage() en-ZA road formatDouble"); + assertTrue( + uTestCase + u", got outputUnit: \"" + formattedNum.getOutputUnit(status).getIdentifier() + "\"", + MeasureUnit::getMeter() == formattedNum.getOutputUnit(status)); + assertEquals(uTestCase, "300 m", formattedNum.toString(status)); + { + static const UFieldPosition expectedFieldPositions[] = { + {UNUM_INTEGER_FIELD, 0, 3}, + {UNUM_MEASURE_UNIT_FIELD, 4, 5}}; + assertNumberFieldPositions( + (uTestCase + u" field positions").getTerminatedBuffer(), + formattedNum, + expectedFieldPositions, + UPRV_LENGTHOF(expectedFieldPositions)); + } + assertFormatDescendingBig( + uTestCase.getTerminatedBuffer(), + u"measure-unit/length-meter usage/road", + u"unit/meter usage/road", + unloc_formatter, + Locale("en-ZA"), + u"87\u00A0650 km", + u"8\u00A0765 km", + u"876 km", // 6.5 rounds down, 7.5 rounds up. + u"88 km", + u"8,8 km", + 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(321, status); + status.errIfFailureAndReset("unitUsage() en-GB road, formatDouble(...)"); + assertTrue( + uTestCase + u", got outputUnit: \"" + formattedNum.getOutputUnit(status).getIdentifier() + "\"", + MeasureUnit::getYard() == formattedNum.getOutputUnit(status)); + status.errIfFailureAndReset("unitUsage() en-GB road, getOutputUnit(...)"); + assertEquals(uTestCase, "350 yd", formattedNum.toString(status)); + status.errIfFailureAndReset("unitUsage() en-GB road, toString(...)"); + { + static const UFieldPosition expectedFieldPositions[] = { + {UNUM_INTEGER_FIELD, 0, 3}, + {UNUM_MEASURE_UNIT_FIELD, 4, 6}}; + assertNumberFieldPositions( + (uTestCase + u" field positions").getTerminatedBuffer(), + formattedNum, + expectedFieldPositions, + UPRV_LENGTHOF(expectedFieldPositions)); + } + assertFormatDescendingBig( + uTestCase.getTerminatedBuffer(), + u"measure-unit/length-meter usage/road", + u"unit/meter usage/road", + unloc_formatter, + Locale("en-GB"), + u"54,463 mi", + u"5,446 mi", + u"545 mi", + u"54 mi", + u"5.4 mi", + u"0.54 mi", + u"96 yd", + u"9.6 yd", + u"0 yd"); + + uTestCase = u"unitUsage() en-US road"; + formatter = unloc_formatter.locale("en-US"); + formattedNum = formatter.formatDouble(321, status); + status.errIfFailureAndReset("unitUsage() en-US road, formatDouble(...)"); + assertTrue( + uTestCase + u", got outputUnit: \"" + formattedNum.getOutputUnit(status).getIdentifier() + "\"", + MeasureUnit::getFoot() == formattedNum.getOutputUnit(status)); + status.errIfFailureAndReset("unitUsage() en-US road, getOutputUnit(...)"); + assertEquals(uTestCase, "1,050 ft", formattedNum.toString(status)); + status.errIfFailureAndReset("unitUsage() en-US road, toString(...)"); + { + static const UFieldPosition expectedFieldPositions[] = { + {UNUM_GROUPING_SEPARATOR_FIELD, 1, 2}, + {UNUM_INTEGER_FIELD, 0, 5}, + {UNUM_MEASURE_UNIT_FIELD, 6, 8}}; + assertNumberFieldPositions( + (uTestCase + u" field positions").getTerminatedBuffer(), + formattedNum, + expectedFieldPositions, + UPRV_LENGTHOF(expectedFieldPositions)); + } + assertFormatDescendingBig( + uTestCase.getTerminatedBuffer(), + u"measure-unit/length-meter usage/road", + u"unit/meter usage/road", + unloc_formatter, + Locale("en-US"), + u"54,463 mi", + u"5,446 mi", + u"545 mi", + u"54 mi", + u"5.4 mi", + u"0.54 mi", + 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); + status.errIfFailureAndReset("unitUsage() en-GB person formatDouble"); + assertTrue( + uTestCase + ", got outputUnit: \"" + formattedNum.getOutputUnit(status).getIdentifier() + "\"", + MeasureUnit::forIdentifier("stone-and-pound", status) == formattedNum.getOutputUnit(status)); + status.errIfFailureAndReset("unitUsage() en-GB person - formattedNum.getOutputUnit(status)"); + assertEquals(uTestCase, "12 st, 8.4 lb", formattedNum.toString(status)); + status.errIfFailureAndReset("unitUsage() en-GB person, toString(...)"); + { + static const UFieldPosition expectedFieldPositions[] = { + // // Desired output: TODO(icu-units#67) + // {UNUM_INTEGER_FIELD, 0, 2}, + // {UNUM_MEASURE_UNIT_FIELD, 3, 5}, + // {ULISTFMT_LITERAL_FIELD, 5, 6}, + // {UNUM_INTEGER_FIELD, 7, 8}, + // {UNUM_DECIMAL_SEPARATOR_FIELD, 8, 9}, + // {UNUM_FRACTION_FIELD, 9, 10}, + // {UNUM_MEASURE_UNIT_FIELD, 11, 13}}; + + // Current output: rather no fields than wrong fields + {UNUM_INTEGER_FIELD, 7, 8}, + {UNUM_DECIMAL_SEPARATOR_FIELD, 8, 9}, + {UNUM_FRACTION_FIELD, 9, 10}, + }; + assertNumberFieldPositions( + (uTestCase + u" field positions").getTerminatedBuffer(), + formattedNum, + expectedFieldPositions, + UPRV_LENGTHOF(expectedFieldPositions)); + } + assertFormatDescending( + uTestCase.getTerminatedBuffer(), + u"measure-unit/mass-kilogram usage/person", + u"unit/kilogram usage/person", + unloc_formatter, + Locale("en-GB"), + u"13,802 st, 7.2 lb", + u"1,380 st, 3.5 lb", + u"138 st, 0.35 lb", + u"13 st, 11 lb", + u"1 st, 5.3 lb", + u"1 lb, 15 oz", + u"0 lb, 3.1 oz", + u"0 lb, 0.31 oz", + u"0 lb, 0 oz"); + + assertFormatDescending( + uTestCase.getTerminatedBuffer(), + u"usage/person unit-width-narrow measure-unit/mass-kilogram", + u"usage/person unit-width-narrow unit/kilogram", + unloc_formatter.unitWidth(UNUM_UNIT_WIDTH_NARROW), + Locale("en-GB"), + u"13,802st 7.2lb", + u"1,380st 3.5lb", + u"138st 0.35lb", + u"13st 11lb", + u"1st 5.3lb", + u"1lb 15oz", + u"0lb 3.1oz", + u"0lb 0.31oz", + u"0lb 0oz"); + + assertFormatDescending( + uTestCase.getTerminatedBuffer(), + u"usage/person unit-width-short measure-unit/mass-kilogram", + u"usage/person unit-width-short unit/kilogram", + unloc_formatter.unitWidth(UNUM_UNIT_WIDTH_SHORT), + Locale("en-GB"), + u"13,802 st, 7.2 lb", + u"1,380 st, 3.5 lb", + u"138 st, 0.35 lb", + u"13 st, 11 lb", + u"1 st, 5.3 lb", + u"1 lb, 15 oz", + u"0 lb, 3.1 oz", + u"0 lb, 0.31 oz", + u"0 lb, 0 oz"); + + assertFormatDescending( + uTestCase.getTerminatedBuffer(), + u"usage/person unit-width-full-name measure-unit/mass-kilogram", + u"usage/person unit-width-full-name unit/kilogram", + unloc_formatter.unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), + Locale("en-GB"), + u"13,802 stone, 7.2 pounds", + u"1,380 stone, 3.5 pounds", + u"138 stone, 0.35 pounds", + u"13 stone, 11 pounds", + u"1 stone, 5.3 pounds", + u"1 pound, 15 ounces", + 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() { + IcuTestErrorCode status(*this, "unitUsageErrorCodes()"); + UnlocalizedNumberFormatter unloc_formatter; + + unloc_formatter = NumberFormatter::forSkeleton(u"unit/foobar", status); + // This gives an error, because foobar is an invalid unit: + status.expectErrorAndReset(U_NUMBER_SKELETON_SYNTAX_ERROR); + + unloc_formatter = NumberFormatter::forSkeleton(u"usage/foobar", status); + // This does not give an error, because usage is not looked up yet. + status.errIfFailureAndReset("Expected behaviour: no immediate error for invalid usage"); + unloc_formatter.locale("en-GB").formatInt(1, status); + // Lacking a unit results in a failure. The skeleton is "incomplete", but we + // support adding the unit via the fluent API, so it is not an error until + // we build the formatting pipeline itself. + status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR); + // Adding the unit as part of the fluent chain leads to success. + unloc_formatter.unit(MeasureUnit::getMeter()).locale("en-GB").formatInt(1, status); + 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, // 0.45 rounds down, 0.55 rounds up. + u"3,214E2 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::unitCurrency() { @@ -3604,6 +4047,33 @@ void NumberFormatterApiTest::toDecimalNumber() { "9.8765E+14", fn.toDecimalNumber(status).c_str()); } +void NumberFormatterApiTest::microPropsInternals() { + // Verify copy construction and assignment operators. + int64_t testValues[2] = {4, 61}; + + MicroProps mp; + assertEquals("capacity", 2, mp.mixedMeasures.getCapacity()); + mp.mixedMeasures[0] = testValues[0]; + mp.mixedMeasures[1] = testValues[1]; + MicroProps copyConstructed(mp); + MicroProps copyAssigned; + int64_t *resizeResult = mp.mixedMeasures.resize(4, 4); + assertTrue("Resize success", resizeResult != NULL); + copyAssigned = mp; + + assertTrue("MicroProps success status", U_SUCCESS(mp.mixedMeasures.status)); + assertTrue("Copy Constructed success status", U_SUCCESS(copyConstructed.mixedMeasures.status)); + assertTrue("Copy Assigned success status", U_SUCCESS(copyAssigned.mixedMeasures.status)); + assertEquals("Original values[0]", testValues[0], mp.mixedMeasures[0]); + assertEquals("Original values[1]", testValues[1], mp.mixedMeasures[1]); + assertEquals("Copy Constructed[0]", testValues[0], copyConstructed.mixedMeasures[0]); + assertEquals("Copy Constructed[1]", testValues[1], copyConstructed.mixedMeasures[1]); + assertEquals("Copy Assigned[0]", testValues[0], copyAssigned.mixedMeasures[0]); + assertEquals("Copy Assigned[1]", testValues[1], copyAssigned.mixedMeasures[1]); + assertEquals("Original capacity", 4, mp.mixedMeasures.getCapacity()); + assertEquals("Copy Constructed capacity", 2, copyConstructed.mixedMeasures.getCapacity()); + assertEquals("Copy Assigned capacity", 4, copyAssigned.mixedMeasures.getCapacity()); +} void NumberFormatterApiTest::assertFormatDescending( const char16_t* umessage, diff --git a/icu4c/source/test/intltest/unitsdatatest.cpp b/icu4c/source/test/intltest/units_data_test.cpp similarity index 99% rename from icu4c/source/test/intltest/unitsdatatest.cpp rename to icu4c/source/test/intltest/units_data_test.cpp index 3ac36f7d0f8..18464516146 100644 --- a/icu4c/source/test/intltest/unitsdatatest.cpp +++ b/icu4c/source/test/intltest/units_data_test.cpp @@ -5,7 +5,7 @@ #if !UCONFIG_NO_FORMATTING -#include "unitsdata.h" +#include "units_data.h" #include "intltest.h" using namespace ::icu::units; diff --git a/icu4c/source/test/intltest/unitsroutertest.cpp b/icu4c/source/test/intltest/units_router_test.cpp similarity index 97% rename from icu4c/source/test/intltest/unitsroutertest.cpp rename to icu4c/source/test/intltest/units_router_test.cpp index 8d2489e577e..d43e1c85319 100644 --- a/icu4c/source/test/intltest/unitsroutertest.cpp +++ b/icu4c/source/test/intltest/units_router_test.cpp @@ -7,7 +7,7 @@ #include "intltest.h" #include "unicode/unistr.h" -#include "unitsrouter.h" +#include "units_router.h" class UnitsRouterTest : public IntlTest { diff --git a/icu4c/source/test/intltest/unitstest.cpp b/icu4c/source/test/intltest/units_test.cpp similarity index 79% rename from icu4c/source/test/intltest/unitstest.cpp rename to icu4c/source/test/intltest/units_test.cpp index 8309bed1add..8053f1d5357 100644 --- a/icu4c/source/test/intltest/unitstest.cpp +++ b/icu4c/source/test/intltest/units_test.cpp @@ -19,9 +19,10 @@ #include "unicode/unistr.h" #include "unicode/unum.h" #include "unicode/ures.h" -#include "unitconverter.h" -#include "unitsdata.h" -#include "unitsrouter.h" +#include "units_complexconverter.h" +#include "units_converter.h" +#include "units_data.h" +#include "units_router.h" #include "uparse.h" #include "uresimp.h" @@ -44,6 +45,7 @@ class UnitsTest : public IntlTest { void testUnitConstantFreshness(); void testConversionCapability(); void testConversions(); + void testComplexUnitsConverter(); void testComplexUnitConverterSorting(); void testPreferences(); void testSiPrefixes(); @@ -62,6 +64,7 @@ void UnitsTest::runIndexedTest(int32_t index, UBool exec, const char *&name, cha TESTCASE_AUTO(testUnitConstantFreshness); TESTCASE_AUTO(testConversionCapability); TESTCASE_AUTO(testConversions); + TESTCASE_AUTO(testComplexUnitsConverter); TESTCASE_AUTO(testComplexUnitConverterSorting); TESTCASE_AUTO(testPreferences); TESTCASE_AUTO(testSiPrefixes); @@ -88,7 +91,7 @@ void UnitsTest::testUnitConstantFreshness() { addSingleFactorConstant(constant, 1, POSITIVE, factor, status); if (status.errDataIfFailureAndReset( "addSingleFactorConstant(<%s>, ...).\n\n" - "If U_INVALID_FORMAT_ERROR, please check that \"icu4c/source/i18n/unitconverter.cpp\" " + "If U_INVALID_FORMAT_ERROR, please check that \"icu4c/source/i18n/units_converter.cpp\" " "has all constants? Is \"%s\" a new constant?\n", constant, constant)) { continue; @@ -105,7 +108,7 @@ void UnitsTest::testUnitConstantFreshness() { } DecimalQuantity dqVal; UErrorCode parseStatus = U_ZERO_ERROR; - // TODO(units): unify with strToDouble() in unitconverter.cpp + // TODO(units): unify with strToDouble() in units_converter.cpp dqVal.setToDecNumber(val.toStringPiece(), parseStatus); if (!U_SUCCESS(parseStatus)) { // Not simple to parse, skip validating this constant's value. (We @@ -294,19 +297,21 @@ void UnitsTest::testArea() { } /** - * Trims whitespace (spaces only) off of the specified string. + * Trims whitespace off of the specified string. * @param field is two pointers pointing at the start and end of the string. * @return A StringPiece with initial and final space characters trimmed off. */ StringPiece trimField(char *(&field)[2]) { - char *start = field[0]; - while (start < field[1] && (start[0]) == ' ') { - start++; + const char *start = field[0]; + start = u_skipWhitespace(start); + if (start >= field[1]) { + start = field[1]; } - int32_t length = (int32_t)(field[1] - start); - while (length > 0 && (start[length - 1]) == ' ') { - length--; + const char *end = field[1]; + while ((start < end) && U_IS_INV_WHITESPACE(*(end - 1))) { + end--; } + int32_t length = (int32_t)(end - start); return StringPiece(start, length); } @@ -359,11 +364,13 @@ void unitsTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount, U return; } + CharString sourceIdent(x, status); MeasureUnitImpl sourceUnit = MeasureUnitImpl::forIdentifier(x, status); if (status.errIfFailureAndReset("forIdentifier(\"%.*s\")", x.length(), x.data())) { return; } + CharString targetIdent(y, status); MeasureUnitImpl targetUnit = MeasureUnitImpl::forIdentifier(y, status); if (status.errIfFailureAndReset("forIdentifier(\"%.*s\")", y.length(), y.data())) { return; @@ -378,14 +385,14 @@ void unitsTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount, U // Convertibility: auto convertibility = extractConvertibility(sourceUnit, targetUnit, *ctx->conversionRates, status); if (status.errIfFailureAndReset("extractConvertibility(<%s>, <%s>, ...)", - sourceUnit.identifier.data(), targetUnit.identifier.data())) { + sourceIdent.data(), targetIdent.data())) { return; } CharString msg; msg.append("convertible: ", status) - .append(sourceUnit.identifier.data(), status) + .append(sourceIdent.data(), status) .append(" -> ", status) - .append(targetUnit.identifier.data(), status); + .append(targetIdent.data(), status); if (status.errIfFailureAndReset("msg construction")) { return; } @@ -394,7 +401,7 @@ void unitsTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount, U // Conversion: UnitConverter converter(sourceUnit, targetUnit, *ctx->conversionRates, status); if (status.errIfFailureAndReset("constructor: UnitConverter(<%s>, <%s>, status)", - sourceUnit.identifier.data(), targetUnit.identifier.data())) { + sourceIdent.data(), targetIdent.data())) { return; } double got = converter.convert(1000); @@ -405,7 +412,7 @@ void unitsTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount, U /** * Runs data-driven unit tests for unit conversion. It looks for the test cases - * in source/test/testdata/units/unitsTest.txt, which originates in CLDR. + * in source/test/testdata/cldr/units/unitsTest.txt, which originates in CLDR. */ void UnitsTest::testConversions() { const char *filename = "unitsTest.txt"; @@ -420,7 +427,7 @@ void UnitsTest::testConversions() { } CharString path(sourceTestDataPath, errorCode); - path.appendPathPart("units", errorCode); + path.appendPathPart("cldr/units", errorCode); path.appendPathPart(filename, errorCode); ConversionRates rates(errorCode); @@ -431,6 +438,92 @@ void UnitsTest::testConversions() { } } +void UnitsTest::testComplexUnitsConverter() { + IcuTestErrorCode status(*this, "UnitsTest::testComplexUnitConversions"); + ConversionRates rates(status); + MeasureUnit input = MeasureUnit::getFoot(); + MeasureUnit output = MeasureUnit::forIdentifier("foot-and-inch", status); + MeasureUnitImpl tempInput, tempOutput; + const MeasureUnitImpl &inputImpl = MeasureUnitImpl::forMeasureUnit(input, tempInput, status); + const MeasureUnitImpl &outputImpl = MeasureUnitImpl::forMeasureUnit(output, tempOutput, status); + auto converter = ComplexUnitsConverter(inputImpl, outputImpl, rates, status); + + // Significantly less than 2.0. + MaybeStackVector measures = converter.convert(1.9999, status); + assertEquals("measures length", 2, measures.length()); + assertEquals("1.9999: measures[0] value", 1.0, measures[0]->getNumber().getDouble(status)); + assertEquals("1.9999: measures[0] unit", MeasureUnit::getFoot().getIdentifier(), + measures[0]->getUnit().getIdentifier()); + assertEqualsNear("1.9999: measures[1] value", 11.9988, measures[1]->getNumber().getDouble(status), 0.0001); + assertEquals("1.9999: measures[1] unit", MeasureUnit::getInch().getIdentifier(), + measures[1]->getUnit().getIdentifier()); + + // TODO: consider factoring out the set of tests to make this function more + // data-driven, *after* dealing appropriately with the memory leaks that can + // be demonstrated by this code. + + // TODO: reusing measures results in a leak. + // A minimal nudge under 2.0. + MaybeStackVector measures2 = converter.convert((2.0 - DBL_EPSILON), status); + assertEquals("measures length", 2, measures2.length()); + assertEquals("1 - eps: measures[0] value", 2.0, measures2[0]->getNumber().getDouble(status)); + assertEquals("1 - eps: measures[0] unit", MeasureUnit::getFoot().getIdentifier(), + measures2[0]->getUnit().getIdentifier()); + assertEquals("1 - eps: measures[1] value", 0.0, measures2[1]->getNumber().getDouble(status)); + assertEquals("1 - eps: measures[1] unit", MeasureUnit::getInch().getIdentifier(), + measures2[1]->getUnit().getIdentifier()); + + // Testing precision with meter and light-year. 1e-16 light years is + // 0.946073 meters, and double precision can provide only ~15 decimal + // digits, so we don't expect to get anything less than 1 meter. + + // An epsilon's nudge under one light-year: should give 1 ly, 0 m. + input = MeasureUnit::getLightYear(); + output = MeasureUnit::forIdentifier("light-year-and-meter", status); + // TODO: reusing tempInput and tempOutput results in a leak. + MeasureUnitImpl tempInput3, tempOutput3; + const MeasureUnitImpl &inputImpl3 = MeasureUnitImpl::forMeasureUnit(input, tempInput3, status); + const MeasureUnitImpl &outputImpl3 = MeasureUnitImpl::forMeasureUnit(output, tempOutput3, status); + // TODO: reusing converter results in a leak. + ComplexUnitsConverter converter3 = ComplexUnitsConverter(inputImpl3, outputImpl3, rates, status); + // TODO: reusing measures results in a leak. + MaybeStackVector measures3 = converter3.convert((2.0 - DBL_EPSILON), status); + assertEquals("measures length", 2, measures3.length()); + assertEquals("light-year test: measures[0] value", 2.0, measures3[0]->getNumber().getDouble(status)); + assertEquals("light-year test: measures[0] unit", MeasureUnit::getLightYear().getIdentifier(), + measures3[0]->getUnit().getIdentifier()); + assertEquals("light-year test: measures[1] value", 0.0, measures3[1]->getNumber().getDouble(status)); + assertEquals("light-year test: measures[1] unit", MeasureUnit::getMeter().getIdentifier(), + measures3[1]->getUnit().getIdentifier()); + + // 1e-15 light years is 9.46073 meters (calculated using "bc" and the CLDR + // conversion factor). With double-precision maths, we get 10.5. In this + // case, we're off by almost 1 meter. + MaybeStackVector measures4 = converter3.convert((1.0 + 1e-15), status); + assertEquals("measures length", 2, measures4.length()); + assertEquals("light-year test: measures[0] value", 1.0, measures4[0]->getNumber().getDouble(status)); + assertEquals("light-year test: measures[0] unit", MeasureUnit::getLightYear().getIdentifier(), + measures4[0]->getUnit().getIdentifier()); + assertEqualsNear("light-year test: measures[1] value", 10, + measures4[1]->getNumber().getDouble(status), 1); + assertEquals("light-year test: measures[1] unit", MeasureUnit::getMeter().getIdentifier(), + measures4[1]->getUnit().getIdentifier()); + + // 2e-16 light years is 1.892146 meters. We consider this in the noise, and + // thus expect a 0. (This test fails when 2e-16 is increased to 4e-16.) + MaybeStackVector measures5 = converter3.convert((1.0 + 2e-16), status); + assertEquals("measures length", 2, measures5.length()); + assertEquals("light-year test: measures[0] value", 1.0, measures5[0]->getNumber().getDouble(status)); + assertEquals("light-year test: measures[0] unit", MeasureUnit::getLightYear().getIdentifier(), + measures5[0]->getUnit().getIdentifier()); + assertEquals("light-year test: measures[1] value", 0.0, + measures5[1]->getNumber().getDouble(status)); + assertEquals("light-year test: measures[1] unit", MeasureUnit::getMeter().getIdentifier(), + measures5[1]->getUnit().getIdentifier()); + + // TODO(icu-units#63): test negative numbers! +} + void UnitsTest::testComplexUnitConverterSorting() { IcuTestErrorCode status(*this, "UnitsTest::testComplexUnitConverterSorting"); @@ -442,11 +535,11 @@ void UnitsTest::testComplexUnitConverterSorting() { auto measures = complexConverter.convert(10.0, status); U_ASSERT(measures.length() == 2); - assertEquals("Sorted Data", "foot", measures[0]->getUnit().getIdentifier()); - assertEquals("Sorted Data", "inch", measures[1]->getUnit().getIdentifier()); + assertEquals("inch-and-foot unit 0", "inch", measures[0]->getUnit().getIdentifier()); + assertEquals("inch-and-foot unit 1", "foot", measures[1]->getUnit().getIdentifier()); - assertEqualsNear("Sorted Data", 32, measures[0]->getNumber().getInt64(), 0.00001); - assertEqualsNear("Sorted Data", 9.7008, measures[1]->getNumber().getDouble(), 0.0001); + assertEqualsNear("inch-and-foot value 0", 9.7008, measures[0]->getNumber().getDouble(), 0.0001); + assertEqualsNear("inch-and-foot value 1", 32, measures[1]->getNumber().getInt64(), 0.00001); } /** @@ -750,7 +843,9 @@ void parsePreferencesTests(const char *filename, char delimiter, char *fields[][ } /** - * Runs data-driven unit tests for unit preferences. + * Runs data-driven unit tests for unit preferences. It looks for the test cases + * in source/test/testdata/cldr/units/unitPreferencesTest.txt, which originates + * in CLDR. */ void UnitsTest::testPreferences() { const char *filename = "unitPreferencesTest.txt"; @@ -765,7 +860,7 @@ void UnitsTest::testPreferences() { } CharString path(sourceTestDataPath, errorCode); - path.appendPathPart("units", errorCode); + path.appendPathPart("cldr/units", errorCode); path.appendPathPart(filename, errorCode); parsePreferencesTests(path.data(), ';', fields, maxFields, unitPreferencesTestDataLineFn, this, diff --git a/icu4c/source/test/testdata/units/unitPreferencesTest.txt b/icu4c/source/test/testdata/units/unitPreferencesTest.txt deleted file mode 100644 index e67b87b6669..00000000000 --- a/icu4c/source/test/testdata/units/unitPreferencesTest.txt +++ /dev/null @@ -1,453 +0,0 @@ -# This file is a copy of common/testData/units/unitPreferencesTest.txt from CLDR. -# WIP/TODO(hugovdm): determine a good update procedure and document it. -# -# Test data for unit preferences -# Copyright © 1991-2020 Unicode, Inc. -# For terms of use, see http://www.unicode.org/copyright.html -# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. -# CLDR data files are interpreted according to the LDML specification (http://unicode.org/reports/tr35/) -# -# Format: -# Quantity; Usage; Region; Input (r); Input (d); Input Unit; Output (r); Output (d); Output Unit -# -# Use: Convert the Input amount & unit according to the Usage and Region. -# The result should match the Output amount and unit. -# Both rational (r) and double64 (d) forms of the input and output amounts are supplied so that implementations -# have two options for testing based on the precision in their implementations. For example: -# 3429 / 12500; 0.27432; meter; -# The Output amount and Unit are repeated for mixed units. In such a case, only the smallest unit will have -# both a rational and decimal amount; the others will have a single integer value, such as: -# length; person-height; CA; 3429 / 12500; 0.27432; meter; 2; foot; 54 / 5; 10.8; inch -# The input and output units are unit identifers; in particular, the output does not have further processing: -# • no localization -# • no adjustment for pluralization -# • no formatted with the skeleton -# • no suppression of zero values (for secondary -and- units such as pound in stone-and-pound) -# -# Generation: Set GENERATE_TESTS in TestUnits.java, and look at TestUnitPreferences results. - -area; default; 001; 1100000; 1100000.0; square-meter; 11 / 10; 1.1; square-kilometer -area; default; 001; 1000000; 1000000.0; square-meter; 1; 1.0; square-kilometer -area; default; 001; 900000; 900000.0; square-meter; 90; 90.0; hectare -area; default; 001; 10000; 10000.0; square-meter; 1; 1.0; hectare -area; default; 001; 9000; 9000.0; square-meter; 9000; 9000.0; square-meter -area; default; 001; 1; 1.0; square-meter; 1; 1.0; square-meter -area; default; 001; 9 / 10; 0.9; square-meter; 9000; 9000.0; square-centimeter -area; default; 001; 1 / 10000; 1.0E-4; square-meter; 1; 1.0; square-centimeter -area; default; 001; 9 / 100000; 9.0E-5; square-meter; 9 / 10; 0.9; square-centimeter - -area; default; GB; 222577103232 / 78125; 2848986.9213696; square-meter; 11 / 10; 1.1; square-mile -area; default; GB; 40468564224 / 15625; 2589988.110336; square-meter; 1; 1.0; square-mile -area; default; GB; 182108539008 / 78125; 2330989.2993024; square-meter; 576; 576.0; acre -area; default; GB; 316160658 / 78125; 4046.8564224; square-meter; 1; 1.0; acre -area; default; GB; 1422722961 / 390625; 3642.17078016; square-meter; 39204; 39204.0; square-foot -area; default; GB; 145161 / 1562500; 0.09290304; square-meter; 1; 1.0; square-foot -area; default; GB; 1306449 / 15625000; 0.083612736; square-meter; 648 / 5; 129.6; square-inch -area; default; GB; 16129 / 25000000; 6.4516E-4; square-meter; 1; 1.0; square-inch -area; default; GB; 145161 / 250000000; 5.80644E-4; square-meter; 9 / 10; 0.9; square-inch - -area; geograph; 001; 1100000; 1100000.0; square-meter; 11 / 10; 1.1; square-kilometer -area; geograph; 001; 1000000; 1000000.0; square-meter; 1; 1.0; square-kilometer -area; geograph; 001; 900000; 900000.0; square-meter; 9 / 10; 0.9; square-kilometer - -area; geograph; GB; 222577103232 / 78125; 2848986.9213696; square-meter; 11 / 10; 1.1; square-mile -area; geograph; GB; 40468564224 / 15625; 2589988.110336; square-meter; 1; 1.0; square-mile -area; geograph; GB; 182108539008 / 78125; 2330989.2993024; square-meter; 9 / 10; 0.9; square-mile - -area; land; 001; 11000; 11000.0; square-meter; 11 / 10; 1.1; hectare -area; land; 001; 10000; 10000.0; square-meter; 1; 1.0; hectare -area; land; 001; 9000; 9000.0; square-meter; 9 / 10; 0.9; hectare - -area; land; GB; 1738883619 / 390625; 4451.54206464; square-meter; 11 / 10; 1.1; acre -area; land; GB; 316160658 / 78125; 4046.8564224; square-meter; 1; 1.0; acre -area; land; GB; 1422722961 / 390625; 3642.17078016; square-meter; 9 / 10; 0.9; acre - -concentration; blood-glucose; AG; 662435483600000000000000; 6.624354836E23; item-per-cubic-meter; 11 / 10; 1.1; millimole-per-liter -concentration; blood-glucose; AG; 602214076000000000000000; 6.02214076E23; item-per-cubic-meter; 1; 1.0; millimole-per-liter -concentration; blood-glucose; AG; 541992668400000000000000; 5.419926684E23; item-per-cubic-meter; 9 / 10; 0.9; millimole-per-liter - -concentration; default; 001; 11 / 10; 1.1; item-per-cubic-meter; 11 / 10; 1.1; item-per-cubic-meter -concentration; default; 001; 1; 1.0; item-per-cubic-meter; 1; 1.0; item-per-cubic-meter -concentration; default; 001; 9 / 10; 0.9; item-per-cubic-meter; 9 / 10; 0.9; item-per-cubic-meter - -consumption; default; 001; 11 / 1000000000; 1.1E-8; cubic-meter-per-meter; 11 / 10; 1.1; liter-per-100-kilometer -consumption; default; 001; 1 / 100000000; 1.0E-8; cubic-meter-per-meter; 1; 1.0; liter-per-100-kilometer -consumption; default; 001; 9 / 1000000000; 9.0E-9; cubic-meter-per-meter; 9 / 10; 0.9; liter-per-100-kilometer - -consumption; vehicle-fuel; 001; 11 / 1000000000; 1.1E-8; cubic-meter-per-meter; 11 / 10; 1.1; liter-per-100-kilometer -consumption; vehicle-fuel; 001; 1 / 100000000; 1.0E-8; cubic-meter-per-meter; 1; 1.0; liter-per-100-kilometer -consumption; vehicle-fuel; 001; 9 / 1000000000; 9.0E-9; cubic-meter-per-meter; 9 / 10; 0.9; liter-per-100-kilometer - -consumption; vehicle-fuel; BR; 11 / 10000000; 1.1E-6; cubic-meter-per-meter; 11 / 10; 1.1; liter-per-kilometer -consumption; vehicle-fuel; BR; 1 / 1000000; 1.0E-6; cubic-meter-per-meter; 1; 1.0; liter-per-kilometer -consumption; vehicle-fuel; BR; 9 / 10000000; 9.0E-7; cubic-meter-per-meter; 9 / 10; 0.9; liter-per-kilometer - -consumption-inverse; default; 001; 110000000; 1.1E8; meter-per-cubic-meter; 11 / 10; 1.1; kilometer-per-centiliter -consumption-inverse; default; 001; 100000000; 1.0E8; meter-per-cubic-meter; 1; 1.0; kilometer-per-centiliter -consumption-inverse; default; 001; 90000000; 9.0E7; meter-per-cubic-meter; 9 / 10; 0.9; kilometer-per-centiliter - -consumption-inverse; vehicle-fuel; 001; 110000000; 1.1E8; meter-per-cubic-meter; 11 / 10; 1.1; kilometer-per-centiliter -consumption-inverse; vehicle-fuel; 001; 100000000; 1.0E8; meter-per-cubic-meter; 1; 1.0; kilometer-per-centiliter -consumption-inverse; vehicle-fuel; 001; 90000000; 9.0E7; meter-per-cubic-meter; 9 / 10; 0.9; kilometer-per-centiliter - -consumption-inverse; vehicle-fuel; US; 52800000000 / 112903; 467658.0781732992; meter-per-cubic-meter; 11 / 10; 1.1; mile-per-gallon -consumption-inverse; vehicle-fuel; US; 48000000000 / 112903; 425143.707430272; meter-per-cubic-meter; 1; 1.0; mile-per-gallon -consumption-inverse; vehicle-fuel; US; 43200000000 / 112903; 382629.3366872448; meter-per-cubic-meter; 9 / 10; 0.9; mile-per-gallon - -consumption-inverse; vehicle-fuel; CA; 177027840000 / 454609; 389406.8089281118; meter-per-cubic-meter; 11 / 10; 1.1; mile-per-gallon-imperial -consumption-inverse; vehicle-fuel; CA; 160934400000 / 454609; 354006.1899346471; meter-per-cubic-meter; 1; 1.0; mile-per-gallon-imperial -consumption-inverse; vehicle-fuel; CA; 144840960000 / 454609; 318605.5709411824; meter-per-cubic-meter; 9 / 10; 0.9; mile-per-gallon-imperial - -duration; default; 001; 95040; 95040.0; second; 11 / 10; 1.1; day -duration; default; 001; 86400; 86400.0; second; 1; 1.0; day -duration; default; 001; 77760; 77760.0; second; 108 / 5; 21.6; hour -duration; default; 001; 3600; 3600.0; second; 1; 1.0; hour -duration; default; 001; 3240; 3240.0; second; 54; 54.0; minute -duration; default; 001; 60; 60.0; second; 1; 1.0; minute -duration; default; 001; 54; 54.0; second; 54; 54.0; second -duration; default; 001; 1; 1.0; second; 1; 1.0; second -duration; default; 001; 9 / 10; 0.9; second; 900; 900.0; millisecond -duration; default; 001; 1 / 1000; 0.001; second; 1; 1.0; millisecond -duration; default; 001; 9 / 10000; 9.0E-4; second; 900; 900.0; microsecond -duration; default; 001; 1 / 1000000; 1.0E-6; second; 1; 1.0; microsecond -duration; default; 001; 9 / 10000000; 9.0E-7; second; 900; 900.0; nanosecond -duration; default; 001; 1 / 1000000000; 1.0E-9; second; 1; 1.0; nanosecond -duration; default; 001; 9 / 10000000000; 9.0E-10; second; 9 / 10; 0.9; nanosecond - -duration; media; 001; 66; 66.0; second; 1; minute; 6; 6.0; second -duration; media; 001; 60; 60.0; second; 1; minute; 0; 0.0; second -duration; media; 001; 54; 54.0; second; 54; 54.0; second -duration; media; 001; 1; 1.0; second; 1; 1.0; second -duration; media; 001; 9 / 10; 0.9; second; 9 / 10; 0.9; second - -energy; default; 001; 3960000; 3960000.0; kilogram-square-meter-per-square-second; 11 / 10; 1.1; kilowatt-hour -energy; default; 001; 3600000; 3600000.0; kilogram-square-meter-per-square-second; 1; 1.0; kilowatt-hour -energy; default; 001; 3240000; 3240000.0; kilogram-square-meter-per-square-second; 9 / 10; 0.9; kilowatt-hour - -energy; food; US; 23012 / 5; 4602.4; kilogram-square-meter-per-square-second; 11 / 10; 1.1; foodcalorie -energy; food; US; 4184; 4184.0; kilogram-square-meter-per-square-second; 1; 1.0; foodcalorie -energy; food; US; 18828 / 5; 3765.6; kilogram-square-meter-per-square-second; 9 / 10; 0.9; foodcalorie - -energy; food; 001; 23012 / 5; 4602.4; kilogram-square-meter-per-square-second; 11 / 10; 1.1; kilocalorie -energy; food; 001; 4184; 4184.0; kilogram-square-meter-per-square-second; 1; 1.0; kilocalorie -energy; food; 001; 18828 / 5; 3765.6; kilogram-square-meter-per-square-second; 9 / 10; 0.9; kilocalorie - -length; default; 001; 1100; 1100.0; meter; 11 / 10; 1.1; kilometer -length; default; 001; 1000; 1000.0; meter; 1; 1.0; kilometer -length; default; 001; 900; 900.0; meter; 900; 900.0; meter -length; default; 001; 1; 1.0; meter; 1; 1.0; meter -length; default; 001; 9 / 10; 0.9; meter; 90; 90.0; centimeter -length; default; 001; 1 / 100; 0.01; meter; 1; 1.0; centimeter -length; default; 001; 9 / 1000; 0.009; meter; 9 / 10; 0.9; centimeter - -length; default; GB; 1106424 / 625; 1770.2784; meter; 11 / 10; 1.1; mile -length; default; GB; 201168 / 125; 1609.344; meter; 1; 1.0; mile -length; default; GB; 905256 / 625; 1448.4096; meter; 4752; 4752.0; foot -length; default; GB; 381 / 1250; 0.3048; meter; 1; 1.0; foot -length; default; GB; 3429 / 12500; 0.27432; meter; 54 / 5; 10.8; inch -length; default; GB; 127 / 5000; 0.0254; meter; 1; 1.0; inch -length; default; GB; 1143 / 50000; 0.02286; meter; 9 / 10; 0.9; inch - -length; person; 001; 11 / 1000; 0.011; meter; 11 / 10; 1.1; centimeter -length; person; 001; 1 / 100; 0.01; meter; 1; 1.0; centimeter -length; person; 001; 9 / 1000; 0.009; meter; 9 / 10; 0.9; centimeter - -length; person; CA; 1397 / 50000; 0.02794; meter; 11 / 10; 1.1; inch -length; person; CA; 127 / 5000; 0.0254; meter; 1; 1.0; inch -length; person; CA; 1143 / 50000; 0.02286; meter; 9 / 10; 0.9; inch - -length; person-height; 001; 11 / 1000; 0.011; meter; 11 / 10; 1.1; centimeter -length; person-height; 001; 1 / 100; 0.01; meter; 1; 1.0; centimeter -length; person-height; 001; 9 / 1000; 0.009; meter; 9 / 10; 0.9; centimeter - -length; person-height; CA; 11811 / 12500; 0.94488; meter; 3; foot; 6 / 5; 1.2; inch -length; person-height; CA; 1143 / 1250; 0.9144; meter; 3; foot; 0; 0.0; inch -length; person-height; CA; 11049 / 12500; 0.88392; meter; 174 / 5; 34.8; inch -length; person-height; CA; 127 / 5000; 0.0254; meter; 1; 1.0; inch -length; person-height; CA; 1143 / 50000; 0.02286; meter; 9 / 10; 0.9; inch - -length; person-height; AT; 11 / 10; 1.1; meter; 1; meter; 10; 10.0; centimeter -length; person-height; AT; 1; 1.0; meter; 1; meter; 0; 0.0; centimeter -length; person-height; AT; 9 / 10; 0.9; meter; 0; meter; 90; 90.0; centimeter - -length; rainfall; BR; 11 / 1000; 0.011; meter; 11 / 10; 1.1; centimeter -length; rainfall; BR; 1 / 100; 0.01; meter; 1; 1.0; centimeter -length; rainfall; BR; 9 / 1000; 0.009; meter; 9 / 10; 0.9; centimeter - -length; rainfall; US; 1397 / 50000; 0.02794; meter; 11 / 10; 1.1; inch -length; rainfall; US; 127 / 5000; 0.0254; meter; 1; 1.0; inch -length; rainfall; US; 1143 / 50000; 0.02286; meter; 9 / 10; 0.9; inch - -length; rainfall; 001; 11 / 10000; 0.0011; meter; 11 / 10; 1.1; millimeter -length; rainfall; 001; 1 / 1000; 0.001; meter; 1; 1.0; millimeter -length; rainfall; 001; 9 / 10000; 9.0E-4; meter; 9 / 10; 0.9; millimeter - -length; road; 001; 1000; 1000.0; meter; 1; 1.0; kilometer -length; road; 001; 900; 900.0; meter; 9 / 10; 0.9; kilometer -length; road; 001; 800; 800.0; meter; 800; 800.0; meter -length; road; 001; 300; 300.0; meter; 300; 300.0; meter -length; road; 001; 2999 / 10; 299.9; meter; 2999 / 10; 299.9; meter -length; road; 001; 1; 1.0; meter; 1; 1.0; meter -length; road; 001; 9 / 10; 0.9; meter; 9 / 10; 0.9; meter - -length; road; US; 603504 / 625; 965.6064; meter; 3 / 5; 0.6; mile -length; road; US; 100584 / 125; 804.672; meter; 1 / 2; 0.5; mile -length; road; US; 402336 / 625; 643.7376; meter; 2112; 2112.0; foot -length; road; US; 762 / 25; 30.48; meter; 100; 100.0; foot -length; road; US; 380619 / 12500; 30.44952; meter; 999 / 10; 99.9; foot -length; road; US; 381 / 1250; 0.3048; meter; 1; 1.0; foot -length; road; US; 3429 / 12500; 0.27432; meter; 9 / 10; 0.9; foot - -length; road; GB; 603504 / 625; 965.6064; meter; 3 / 5; 0.6; mile -length; road; GB; 100584 / 125; 804.672; meter; 1 / 2; 0.5; mile -length; road; GB; 402336 / 625; 643.7376; meter; 704; 704.0; yard -length; road; GB; 2286 / 25; 91.44; meter; 100; 100.0; yard -length; road; GB; 1141857 / 12500; 91.34856; meter; 999 / 10; 99.9; yard -length; road; GB; 1143 / 1250; 0.9144; meter; 1; 1.0; yard -length; road; GB; 10287 / 12500; 0.82296; meter; 9 / 10; 0.9; yard - -length; road; SE; 11000; 11000.0; meter; 11 / 10; 1.1; mile-scandinavian -length; road; SE; 10000; 10000.0; meter; 1; 1.0; mile-scandinavian -length; road; SE; 9000; 9000.0; meter; 9; 9.0; kilometer -length; road; SE; 1000; 1000.0; meter; 1; 1.0; kilometer -length; road; SE; 900; 900.0; meter; 900; 900.0; meter -length; road; SE; 300; 300.0; meter; 300; 300.0; meter -length; road; SE; 2999 / 10; 299.9; meter; 2999 / 10; 299.9; meter -length; road; SE; 1; 1.0; meter; 1; 1.0; meter -length; road; SE; 9 / 10; 0.9; meter; 9 / 10; 0.9; meter - -length; snowfall; 001; 11 / 1000; 0.011; meter; 11 / 10; 1.1; centimeter -length; snowfall; 001; 1 / 100; 0.01; meter; 1; 1.0; centimeter -length; snowfall; 001; 9 / 1000; 0.009; meter; 9 / 10; 0.9; centimeter - -length; snowfall; US; 1397 / 50000; 0.02794; meter; 11 / 10; 1.1; inch -length; snowfall; US; 127 / 5000; 0.0254; meter; 1; 1.0; inch -length; snowfall; US; 1143 / 50000; 0.02286; meter; 9 / 10; 0.9; inch - -length; vehicle; GB; 4191 / 12500; 0.33528; meter; 1; foot; 6 / 5; 1.2; inch -length; vehicle; GB; 381 / 1250; 0.3048; meter; 1; foot; 0; 0.0; inch -length; vehicle; GB; 3429 / 12500; 0.27432; meter; 0; foot; 54 / 5; 10.8; inch - -length; vehicle; 001; 11 / 10; 1.1; meter; 11 / 10; 1.1; meter -length; vehicle; 001; 1; 1.0; meter; 1; 1.0; meter -length; vehicle; 001; 9 / 10; 0.9; meter; 9 / 10; 0.9; meter - -length; vehicle; MX; 11 / 10; 1.1; meter; 1; meter; 10; 10.0; centimeter -length; vehicle; MX; 1; 1.0; meter; 1; meter; 0; 0.0; centimeter -length; vehicle; MX; 9 / 10; 0.9; meter; 0; meter; 90; 90.0; centimeter - -length; visiblty; 001; 200; 200.0; meter; 1 / 5; 0.2; kilometer -length; visiblty; 001; 100; 100.0; meter; 1 / 10; 0.1; kilometer -length; visiblty; 001; 1; 1.0; meter; 1; 1.0; meter -length; visiblty; 001; 9 / 10; 0.9; meter; 9 / 10; 0.9; meter -length; visiblty; 001; 0; 0.0; meter; 0; 0.0; meter - -length; visiblty; DE; 11 / 10; 1.1; meter; 11 / 10; 1.1; meter -length; visiblty; DE; 1; 1.0; meter; 1; 1.0; meter -length; visiblty; DE; 9 / 10; 0.9; meter; 9 / 10; 0.9; meter - -length; visiblty; GB; 1106424 / 625; 1770.2784; meter; 11 / 10; 1.1; mile -length; visiblty; GB; 201168 / 125; 1609.344; meter; 1; 1.0; mile -length; visiblty; GB; 905256 / 625; 1448.4096; meter; 4752; 4752.0; foot -length; visiblty; GB; 381 / 1250; 0.3048; meter; 1; 1.0; foot -length; visiblty; GB; 3429 / 12500; 0.27432; meter; 9 / 10; 0.9; foot - -mass; default; 001; 1100; 1100.0; kilogram; 11 / 10; 1.1; metric-ton -mass; default; 001; 1000; 1000.0; kilogram; 1; 1.0; metric-ton -mass; default; 001; 900; 900.0; kilogram; 900; 900.0; kilogram -mass; default; 001; 1; 1.0; kilogram; 1; 1.0; kilogram -mass; default; 001; 9 / 10; 0.9; kilogram; 900; 900.0; gram -mass; default; 001; 1 / 1000; 0.001; kilogram; 1; 1.0; gram -mass; default; 001; 9 / 10000; 9.0E-4; kilogram; 900; 900.0; milligram -mass; default; 001; 1 / 1000000; 1.0E-6; kilogram; 1; 1.0; milligram -mass; default; 001; 9 / 10000000; 9.0E-7; kilogram; 900; 900.0; microgram -mass; default; 001; 1 / 1000000000; 1.0E-9; kilogram; 1; 1.0; microgram -mass; default; 001; 9 / 10000000000; 9.0E-10; kilogram; 9 / 10; 0.9; microgram - -mass; default; GB; 498951607 / 500000; 997.903214; kilogram; 11 / 10; 1.1; ton -mass; default; GB; 45359237 / 50000; 907.18474; kilogram; 1; 1.0; ton -mass; default; GB; 408233133 / 500000; 816.466266; kilogram; 1800; 1800.0; pound -mass; default; GB; 45359237 / 100000000; 0.45359237; kilogram; 1; 1.0; pound -mass; default; GB; 408233133 / 1000000000; 0.408233133; kilogram; 72 / 5; 14.4; ounce -mass; default; GB; 45359237 / 1600000000; 0.028349523125; kilogram; 1; 1.0; ounce -mass; default; GB; 408233133 / 16000000000; 0.0255145708125; kilogram; 9 / 10; 0.9; ounce - -mass; person; 001; 11 / 10; 1.1; kilogram; 11 / 10; 1.1; kilogram -mass; person; 001; 1; 1.0; kilogram; 1; 1.0; kilogram -mass; person; 001; 9 / 10; 0.9; kilogram; 900; 900.0; gram -mass; person; 001; 1 / 1000; 0.001; kilogram; 1; 1.0; gram -mass; person; 001; 9 / 10000; 9.0E-4; kilogram; 9 / 10; 0.9; gram - -mass; person; DZ; 11 / 10; 1.1; kilogram; 1; kilogram; 100; 100.0; gram -mass; person; DZ; 1; 1.0; kilogram; 1; kilogram; 0; 0.0; gram -mass; person; DZ; 9 / 10; 0.9; kilogram; 0; kilogram; 900; 900.0; gram - -mass; person; US; 498951607 / 1000000000; 0.498951607; kilogram; 11 / 10; 1.1; pound -mass; person; US; 45359237 / 100000000; 0.45359237; kilogram; 1; 1.0; pound -mass; person; US; 408233133 / 1000000000; 0.408233133; kilogram; 0; pound; 72 / 5; 14.4; ounce - -mass; person; GB; 3492661249 / 500000000; 6.985322498; kilogram; 1; stone; 7 / 5; 1.4; pound -mass; person; GB; 317514659 / 50000000; 6.35029318; kilogram; 1; stone; 0; 0.0; pound -mass; person; GB; 2857631931 / 500000000; 5.715263862; kilogram; 12; pound; 48 / 5; 9.6; ounce -mass; person; GB; 45359237 / 100000000; 0.45359237; kilogram; 1; pound; 0; 0.0; ounce -mass; person; GB; 408233133 / 1000000000; 0.408233133; kilogram; 0; pound; 72 / 5; 14.4; ounce - -mass; person; HK; 498951607 / 1000000000; 0.498951607; kilogram; 1; pound; 8 / 5; 1.6; ounce -mass; person; HK; 45359237 / 100000000; 0.45359237; kilogram; 1; pound; 0; 0.0; ounce -mass; person; HK; 408233133 / 1000000000; 0.408233133; kilogram; 0; pound; 72 / 5; 14.4; ounce - -mass-density; blood-glucose; 001; 11 / 1000; 0.011; kilogram-per-cubic-meter; 11 / 10; 1.1; milligram-per-deciliter -mass-density; blood-glucose; 001; 1 / 100; 0.01; kilogram-per-cubic-meter; 1; 1.0; milligram-per-deciliter -mass-density; blood-glucose; 001; 9 / 1000; 0.009; kilogram-per-cubic-meter; 9 / 10; 0.9; milligram-per-deciliter - -mass-density; default; 001; 11 / 10; 1.1; kilogram-per-cubic-meter; 11 / 10; 1.1; kilogram-per-cubic-meter -mass-density; default; 001; 1; 1.0; kilogram-per-cubic-meter; 1; 1.0; kilogram-per-cubic-meter -mass-density; default; 001; 9 / 10; 0.9; kilogram-per-cubic-meter; 9 / 10; 0.9; kilogram-per-cubic-meter - -power; default; 001; 1100000000; 1.1E9; kilogram-square-meter-per-cubic-second; 11 / 10; 1.1; gigawatt -power; default; 001; 1000000000; 1.0E9; kilogram-square-meter-per-cubic-second; 1; 1.0; gigawatt -power; default; 001; 900000000; 9.0E8; kilogram-square-meter-per-cubic-second; 900; 900.0; megawatt -power; default; 001; 1000000; 1000000.0; kilogram-square-meter-per-cubic-second; 1; 1.0; megawatt -power; default; 001; 900000; 900000.0; kilogram-square-meter-per-cubic-second; 900; 900.0; kilowatt -power; default; 001; 1000; 1000.0; kilogram-square-meter-per-cubic-second; 1; 1.0; kilowatt -power; default; 001; 900; 900.0; kilogram-square-meter-per-cubic-second; 900; 900.0; watt -power; default; 001; 1; 1.0; kilogram-square-meter-per-cubic-second; 1; 1.0; watt -power; default; 001; 9 / 10; 0.9; kilogram-square-meter-per-cubic-second; 900; 900.0; milliwatt -power; default; 001; 1 / 1000; 0.001; kilogram-square-meter-per-cubic-second; 1; 1.0; milliwatt -power; default; 001; 9 / 10000; 9.0E-4; kilogram-square-meter-per-cubic-second; 9 / 10; 0.9; milliwatt - -power; engine; 001; 1100; 1100.0; kilogram-square-meter-per-cubic-second; 11 / 10; 1.1; kilowatt -power; engine; 001; 1000; 1000.0; kilogram-square-meter-per-cubic-second; 1; 1.0; kilowatt -power; engine; 001; 900; 900.0; kilogram-square-meter-per-cubic-second; 9 / 10; 0.9; kilowatt - -power; engine; GB; 410134929370248621 / 500000000000000; 820.2698587404972; kilogram-square-meter-per-cubic-second; 11 / 10; 1.1; horsepower -power; engine; GB; 37284993579113511 / 50000000000000; 745.6998715822702; kilogram-square-meter-per-cubic-second; 1; 1.0; horsepower -power; engine; GB; 335564942212021599 / 500000000000000; 671.1298844240432; kilogram-square-meter-per-cubic-second; 9 / 10; 0.9; horsepower - -pressure; baromtrc; 001; 110; 110.0; kilogram-per-meter-square-second; 11 / 10; 1.1; hectopascal -pressure; baromtrc; 001; 100; 100.0; kilogram-per-meter-square-second; 1; 1.0; hectopascal -pressure; baromtrc; 001; 90; 90.0; kilogram-per-meter-square-second; 9 / 10; 0.9; hectopascal - -pressure; baromtrc; IN; 37250275043751 / 10000000000; 3725.0275043751; kilogram-per-meter-square-second; 11 / 10; 1.1; inch-ofhg -pressure; baromtrc; IN; 3386388640341 / 1000000000; 3386.388640341; kilogram-per-meter-square-second; 1; 1.0; inch-ofhg -pressure; baromtrc; IN; 30477497763069 / 10000000000; 3047.7497763069; kilogram-per-meter-square-second; 9 / 10; 0.9; inch-ofhg - -pressure; baromtrc; BR; 110; 110.0; kilogram-per-meter-square-second; 11 / 10; 1.1; millibar -pressure; baromtrc; BR; 100; 100.0; kilogram-per-meter-square-second; 1; 1.0; millibar -pressure; baromtrc; BR; 90; 90.0; kilogram-per-meter-square-second; 9 / 10; 0.9; millibar - -pressure; baromtrc; MX; 293309252313 / 2000000000; 146.6546261565; kilogram-per-meter-square-second; 11 / 10; 1.1; millimeter-ofhg -pressure; baromtrc; MX; 26664477483 / 200000000; 133.322387415; kilogram-per-meter-square-second; 1; 1.0; millimeter-ofhg -pressure; baromtrc; MX; 239980297347 / 2000000000; 119.9901486735; kilogram-per-meter-square-second; 9 / 10; 0.9; millimeter-ofhg - -pressure; default; 001; 1100000; 1100000.0; kilogram-per-meter-square-second; 11 / 10; 1.1; megapascal -pressure; default; 001; 1000000; 1000000.0; kilogram-per-meter-square-second; 1; 1.0; megapascal -pressure; default; 001; 900000; 900000.0; kilogram-per-meter-square-second; 900000; 900000.0; pascal -pressure; default; 001; 1; 1.0; kilogram-per-meter-square-second; 1; 1.0; pascal -pressure; default; 001; 9 / 10; 0.9; kilogram-per-meter-square-second; 9 / 10; 0.9; pascal - -pressure; default; GB; 97860875535731 / 12903200000; 7584.233022485197; kilogram-per-meter-square-second; 11 / 10; 1.1; pound-force-per-square-inch -pressure; default; GB; 8896443230521 / 1290320000; 6894.757293168361; kilogram-per-meter-square-second; 1; 1.0; pound-force-per-square-inch -pressure; default; GB; 80067989074689 / 12903200000; 6205.281563851525; kilogram-per-meter-square-second; 9 / 10; 0.9; pound-force-per-square-inch - -speed; default; 001; 11 / 36; 0.3055555555555556; meter-per-second; 11 / 10; 1.1; kilometer-per-hour -speed; default; 001; 5 / 18; 0.2777777777777778; meter-per-second; 1; 1.0; kilometer-per-hour -speed; default; 001; 1 / 4; 0.25; meter-per-second; 9 / 10; 0.9; kilometer-per-hour - -speed; default; GB; 15367 / 31250; 0.491744; meter-per-second; 11 / 10; 1.1; mile-per-hour -speed; default; GB; 1397 / 3125; 0.44704; meter-per-second; 1; 1.0; mile-per-hour -speed; default; GB; 12573 / 31250; 0.402336; meter-per-second; 9 / 10; 0.9; mile-per-hour - -speed; wind; 001; 11 / 36; 0.3055555555555556; meter-per-second; 11 / 10; 1.1; kilometer-per-hour -speed; wind; 001; 5 / 18; 0.2777777777777778; meter-per-second; 1; 1.0; kilometer-per-hour -speed; wind; 001; 1 / 4; 0.25; meter-per-second; 9 / 10; 0.9; kilometer-per-hour - -speed; wind; FI; 11 / 10; 1.1; meter-per-second; 11 / 10; 1.1; meter-per-second -speed; wind; FI; 1; 1.0; meter-per-second; 1; 1.0; meter-per-second -speed; wind; FI; 9 / 10; 0.9; meter-per-second; 9 / 10; 0.9; meter-per-second - -speed; wind; US; 15367 / 31250; 0.491744; meter-per-second; 11 / 10; 1.1; mile-per-hour -speed; wind; US; 1397 / 3125; 0.44704; meter-per-second; 1; 1.0; mile-per-hour -speed; wind; US; 12573 / 31250; 0.402336; meter-per-second; 9 / 10; 0.9; mile-per-hour - -temperature; default; 001; 1097 / 4; 274.25; kelvin; 11 / 10; 1.1; celsius -temperature; default; 001; 5483 / 20; 274.15; kelvin; 1; 1.0; celsius -temperature; default; 001; 5481 / 20; 274.05; kelvin; 9 / 10; 0.9; celsius - -temperature; default; US; 15359 / 60; 255.9833333333333; kelvin; 11 / 10; 1.1; fahrenheit -temperature; default; US; 46067 / 180; 255.9277777777778; kelvin; 1; 1.0; fahrenheit -temperature; default; US; 46057 / 180; 255.8722222222222; kelvin; 9 / 10; 0.9; fahrenheit - -temperature; weather; 001; 1097 / 4; 274.25; kelvin; 11 / 10; 1.1; celsius -temperature; weather; 001; 5483 / 20; 274.15; kelvin; 1; 1.0; celsius -temperature; weather; 001; 5481 / 20; 274.05; kelvin; 9 / 10; 0.9; celsius - -temperature; weather; BS; 15359 / 60; 255.9833333333333; kelvin; 11 / 10; 1.1; fahrenheit -temperature; weather; BS; 46067 / 180; 255.9277777777778; kelvin; 1; 1.0; fahrenheit -temperature; weather; BS; 46057 / 180; 255.8722222222222; kelvin; 9 / 10; 0.9; fahrenheit - -volume; default; 001; 11 / 10; 1.1; cubic-meter; 11 / 10; 1.1; cubic-meter -volume; default; 001; 1; 1.0; cubic-meter; 1; 1.0; cubic-meter -volume; default; 001; 9 / 10; 0.9; cubic-meter; 900000; 900000.0; cubic-centimeter -volume; default; 001; 1 / 1000000; 1.0E-6; cubic-meter; 1; 1.0; cubic-centimeter -volume; default; 001; 9 / 10000000; 9.0E-7; cubic-meter; 9 / 10; 0.9; cubic-centimeter - -volume; default; GB; 608369751 / 19531250000; 0.0311485312512; cubic-meter; 11 / 10; 1.1; cubic-foot -volume; default; GB; 55306341 / 1953125000; 0.028316846592; cubic-meter; 1; 1.0; cubic-foot -volume; default; GB; 497757069 / 19531250000; 0.0254851619328; cubic-meter; 7776 / 5; 1555.2; cubic-inch -volume; default; GB; 2048383 / 125000000000; 1.6387064E-5; cubic-meter; 1; 1.0; cubic-inch -volume; default; GB; 18435447 / 1250000000000; 1.47483576E-5; cubic-meter; 9 / 10; 0.9; cubic-inch - -volume; fluid; 001; 11 / 10000; 0.0011; cubic-meter; 11 / 10; 1.1; liter -volume; fluid; 001; 1 / 1000; 0.001; cubic-meter; 1; 1.0; liter -volume; fluid; 001; 9 / 10000; 9.0E-4; cubic-meter; 900; 900.0; milliliter -volume; fluid; 001; 1 / 1000000; 1.0E-6; cubic-meter; 1; 1.0; milliliter -volume; fluid; 001; 9 / 10000000; 9.0E-7; cubic-meter; 9 / 10; 0.9; milliliter - -volume; fluid; US; 5204941203 / 1250000000000; 0.0041639529624; cubic-meter; 11 / 10; 1.1; gallon -volume; fluid; US; 473176473 / 125000000000; 0.003785411784; cubic-meter; 1; 1.0; gallon -volume; fluid; US; 4258588257 / 1250000000000; 0.0034068706056; cubic-meter; 18 / 5; 3.6; quart -volume; fluid; US; 473176473 / 500000000000; 9.46352946E-4; cubic-meter; 1; 1.0; quart -volume; fluid; US; 4258588257 / 5000000000000; 8.517176514E-4; cubic-meter; 9 / 5; 1.8; pint -volume; fluid; US; 473176473 / 1000000000000; 4.73176473E-4; cubic-meter; 1; 1.0; pint -volume; fluid; US; 4258588257 / 10000000000000; 4.258588257E-4; cubic-meter; 9 / 5; 1.8; cup -volume; fluid; US; 473176473 / 2000000000000; 2.365882365E-4; cubic-meter; 1; 1.0; cup -volume; fluid; US; 4258588257 / 20000000000000; 2.1292941285E-4; cubic-meter; 36 / 5; 7.2; fluid-ounce -volume; fluid; US; 473176473 / 16000000000000; 2.95735295625E-5; cubic-meter; 1; 1.0; fluid-ounce -volume; fluid; US; 4258588257 / 160000000000000; 2.661617660625E-5; cubic-meter; 9 / 5; 1.8; tablespoon -volume; fluid; US; 473176473 / 32000000000000; 1.478676478125E-5; cubic-meter; 1; 1.0; tablespoon -volume; fluid; US; 4258588257 / 320000000000000; 1.3308088303125E-5; cubic-meter; 27 / 10; 2.7; teaspoon -volume; fluid; US; 157725491 / 32000000000000; 4.92892159375E-6; cubic-meter; 1; 1.0; teaspoon -volume; fluid; US; 1419529419 / 320000000000000; 4.436029434375E-6; cubic-meter; 9 / 10; 0.9; teaspoon - -volume; fluid; GB; 5000699 / 1000000000; 0.005000699; cubic-meter; 11 / 10; 1.1; gallon-imperial -volume; fluid; GB; 454609 / 100000000; 0.00454609; cubic-meter; 1; 1.0; gallon-imperial -volume; fluid; GB; 4091481 / 1000000000; 0.004091481; cubic-meter; 144; 144.0; fluid-ounce-imperial -volume; fluid; GB; 454609 / 16000000000; 2.84130625E-5; cubic-meter; 1; 1.0; fluid-ounce-imperial -volume; fluid; GB; 4091481 / 160000000000; 2.557175625E-5; cubic-meter; 9 / 10; 0.9; fluid-ounce-imperial - -volume; oil; 001; 109303765263 / 625000000000; 0.1748860244208; cubic-meter; 11 / 10; 1.1; barrel -volume; oil; 001; 9936705933 / 62500000000; 0.158987294928; cubic-meter; 1; 1.0; barrel -volume; oil; 001; 89430353397 / 625000000000; 0.1430885654352; cubic-meter; 9 / 10; 0.9; barrel - -volume; vehicle; US; 5204941203 / 1250000000000; 0.0041639529624; cubic-meter; 11 / 10; 1.1; gallon -volume; vehicle; US; 473176473 / 125000000000; 0.003785411784; cubic-meter; 1; 1.0; gallon -volume; vehicle; US; 4258588257 / 1250000000000; 0.0034068706056; cubic-meter; 9 / 10; 0.9; gallon - -volume; vehicle; 001; 11 / 10000; 0.0011; cubic-meter; 11 / 10; 1.1; liter -volume; vehicle; 001; 1 / 1000; 0.001; cubic-meter; 1; 1.0; liter -volume; vehicle; 001; 9 / 10000; 9.0E-4; cubic-meter; 9 / 10; 0.9; liter - -year-duration; default; 001; 11 / 10; 1.1; year; 11 / 10; 1.1; year -year-duration; default; 001; 1; 1.0; year; 1; 1.0; year -year-duration; default; 001; 9 / 10; 0.9; year; 54 / 5; 10.8; month -year-duration; default; 001; 1 / 12; 0.08333333333333333; year; 1; 1.0; month -year-duration; default; 001; 3 / 40; 0.075; year; 9 / 10; 0.9; month - -year-duration; person-age; 001; 13 / 5; 2.6; year; 13 / 5; 2.6; year-person -year-duration; person-age; 001; 5 / 2; 2.5; year; 5 / 2; 2.5; year-person -year-duration; person-age; 001; 12 / 5; 2.4; year; 2; year-person; 24 / 5; 4.8; month-person -year-duration; person-age; 001; 1; 1.0; year; 1; year-person; 0; 0.0; month-person -year-duration; person-age; 001; 9 / 10; 0.9; year; 54 / 5; 10.8; month-person -year-duration; person-age; 001; 1 / 12; 0.08333333333333333; year; 1; 1.0; month-person -year-duration; person-age; 001; 3 / 40; 0.075; year; 9 / 10; 0.9; month-person diff --git a/icu4c/source/test/testdata/units/unitsTest.txt b/icu4c/source/test/testdata/units/unitsTest.txt deleted file mode 100644 index 31b0e256614..00000000000 --- a/icu4c/source/test/testdata/units/unitsTest.txt +++ /dev/null @@ -1,197 +0,0 @@ -# This file is a copy of common/testData/units/unitsTest.txt from CLDR. -# WIP/TODO(hugovdm): determine a good update procedure and document it. -# -# Test data for unit conversions -# Copyright © 1991-2020 Unicode, Inc. -# For terms of use, see http://www.unicode.org/copyright.html -# Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. -# CLDR data files are interpreted according to the LDML specification (http://unicode.org/reports/tr35/) -# -# Format: -# Quantity ; x ; y ; conversion to y (rational) ; test: 1000 x ⟹ y -# -# Use: convert 1000 x units to the y unit; the result should match the final column, -# at the given precision. For example, when the last column is 159.1549, -# round to 4 decimal digits before comparing. -# Note that certain conversions are approximate, such as degrees to radians -# -# Generation: Set GENERATE_TESTS in TestUnits.java, and look at TestParseUnit results. - -acceleration ; meter-per-square-second ; meter-per-square-second ; 1 * x ; 1,000.00 -acceleration ; g-force ; meter-per-square-second ; 9.80665 * x ; 9806.65 -angle ; arc-second ; revolution ; 0.0000625/81 * x ; 7.716049E-4 -angle ; arc-minute ; revolution ; 0.00125/27 * x ; 0.0462963 -angle ; degree ; revolution ; 0.025/9 * x ; 2.777778 -angle ; radian ; revolution ; 65,501,488/411,557,987 * x ; 159.1549 -angle ; revolution ; revolution ; 1 * x ; 1,000.00 -area ; square-centimeter ; square-meter ; 0.0001 * x ; 0.1 -area ; square-inch ; square-meter ; 0.00064516 * x ; 0.64516 -area ; square-foot ; square-meter ; 0.09290304 * x ; 92.90304 -area ; square-yard ; square-meter ; 0.83612736 * x ; 836.1274 -area ; square-meter ; square-meter ; 1 * x ; 1,000.00 -area ; dunam ; square-meter ; 1,000 * x ; 1000000.0 -area ; acre ; square-meter ; 4,046.8564224 * x ; 4046856.0 -area ; hectare ; square-meter ; 10,000 * x ; 1.0E7 -area ; square-kilometer ; square-meter ; 1,000,000 * x ; 1.0E9 -area ; square-mile ; square-meter ; 2,589,988.110336 * x ; 2.589988E9 -concentration ; millimole-per-liter ; item-per-cubic-meter ; 602,214,076,000,000,000,000,000 * x ; 6.022141E26 -consumption ; liter-per-100-kilometer ; cubic-meter-per-meter ; 0.00000001 * x ; 1.0E-5 -consumption ; liter-per-kilometer ; cubic-meter-per-meter ; 0.000001 * x ; 0.001 -consumption-inverse ; mile-per-gallon-imperial ; meter-per-cubic-meter ; 160,934,400,000/454,609 * x ; 3.540062E8 -consumption-inverse ; mile-per-gallon ; meter-per-cubic-meter ; 48,000,000,000/112,903 * x ; 4.251437E8 -digital ; bit ; bit ; 1 * x ; 1,000.00 -digital ; byte ; bit ; 8 * x ; 8000.0 -digital ; kilobit ; bit ; 1,000 * x ; 1000000.0 -digital ; kilobyte ; bit ; 8,000 * x ; 8000000.0 -digital ; megabit ; bit ; 1,000,000 * x ; 1.0E9 -digital ; megabyte ; bit ; 8,000,000 * x ; 8.0E9 -digital ; gigabit ; bit ; 1,000,000,000 * x ; 1.0E12 -digital ; gigabyte ; bit ; 8,000,000,000 * x ; 8.0E12 -digital ; terabit ; bit ; 1,000,000,000,000 * x ; 1.0E15 -digital ; terabyte ; bit ; 8,000,000,000,000 * x ; 8.0E15 -digital ; petabyte ; bit ; 8,000,000,000,000,000 * x ; 8.0E18 -duration ; nanosecond ; second ; 0.000000001 * x ; 1.0E-6 -duration ; microsecond ; second ; 0.000001 * x ; 0.001 -duration ; millisecond ; second ; 0.001 * x ; 1.0 -duration ; second ; second ; 1 * x ; 1,000.00 -duration ; minute ; second ; 60 * x ; 60000.0 -duration ; hour ; second ; 3,600 * x ; 3600000.0 -duration ; day ; second ; 86,400 * x ; 8.64E7 -duration ; day-person ; second ; 86,400 * x ; 8.64E7 -duration ; week ; second ; 604,800 * x ; 6.048E8 -duration ; week-person ; second ; 604,800 * x ; 6.048E8 -electric-current ; milliampere ; ampere ; 0.001 * x ; 1.0 -electric-current ; ampere ; ampere ; 1 * x ; 1,000.00 -electric-resistance ; ohm ; kilogram-square-meter-per-cubic-second-square-ampere ; 1 * x ; 1000.0 -energy ; electronvolt ; kilogram-square-meter-per-square-second ; 0.0000000000000000001602177 * x ; 1.602177E-16 -energy ; dalton ; kilogram-square-meter-per-square-second ; 0.00000000014924180856 * x ; 1.492418E-7 -energy ; joule ; kilogram-square-meter-per-square-second ; 1 * x ; 1000.0 -energy ; newton-meter ; kilogram-square-meter-per-square-second ; 1 * x ; 1000.0 -energy ; pound-force-foot ; kilogram-square-meter-per-square-second ; 1.3558179483314004 * x ; 1355.818 -energy ; calorie ; kilogram-square-meter-per-square-second ; 4.184 * x ; 4184.0 -energy ; kilojoule ; kilogram-square-meter-per-square-second ; 1,000 * x ; 1000000.0 -energy ; british-thermal-unit ; kilogram-square-meter-per-square-second ; 9,489.1523804/9 * x ; 1054350.0 -energy ; foodcalorie ; kilogram-square-meter-per-square-second ; 4,184 * x ; 4184000.0 -energy ; kilocalorie ; kilogram-square-meter-per-square-second ; 4,184 * x ; 4184000.0 -energy ; kilowatt-hour ; kilogram-square-meter-second-per-cubic-second ; 3,600,000 * x ; 3.6E9 -energy ; therm-us ; kilogram-square-meter-per-square-second ; 105,480,400 * x ; 1.054804E11 -force ; newton ; kilogram-meter-per-square-second ; 1 * x ; 1000.0 -force ; pound-force ; kilogram-meter-per-square-second ; 4.4482216152605 * x ; 4448.222 -frequency ; hertz ; revolution-per-second ; 1 * x ; 1000.0 -frequency ; kilohertz ; revolution-per-second ; 1,000 * x ; 1000000.0 -frequency ; megahertz ; revolution-per-second ; 1,000,000 * x ; 1.0E9 -frequency ; gigahertz ; revolution-per-second ; 1,000,000,000 * x ; 1.0E12 -graphics ; dot ; pixel ; 1 * x ; 1000.0 -graphics ; pixel ; pixel ; 1 * x ; 1,000.00 -graphics ; megapixel ; pixel ; 1,000,000 * x ; 1.0E9 -length ; picometer ; meter ; 0.000000000001 * x ; 1.0E-9 -length ; nanometer ; meter ; 0.000000001 * x ; 1.0E-6 -length ; micrometer ; meter ; 0.000001 * x ; 0.001 -length ; point ; meter ; 0.003175/9 * x ; 0.3527778 -length ; millimeter ; meter ; 0.001 * x ; 1.0 -length ; centimeter ; meter ; 0.01 * x ; 10.0 -length ; inch ; meter ; 0.0254 * x ; 25.4 -length ; decimeter ; meter ; 0.1 * x ; 100.0 -length ; foot ; meter ; 0.3048 * x ; 304.8 -length ; yard ; meter ; 0.9144 * x ; 914.4 -length ; meter ; meter ; 1 * x ; 1,000.00 -length ; fathom ; meter ; 1.8288 * x ; 1828.8 -length ; furlong ; meter ; 201.168 * x ; 201168.0 -length ; kilometer ; meter ; 1,000 * x ; 1000000.0 -length ; mile ; meter ; 1,609.344 * x ; 1609344.0 -length ; nautical-mile ; meter ; 1,852 * x ; 1852000.0 -length ; mile-scandinavian ; meter ; 10,000 * x ; 1.0E7 -length ; 100-kilometer ; meter ; 100,000 * x ; 1.0E8 -length ; earth-radius ; meter ; 6,378,100 * x ; 6.3781E9 -length ; solar-radius ; meter ; 695,700,000 * x ; 6.957E11 -length ; astronomical-unit ; meter ; 149,597,900,000 * x ; 1.495979E14 -length ; light-year ; meter ; 9,460,730,000,000,000 * x ; 9.46073E18 -length ; parsec ; meter ; 30,856,780,000,000,000 * x ; 3.085678E19 -luminous-flux ; lux ; candela-square-meter-per-square-meter ; 1 * x ; 1000.0 -luminous-intensity ; candela ; candela ; 1 * x ; 1,000.00 -mass ; microgram ; kilogram ; 0.000000001 * x ; 1.0E-6 -mass ; milligram ; kilogram ; 0.000001 * x ; 0.001 -mass ; carat ; kilogram ; 0.0002 * x ; 0.2 -mass ; gram ; kilogram ; 0.001 * x ; 1.0 -mass ; ounce ; kilogram ; 0.028349523125 * x ; 28.34952 -mass ; ounce-troy ; kilogram ; 0.03110348 * x ; 31.10348 -mass ; pound ; kilogram ; 0.45359237 * x ; 453.5924 -mass ; kilogram ; kilogram ; 1 * x ; 1,000.00 -mass ; stone ; kilogram ; 6.35029318 * x ; 6350.293 -mass ; ton ; kilogram ; 907.18474 * x ; 907184.7 -mass ; metric-ton ; kilogram ; 1,000 * x ; 1000000.0 -mass ; earth-mass ; kilogram ; 5,972,200,000,000,000,000,000,000 * x ; 5.9722E27 -mass ; solar-mass ; kilogram ; 1,988,470,000,000,000,000,000,000,000,000 * x ; 1.98847E33 -mass-density ; milligram-per-deciliter ; kilogram-per-cubic-meter ; 0.01 * x ; 10.0 -portion ; permillion ; portion ; 0.000001 * x ; 0.001 -portion ; permyriad ; portion ; 0.0001 * x ; 0.1 -portion ; permille ; portion ; 0.001 * x ; 1.0 -portion ; percent ; portion ; 0.01 * x ; 10.0 -portion ; karat ; portion ; 0.125/3 * x ; 41.66667 -portion ; portion ; portion ; 1 * x ; 1,000.00 -power ; milliwatt ; kilogram-square-meter-per-cubic-second ; 0.001 * x ; 1.0 -power ; watt ; kilogram-square-meter-per-cubic-second ; 1 * x ; 1000.0 -power ; horsepower ; kilogram-square-meter-per-cubic-second ; 745.69987158227022 * x ; 745699.9 -power ; kilowatt ; kilogram-square-meter-per-cubic-second ; 1,000 * x ; 1000000.0 -power ; megawatt ; kilogram-square-meter-per-cubic-second ; 1,000,000 * x ; 1.0E9 -power ; gigawatt ; kilogram-square-meter-per-cubic-second ; 1,000,000,000 * x ; 1.0E12 -power ; solar-luminosity ; kilogram-square-meter-per-cubic-second ; 382,800,000,000,000,000,000,000,000 * x ; 3.828E29 -pressure ; pascal ; kilogram-per-meter-square-second ; 1 * x ; 1000.0 -pressure ; hectopascal ; kilogram-per-meter-square-second ; 100 * x ; 100000.0 -pressure ; millibar ; kilogram-per-meter-square-second ; 100 * x ; 100000.0 -pressure ; millimeter-ofhg ; kilogram-meter-per-square-meter-square-second ; 133.322387415 * x ; 133322.4 -pressure ; kilopascal ; kilogram-per-meter-square-second ; 1,000 * x ; 1000000.0 -pressure ; inch-ofhg ; kilogram-meter-per-square-meter-square-second ; 3,386.388640341 * x ; 3386389.0 -pressure ; pound-force-per-square-inch ; kilogram-meter-per-square-meter-square-second ; 111,205,540.3815125/16,129 * x ; 6894757.0 -pressure ; bar ; kilogram-per-meter-square-second ; 100,000 * x ; 1.0E8 -pressure ; atmosphere ; kilogram-per-meter-square-second ; 101,325 * x ; 1.01325E8 -pressure ; megapascal ; kilogram-per-meter-square-second ; 1,000,000 * x ; 1.0E9 -pressure-per-length ; ofhg ; kilogram-per-square-meter-square-second ; 133,322.387415 * x ; 1.333224E8 -resolution ; dot-per-inch ; pixel-per-meter ; 5,000/127 * x ; 39370.08 -resolution ; pixel-per-inch ; pixel-per-meter ; 5,000/127 * x ; 39370.08 -resolution ; dot-per-centimeter ; pixel-per-meter ; 100 * x ; 100000.0 -resolution ; pixel-per-centimeter ; pixel-per-meter ; 100 * x ; 100000.0 -speed ; kilometer-per-hour ; meter-per-second ; 2.5/9 * x ; 277.7778 -speed ; mile-per-hour ; meter-per-second ; 0.44704 * x ; 447.04 -speed ; knot ; meter-per-second ; 4.63/9 * x ; 514.4444 -speed ; meter-per-second ; meter-per-second ; 1 * x ; 1,000.00 -substance-amount ; item ; item ; 1 * x ; 1,000.00 -substance-amount ; mole ; item ; 602,214,076,000,000,000,000,000 * x ; 6.022141E26 -temperature ; fahrenheit ; kelvin ; 5/9 * x - 2,298.35/9 ; 810.9278 -temperature ; kelvin ; kelvin ; 1 * x ; 1,000.00 -temperature ; celsius ; kelvin ; 1 * x - 273.15 ; 1273.15 -typewidth ; em ; em ; 1 * x ; 1,000.00 -voltage ; volt ; kilogram-square-meter-per-cubic-second-ampere ; 1 * x ; 1000.0 -volume ; cubic-centimeter ; cubic-meter ; 0.000001 * x ; 0.001 -volume ; milliliter ; cubic-meter ; 0.000001 * x ; 0.001 -volume ; teaspoon ; cubic-meter ; 0.00000492892159375 * x ; 0.004928922 -volume ; centiliter ; cubic-meter ; 0.00001 * x ; 0.01 -volume ; tablespoon ; cubic-meter ; 0.00001478676478125 * x ; 0.01478676 -volume ; cubic-inch ; cubic-meter ; 0.000016387064 * x ; 0.01638706 -volume ; fluid-ounce-imperial ; cubic-meter ; 0.0000284130625 * x ; 0.02841306 -volume ; fluid-ounce ; cubic-meter ; 0.0000295735295625 * x ; 0.02957353 -volume ; deciliter ; cubic-meter ; 0.0001 * x ; 0.1 -volume ; cup ; cubic-meter ; 0.0002365882365 * x ; 0.2365882 -volume ; cup-metric ; cubic-meter ; 0.00025 * x ; 0.25 -volume ; pint ; cubic-meter ; 0.000473176473 * x ; 0.4731765 -volume ; pint-metric ; cubic-meter ; 0.0005 * x ; 0.5 -volume ; quart ; cubic-meter ; 0.000946352946 * x ; 0.9463529 -volume ; liter ; cubic-meter ; 0.001 * x ; 1.0 -volume ; gallon ; cubic-meter ; 0.003785411784 * x ; 3.785412 -volume ; gallon-imperial ; cubic-meter ; 0.00454609 * x ; 4.54609 -volume ; cubic-foot ; cubic-meter ; 0.028316846592 * x ; 28.31685 -volume ; bushel ; cubic-meter ; 0.03523907016688 * x ; 35.23907 -volume ; hectoliter ; cubic-meter ; 0.1 * x ; 100.0 -volume ; barrel ; cubic-meter ; 0.158987294928 * x ; 158.9873 -volume ; cubic-yard ; cubic-meter ; 0.764554857984 * x ; 764.5549 -volume ; cubic-meter ; cubic-meter ; 1 * x ; 1,000.00 -volume ; megaliter ; cubic-meter ; 1,000 * x ; 1000000.0 -volume ; acre-foot ; cubic-meter ; 1,233.48183754752 * x ; 1233482.0 -volume ; cubic-kilometer ; cubic-meter ; 1,000,000,000 * x ; 1.0E12 -volume ; cubic-mile ; cubic-meter ; 4,168,181,825.440579584 * x ; 4.168182E12 -year-duration ; month ; year ; 0.25/3 * x ; 83.33333 -year-duration ; month-person ; year ; 0.25/3 * x ; 83.33333 -year-duration ; year ; year ; 1 * x ; 1,000.00 -year-duration ; year-person ; year ; 1 * x ; 1000.0 -year-duration ; decade ; year ; 10 * x ; 10000.0 -year-duration ; century ; year ; 100 * x ; 100000.0 -- 2.40.0