From 1b853904cdf1ec66fb91a2c57a4f17b53f4e6036 Mon Sep 17 00:00:00 2001 From: younies Date: Tue, 23 Jun 2020 14:46:50 +0200 Subject: [PATCH] ICU-20568 Use `Impl` libraries, add precision UnitsRouter#route output Add precision to the output of UnitsRouter#route PR: https://github.com/icu-units/icu/pull/10 Commit: 030bda3ec86a02b190a83798a7c9be530a335067 Use `Impl` libraries for all internal libraries PR: https://github.com/icu-units/icu/pull/15 Commit: cc786cfb3bee3c72e78a928da8b9a6fd58f31b04 Sort the units in ComplexUnitConverter PR: https://github.com/icu-units/icu/pull/6 Commit: f65b181c4447bb4eb9eef5dc20ea1b296d053ffa --- icu4c/source/i18n/complexunitsconverter.cpp | 99 +++++----- icu4c/source/i18n/complexunitsconverter.h | 13 +- icu4c/source/i18n/measunit_extra.cpp | 24 ++- icu4c/source/i18n/measunit_impl.h | 17 ++ icu4c/source/i18n/number_usageprefs.cpp | 8 +- icu4c/source/i18n/unitconverter.cpp | 196 ++++++++++++-------- icu4c/source/i18n/unitconverter.h | 47 +++-- icu4c/source/i18n/unitsdata.cpp | 4 +- icu4c/source/i18n/unitsdata.h | 7 +- icu4c/source/i18n/unitsrouter.cpp | 47 ++++- icu4c/source/i18n/unitsrouter.h | 31 +++- icu4c/source/test/depstest/dependencies.txt | 2 +- icu4c/source/test/intltest/unitstest.cpp | 82 +++++--- 13 files changed, 373 insertions(+), 204 deletions(-) diff --git a/icu4c/source/i18n/complexunitsconverter.cpp b/icu4c/source/i18n/complexunitsconverter.cpp index 4890f748c33..7ad1c5fcf9e 100644 --- a/icu4c/source/i18n/complexunitsconverter.cpp +++ b/icu4c/source/i18n/complexunitsconverter.cpp @@ -5,32 +5,63 @@ #if !UCONFIG_NO_FORMATTING -#include +#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" U_NAMESPACE_BEGIN namespace units { -ComplexUnitsConverter::ComplexUnitsConverter(const MeasureUnit &inputUnit, - const MeasureUnit &outputUnits, - const ConversionRates &ratesInfo, UErrorCode &status) { +ComplexUnitsConverter::ComplexUnitsConverter(const MeasureUnitImpl &inputUnit, + const MeasureUnitImpl &outputUnits, + const ConversionRates &ratesInfo, UErrorCode &status) + : units_(outputUnits.extractIndividualUnits(status)) { + if (U_FAILURE(status)) { + return; + } - if (outputUnits.getComplexity(status) != UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) { - unitConverters_.emplaceBackAndCheckErrorCode(status, inputUnit, outputUnits, ratesInfo, status); - if (U_FAILURE(status)) { - return; + U_ASSERT(units_.length() != 0); + + // 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. + auto descendingCompareUnits = [](const void *context, const void *left, const void *right) { + UErrorCode status = U_ZERO_ERROR; + + const auto *leftPointer = static_cast(left); + const auto *rightPointer = static_cast(right); + + UnitConverter fromLeftToRight(**leftPointer, // + **rightPointer, // + *static_cast(context), // + status); + + double rightFromOneLeft = fromLeftToRight.convert(1.0); + if (std::abs(rightFromOneLeft - 1.0) < 0.0000000001) { // Equals To + return 0; + } else if (rightFromOneLeft > 1.0) { // Greater Than + return -1; } - units_.emplaceBackAndCheckErrorCode(status, outputUnits); - return; - } + return 1; // Less Than + }; + + uprv_sortArray(units_.getAlias(), // + units_.length(), // + sizeof units_[0], /* NOTE: we have already asserted that the units_ is not empty.*/ // + descendingCompareUnits, // + &ratesInfo, // + false, // + &status // + ); // In case the `outputUnits` are `UMEASURE_UNIT_MIXED` such as `foot+inch`. In this case we need more // converters to convert from the `inputUnit` to the first unit in the `outputUnits`. Then, a @@ -46,47 +77,19 @@ ComplexUnitsConverter::ComplexUnitsConverter(const MeasureUnit &inputUnit, // 2. convert the residual of 6.56168 feet (0.56168) to inches, which will be (6.74016 // inches) // 3. then, the final result will be (6 feet and 6.74016 inches) - int32_t length; - auto singleUnits = outputUnits.splitToSingleUnits(length, status); - MaybeStackVector singleUnitsInOrder; - for (int i = 0; i < length; ++i) { - /** - * TODO(younies): ensure units being in order by the biggest unit at first. - * - * HINT: - * MaybeStackVector singleUnitsInOrder = MeasureUnitImpl::forMeasureUnitMaybeCopy(outputUnits, status).units; - * uprv_sortArray( - * singleUnitsInOrder.getAlias(), - * singleUnitsInOrder.length(), - * sizeof(singleUnitsInOrder[0]), - * compareSingleUnits, - * nullptr, - * false, - * &status); - */ - singleUnitsInOrder.emplaceBackAndCheckErrorCode(status, singleUnits[i]); - } - - if (singleUnitsInOrder.length() == 0) { - status = U_ILLEGAL_ARGUMENT_ERROR; - return; - } - - for (int i = 0, n = singleUnitsInOrder.length(); i < n; i++) { + for (int i = 0, n = units_.length(); i < n; i++) { if (i == 0) { // first element - unitConverters_.emplaceBackAndCheckErrorCode(status, inputUnit, *singleUnitsInOrder[i], - ratesInfo, status); + unitConverters_.emplaceBackAndCheckErrorCode(status, inputUnit, *units_[i], ratesInfo, + status); } else { - unitConverters_.emplaceBackAndCheckErrorCode(status, *singleUnitsInOrder[i - 1], - *singleUnitsInOrder[i], ratesInfo, status); + unitConverters_.emplaceBackAndCheckErrorCode(status, *units_[i - 1], *units_[i], ratesInfo, + status); } if (U_FAILURE(status)) { return; } } - - units_.appendAll(singleUnitsInOrder, status); } UBool ComplexUnitsConverter::greaterThanOrEqual(double quantity, double limit) const { @@ -107,8 +110,8 @@ MaybeStackVector ComplexUnitsConverter::convert(double quantity, UError Formattable formattableNewQuantity(newQuantity); // NOTE: Measure would own its MeasureUnit. - result.emplaceBackAndCheckErrorCode(status, formattableNewQuantity, - new MeasureUnit(*units_[i]), status); + MeasureUnit *type = new MeasureUnit(units_[i]->copy(status).build(status)); + result.emplaceBackAndCheckErrorCode(status, formattableNewQuantity, type, status); // Keep the residual of the quantity. // For example: `3.6 feet`, keep only `0.6 feet` @@ -117,8 +120,8 @@ MaybeStackVector ComplexUnitsConverter::convert(double quantity, UError Formattable formattableQuantity(quantity); // NOTE: Measure would own its MeasureUnit. - result.emplaceBackAndCheckErrorCode(status, formattableQuantity, new MeasureUnit(*units_[i]), - status); + MeasureUnit *type = new MeasureUnit(units_[i]->copy(status).build(status)); + result.emplaceBackAndCheckErrorCode(status, formattableQuantity, type, status); } } diff --git a/icu4c/source/i18n/complexunitsconverter.h b/icu4c/source/i18n/complexunitsconverter.h index fbae2b1b6e3..1cf2eb4a95b 100644 --- a/icu4c/source/i18n/complexunitsconverter.h +++ b/icu4c/source/i18n/complexunitsconverter.h @@ -8,15 +8,14 @@ #define __COMPLEXUNITSCONVERTER_H__ #include "cmemory.h" -#include "unicode/measunit.h" +#include "measunit_impl.h" +#include "unicode/errorcode.h" +#include "unicode/measure.h" #include "unitconverter.h" #include "unitsdata.h" U_NAMESPACE_BEGIN -// Forward declarations -class Measure; - namespace units { /** @@ -28,7 +27,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 { +class U_I18N_API ComplexUnitsConverter : UMemory { public: /** * Constructor of `ComplexUnitsConverter`. @@ -40,7 +39,7 @@ class U_I18N_API ComplexUnitsConverter { * @param outputUnits represents the output unit. could be any type. (single, compound or mixed). * @param status */ - ComplexUnitsConverter(const MeasureUnit &inputUnit, const MeasureUnit &outputUnits, + ComplexUnitsConverter(const MeasureUnitImpl &inputUnit, const MeasureUnitImpl &outputUnits, const ConversionRates &ratesInfo, UErrorCode &status); // Returns true if the specified `quantity` of the `inputUnit`, expressed in terms of the biggest @@ -60,7 +59,7 @@ class U_I18N_API ComplexUnitsConverter { private: MaybeStackVector unitConverters_; - MaybeStackVector units_; + MaybeStackVector units_; }; } // namespace units diff --git a/icu4c/source/i18n/measunit_extra.cpp b/icu4c/source/i18n/measunit_extra.cpp index c702e13e3d7..83a4de83cd3 100644 --- a/icu4c/source/i18n/measunit_extra.cpp +++ b/icu4c/source/i18n/measunit_extra.cpp @@ -788,6 +788,14 @@ const char *SingleUnitImpl::getSimpleUnitID() const { return gSimpleUnits[index]; } +MeasureUnitImpl::MeasureUnitImpl(const MeasureUnitImpl &other, UErrorCode &status) { + *this = other.copy(status); +} + +MeasureUnitImpl::MeasureUnitImpl(const SingleUnitImpl &singleUnit, UErrorCode &status) { + this->append(singleUnit, status); +} + MeasureUnitImpl MeasureUnitImpl::forIdentifier(StringPiece identifier, UErrorCode& status) { return Parser::from(identifier, status).parse(status); } @@ -823,12 +831,26 @@ bool MeasureUnitImpl::append(const SingleUnitImpl& singleUnit, UErrorCode& statu return appendImpl(*this, singleUnit, status); } +MaybeStackVector MeasureUnitImpl::extractIndividualUnits(UErrorCode &status) const { + MaybeStackVector result; + + if (this->complexity != UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) { + result.emplaceBackAndCheckErrorCode(status, *this, status); + return result; + } + + for (int32_t i = 0; i < units.length(); i++) { + result.emplaceBackAndCheckErrorCode(status, *units[i], status); + } + + return result; +} + MeasureUnit MeasureUnitImpl::build(UErrorCode& status) && { serialize(*this, status); return MeasureUnit(std::move(*this)); } - MeasureUnit MeasureUnit::forIdentifier(StringPiece identifier, UErrorCode& status) { return Parser::from(identifier, status).parse(status).build(status); } diff --git a/icu4c/source/i18n/measunit_impl.h b/icu4c/source/i18n/measunit_impl.h index 3ec1e9bc213..3cc2cd0476f 100644 --- a/icu4c/source/i18n/measunit_impl.h +++ b/icu4c/source/i18n/measunit_impl.h @@ -134,6 +134,13 @@ template class U_I18N_API MaybeStackVector; * including mixed and compound units. */ struct U_I18N_API MeasureUnitImpl : public UMemory { + MeasureUnitImpl() = default; + MeasureUnitImpl(MeasureUnitImpl &&other) = default; + MeasureUnitImpl(const MeasureUnitImpl &other, UErrorCode &status); + MeasureUnitImpl(const SingleUnitImpl &singleUnit, UErrorCode &status); + + MeasureUnitImpl &operator=(MeasureUnitImpl &&other) noexcept = default; + /** Extract the MeasureUnitImpl from a MeasureUnit. */ static inline const MeasureUnitImpl* get(const MeasureUnit& measureUnit) { return measureUnit.fImpl; @@ -189,6 +196,16 @@ struct U_I18N_API MeasureUnitImpl : public UMemory { */ MeasureUnitImpl copy(UErrorCode& status) const; + /** + * Extracts the list of all the individual units inside the `MeasureUnitImpl`. + * For example: + * - if the `MeasureUnitImpl` is `foot-per-hour` + * it will return a list of 1 {`foot-per-hour`} + * - if the `MeasureUnitImpl` is `foot-and-inch` + * it will return a list of 2 { `foot`, `inch`} + */ + MaybeStackVector extractIndividualUnits(UErrorCode &status) const; + /** Mutates this MeasureUnitImpl to take the reciprocal. */ void takeReciprocal(UErrorCode& status); diff --git a/icu4c/source/i18n/number_usageprefs.cpp b/icu4c/source/i18n/number_usageprefs.cpp index 3dcc8ef7f51..8a2669ada2d 100644 --- a/icu4c/source/i18n/number_usageprefs.cpp +++ b/icu4c/source/i18n/number_usageprefs.cpp @@ -101,15 +101,17 @@ void UsagePrefsHandler::processQuantity(DecimalQuantity &quantity, MicroProps &m } quantity.roundToInfinity(); // Enables toDouble - auto routed = fUnitsRouter.route(quantity.toDouble(), status); - micros.outputUnit = routed[0]->getUnit(); - quantity.setToDouble(routed[0]->getNumber().getDouble()); + 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? diff --git a/icu4c/source/i18n/unitconverter.cpp b/icu4c/source/i18n/unitconverter.cpp index 97a5e3d5a73..bf472f58860 100644 --- a/icu4c/source/i18n/unitconverter.cpp +++ b/icu4c/source/i18n/unitconverter.cpp @@ -10,8 +10,8 @@ #include "double-conversion-string-to-double.h" #include "measunit_impl.h" #include "uassert.h" +#include "unicode/errorcode.h" #include "unicode/localpointer.h" -#include "unicode/measunit.h" #include "unicode/stringpiece.h" #include "unitconverter.h" #include @@ -88,20 +88,6 @@ void U_I18N_API Factor::applySiPrefix(UMeasureSIPrefix siPrefix) { } void U_I18N_API Factor::substituteConstants() { - // These values are a hard-coded subset of unitConstants in the units - // resources file. A unit test checks that all constants in the resource - // file are at least recognised by the code. Derived constants' values or - // hard-coded derivations are not checked. - // double constantsValues[CONSTANTS_COUNT]; - static const double constantsValues[CONSTANTS_COUNT] = { - [CONSTANT_FT2M] = 0.3048, // - [CONSTANT_PI] = 411557987.0 / 131002976.0, // - [CONSTANT_GRAVITY] = 9.80665, // - [CONSTANT_G] = 6.67408E-11, // - [CONSTANT_GAL_IMP2M3] = 0.00454609, // - [CONSTANT_LB2KG] = 0.45359237, // - }; - for (int i = 0; i < CONSTANTS_COUNT; i++) { if (this->constants[i] == 0) { continue; @@ -235,16 +221,12 @@ Factor loadSingleFactor(StringPiece source, const ConversionRates &ratesInfo, UE } // Load Factor of a compound source unit. -Factor loadCompoundFactor(const MeasureUnit &source, const ConversionRates &ratesInfo, +Factor loadCompoundFactor(const MeasureUnitImpl &source, const ConversionRates &ratesInfo, UErrorCode &status) { Factor result; - MeasureUnitImpl memory; - const auto &compoundSourceUnit = MeasureUnitImpl::forMeasureUnit(source, memory, status); - if (U_FAILURE(status)) return result; - - for (int32_t i = 0, n = compoundSourceUnit.units.length(); i < n; i++) { - auto singleUnit = *compoundSourceUnit.units[i]; // a SingleUnitImpl + for (int32_t i = 0, n = source.units.length(); i < n; i++) { + SingleUnitImpl singleUnit = *source.units[i]; Factor singleFactor = loadSingleFactor(singleUnit.getSimpleUnitID(), ratesInfo, status); if (U_FAILURE(status)) return result; @@ -264,30 +246,35 @@ Factor loadCompoundFactor(const MeasureUnit &source, const ConversionRates &rate /** * Checks if the source unit and the target unit are simple. For example celsius or fahrenheit. But not * square-celsius or square-fahrenheit. + * + * NOTE: + * Empty unit means simple unit. */ -UBool checkSimpleUnit(const MeasureUnit &unit, UErrorCode &status) { - MeasureUnitImpl memory; - const auto &compoundSourceUnit = MeasureUnitImpl::forMeasureUnit(unit, memory, status); +UBool checkSimpleUnit(const MeasureUnitImpl &unit, UErrorCode &status) { if (U_FAILURE(status)) return false; - if (compoundSourceUnit.complexity != UMEASURE_UNIT_SINGLE) { + if (unit.complexity != UMEASURE_UNIT_SINGLE) { return false; } + if (unit.units.length() == 0) { + // Empty units means simple unit. + return true; + } - U_ASSERT(compoundSourceUnit.units.length() == 1); - auto singleUnit = *(compoundSourceUnit.units[0]); + auto singleUnit = *(unit.units[0]); if (singleUnit.dimensionality != 1 || singleUnit.siPrefix != UMEASURE_SI_PREFIX_ONE) { return false; } + return true; } /** * Extract conversion rate from `source` to `target` */ -void loadConversionRate(ConversionRate &conversionRate, const MeasureUnit &source, - const MeasureUnit &target, UnitsConvertibilityState unitsState, +void loadConversionRate(ConversionRate &conversionRate, const MeasureUnitImpl &source, + const MeasureUnitImpl &target, Convertibility unitsState, const ConversionRates &ratesInfo, UErrorCode &status) { // Represents the conversion factor from the source to the target. Factor finalFactor; @@ -299,9 +286,9 @@ void loadConversionRate(ConversionRate &conversionRate, const MeasureUnit &sourc // Merger Factors finalFactor.multiplyBy(sourceToBase); - if (unitsState == UnitsConvertibilityState::CONVERTIBLE) { + if (unitsState == Convertibility::CONVERTIBLE) { finalFactor.divideBy(targetToBase); - } else if (unitsState == UnitsConvertibilityState::RECIPROCAL) { + } else if (unitsState == Convertibility::RECIPROCAL) { finalFactor.multiplyBy(targetToBase); } else { status = UErrorCode::U_ARGUMENT_TYPE_MISMATCH; @@ -321,7 +308,48 @@ void loadConversionRate(ConversionRate &conversionRate, const MeasureUnit &sourc targetToBase.offset * targetToBase.factorDen / targetToBase.factorNum; } - conversionRate.reciprocal = unitsState == UnitsConvertibilityState::RECIPROCAL; + conversionRate.reciprocal = unitsState == Convertibility::RECIPROCAL; +} + +struct UnitIndexAndDimension : UMemory { + int32_t index = 0; + int32_t dimensionality = 0; + + UnitIndexAndDimension(const SingleUnitImpl &singleUnit, int32_t multiplier) { + index = singleUnit.index; + dimensionality = singleUnit.dimensionality * multiplier; + } +}; + +void mergeSingleUnitWithDimension(MaybeStackVector &unitIndicesWithDimension, + const SingleUnitImpl &shouldBeMerged, int32_t multiplier) { + for (int32_t i = 0; i < unitIndicesWithDimension.length(); i++) { + auto &unitWithIndex = *unitIndicesWithDimension[i]; + if (unitWithIndex.index == shouldBeMerged.index) { + unitWithIndex.dimensionality += shouldBeMerged.dimensionality * multiplier; + return; + } + } + + unitIndicesWithDimension.emplaceBack(shouldBeMerged, multiplier); +} + +void mergeUnitsAndDimensions(MaybeStackVector &unitIndicesWithDimension, + const MeasureUnitImpl &shouldBeMerged, int32_t multiplier) { + for (int32_t unit_i = 0; unit_i < shouldBeMerged.units.length(); unit_i++) { + auto singleUnit = *shouldBeMerged.units[unit_i]; + mergeSingleUnitWithDimension(unitIndicesWithDimension, singleUnit, multiplier); + } +} + +UBool checkAllDimensionsAreZeros(const MaybeStackVector &dimensionVector) { + for (int32_t i = 0; i < dimensionVector.length(); i++) { + if (dimensionVector[i]->dimensionality != 0) { + return false; + } + } + + return true; } } // namespace @@ -368,21 +396,20 @@ void U_I18N_API addSingleFactorConstant(StringPiece baseStr, int32_t power, Sign * Extracts the compound base unit of a compound unit (`source`). For example, if the source unit is * `square-mile-per-hour`, the compound base unit will be `square-meter-per-second` */ -MeasureUnit U_I18N_API extractCompoundBaseUnit(const MeasureUnit &source, - const ConversionRates &conversionRates, - UErrorCode &status) { - MeasureUnit result; - int32_t count; - const auto singleUnits = source.splitToSingleUnits(count, status); +MeasureUnitImpl U_I18N_API extractCompoundBaseUnit(const MeasureUnitImpl &source, + const ConversionRates &conversionRates, + UErrorCode &status) { + + MeasureUnitImpl result; if (U_FAILURE(status)) return result; - for (int i = 0; i < count; ++i) { - const auto &singleUnit = singleUnits[i]; + const auto &singleUnits = source.units; + for (int i = 0, count = singleUnits.length(); i < count; ++i) { + const auto &singleUnit = *singleUnits[i]; // Extract `ConversionRateInfo` using the absolute unit. For example: in case of `square-meter`, // we will use `meter` - const auto singleUnitImpl = SingleUnitImpl::forMeasureUnit(singleUnit, status); const auto rateInfo = - conversionRates.extractConversionInfo(singleUnitImpl.getSimpleUnitID(), status); + conversionRates.extractConversionInfo(singleUnit.getSimpleUnitID(), status); if (U_FAILURE(status)) { return result; } @@ -393,14 +420,12 @@ MeasureUnit U_I18N_API extractCompoundBaseUnit(const MeasureUnit &source, // Multiply the power of the singleUnit by the power of the baseUnit. For example, square-hectare // must be pow4-meter. (NOTE: hectare --> square-meter) - auto compoundBaseUnit = MeasureUnit::forIdentifier(rateInfo->baseUnit.toStringPiece(), status); - - int32_t baseUnitsCount; - auto baseUnits = compoundBaseUnit.splitToSingleUnits(baseUnitsCount, status); - for (int j = 0; j < baseUnitsCount; j++) { - int8_t newDimensionality = - baseUnits[j].getDimensionality(status) * singleUnit.getDimensionality(status); - result = result.product(baseUnits[j].withDimensionality(newDimensionality, status), status); + auto baseUnits = + MeasureUnitImpl::forIdentifier(rateInfo->baseUnit.toStringPiece(), status).units; + for (int32_t i = 0, baseUnitsCount = baseUnits.length(); i < baseUnitsCount; i++) { + baseUnits[i]->dimensionality *= singleUnit.dimensionality; + // TODO: Deal with SI-prefix + result.append(*baseUnits[i], status); if (U_FAILURE(status)) { return result; @@ -411,53 +436,70 @@ MeasureUnit U_I18N_API extractCompoundBaseUnit(const MeasureUnit &source, return result; } -UnitsConvertibilityState U_I18N_API checkConvertibility(const MeasureUnit &source, - const MeasureUnit &target, - const ConversionRates &conversionRates, - UErrorCode &status) { - if (source.getComplexity(status) == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED || - target.getComplexity(status) == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) { +/** + * Determine the convertibility between `source` and `target`. + * For example: + * `meter` and `foot` are `CONVERTIBLE`. + * `meter-per-second` and `second-per-meter` are `RECIPROCAL`. + * `meter` and `pound` are `UNCONVERTIBLE`. + * + * NOTE: + * Only works with SINGLE and COMPOUND units. If one of the units is a + * MIXED unit, an error will occur. For more information, see UMeasureUnitComplexity. + */ +Convertibility U_I18N_API extractConvertibility(const MeasureUnitImpl &source, + const MeasureUnitImpl &target, + const ConversionRates &conversionRates, + UErrorCode &status) { + + if (source.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED || + target.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) { status = U_INTERNAL_PROGRAM_ERROR; return UNCONVERTIBLE; } - auto sourceBaseUnit = extractCompoundBaseUnit(source, conversionRates, status); - auto targetBaseUnit = extractCompoundBaseUnit(target, conversionRates, status); - + MeasureUnitImpl sourceBaseUnit = extractCompoundBaseUnit(source, conversionRates, status); + MeasureUnitImpl targetBaseUnit = extractCompoundBaseUnit(target, conversionRates, status); if (U_FAILURE(status)) return UNCONVERTIBLE; - if (sourceBaseUnit == targetBaseUnit) return CONVERTIBLE; - if (sourceBaseUnit == targetBaseUnit.reciprocal(status)) return RECIPROCAL; + MaybeStackVector convertible; + MaybeStackVector reciprocal; + + mergeUnitsAndDimensions(convertible, sourceBaseUnit, 1); + mergeUnitsAndDimensions(reciprocal, sourceBaseUnit, 1); + + mergeUnitsAndDimensions(convertible, targetBaseUnit, -1); + mergeUnitsAndDimensions(reciprocal, targetBaseUnit, 1); - auto sourceSimplified = sourceBaseUnit.simplify(status); - auto targetSimplified = targetBaseUnit.simplify(status); + if (checkAllDimensionsAreZeros(convertible)) { + return CONVERTIBLE; + } - if (sourceSimplified == targetSimplified) return CONVERTIBLE; - if (sourceSimplified == targetSimplified.reciprocal(status)) return RECIPROCAL; + if (checkAllDimensionsAreZeros(reciprocal)) { + return RECIPROCAL; + } return UNCONVERTIBLE; } -UnitConverter::UnitConverter(MeasureUnit source, MeasureUnit target, const ConversionRates &ratesInfo, - UErrorCode &status) { - - if (source.getComplexity(status) == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED || - target.getComplexity(status) == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) { +UnitConverter::UnitConverter(const MeasureUnitImpl &source, const MeasureUnitImpl &target, + const ConversionRates &ratesInfo, UErrorCode &status) + : conversionRate_(source.copy(status), target.copy(status)) { + if (source.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED || + target.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) { status = U_INTERNAL_PROGRAM_ERROR; return; } - UnitsConvertibilityState unitsState = checkConvertibility(source, target, ratesInfo, status); + Convertibility unitsState = extractConvertibility(source, target, ratesInfo, status); if (U_FAILURE(status)) return; - if (unitsState == UnitsConvertibilityState::UNCONVERTIBLE) { + if (unitsState == Convertibility::UNCONVERTIBLE) { status = U_INTERNAL_PROGRAM_ERROR; return; } - conversionRate_.source = source; - conversionRate_.target = target; - - loadConversionRate(conversionRate_, source, target, unitsState, ratesInfo, status); + loadConversionRate(conversionRate_, conversionRate_.source, conversionRate_.target, unitsState, + ratesInfo, status); } double UnitConverter::convert(double inputValue) const { diff --git a/icu4c/source/i18n/unitconverter.h b/icu4c/source/i18n/unitconverter.h index d19994b9d67..3b66dbdfb12 100644 --- a/icu4c/source/i18n/unitconverter.h +++ b/icu4c/source/i18n/unitconverter.h @@ -7,9 +7,12 @@ #ifndef __UNITCONVERTER_H__ #define __UNITCONVERTER_H__ -#include "unicode/measunit.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" U_NAMESPACE_BEGIN @@ -29,6 +32,19 @@ enum Constants { CONSTANTS_COUNT }; +// These values are a hard-coded subset of unitConstants in the units +// resources file. A unit test checks that all constants in the resource +// file are at least recognised by the code. Derived constants' values or +// hard-coded derivations are not checked. +static const double constantsValues[CONSTANTS_COUNT] = { + 0.3048, // CONSTANT_FT2M + 411557987.0 / 131002976.0, // CONSTANT_PI + 9.80665, // CONSTANT_GRAVITY + 6.67408E-11, // CONSTANT_G + 0.00454609, // CONSTANT_GAL_IMP2M3 + 0.45359237, // CONSTANT_LB2KG +}; + typedef enum Signum { NEGATIVE = -1, POSITIVE = 1, @@ -65,25 +81,28 @@ void U_I18N_API addSingleFactorConstant(StringPiece baseStr, int32_t power, Sign /** * Represents the conversion rate between `source` and `target`. */ -struct ConversionRate { - MeasureUnit source; - MeasureUnit target; +struct ConversionRate : public UMemory { + const MeasureUnitImpl source; + const MeasureUnitImpl target; double factorNum = 1; double factorDen = 1; double sourceOffset = 0; double targetOffset = 0; bool reciprocal = false; + + ConversionRate(MeasureUnitImpl &&source, MeasureUnitImpl &&target) + : source(std::move(source)), target(std::move(target)) {} }; -enum U_I18N_API UnitsConvertibilityState { +enum U_I18N_API Convertibility { RECIPROCAL, CONVERTIBLE, UNCONVERTIBLE, }; -MeasureUnit U_I18N_API extractCompoundBaseUnit(const MeasureUnit &source, - const ConversionRates &conversionRates, - UErrorCode &status); +MeasureUnitImpl U_I18N_API extractCompoundBaseUnit(const MeasureUnitImpl &source, + const ConversionRates &conversionRates, + UErrorCode &status); /** * Check if the convertibility between `source` and `target`. @@ -96,10 +115,10 @@ MeasureUnit U_I18N_API extractCompoundBaseUnit(const MeasureUnit &source, * Only works with SINGLE and COMPOUND units. If one of the units is a * MIXED unit, an error will occur. For more information, see UMeasureUnitComplexity. */ -UnitsConvertibilityState U_I18N_API checkConvertibility(const MeasureUnit &source, - const MeasureUnit &target, - const ConversionRates &conversionRates, - UErrorCode &status); +Convertibility U_I18N_API extractConvertibility(const MeasureUnitImpl &source, + const MeasureUnitImpl &target, + const ConversionRates &conversionRates, + UErrorCode &status); /** * Converts from a source `MeasureUnit` to a target `MeasureUnit`. @@ -121,8 +140,8 @@ class U_I18N_API UnitConverter : public UMemory { * @param ratesInfo Contains all the needed conversion rates. * @param status */ - UnitConverter(MeasureUnit source, MeasureUnit target, const ConversionRates &ratesInfo, - UErrorCode &status); + UnitConverter(const MeasureUnitImpl &source, const MeasureUnitImpl &target, + const ConversionRates &ratesInfo, UErrorCode &status); /** * Convert a value in the source unit to another value in the target unit. diff --git a/icu4c/source/i18n/unitsdata.cpp b/icu4c/source/i18n/unitsdata.cpp index 7b7f7dff648..098c9f560bc 100644 --- a/icu4c/source/i18n/unitsdata.cpp +++ b/icu4c/source/i18n/unitsdata.cpp @@ -214,9 +214,7 @@ class UnitPreferencesSink : public ResourceSink { dq.setToDecNumber(geq.data(), status); up->geq = dq.toDouble(); } else if (uprv_strcmp(key, "skeleton") == 0) { - int32_t length; - const UChar *s = value.getString(length, status); - up->skeleton.appendInvariantChars(s, length, status); + up->skeleton = value.getUnicodeString(status); } } } diff --git a/icu4c/source/i18n/unitsdata.h b/icu4c/source/i18n/unitsdata.h index 6f1c1ecfd9c..df5f591be2a 100644 --- a/icu4c/source/i18n/unitsdata.h +++ b/icu4c/source/i18n/unitsdata.h @@ -7,6 +7,8 @@ #ifndef __GETUNITSDATA_H__ #define __GETUNITSDATA_H__ +#include + #include "charstr.h" #include "cmemory.h" #include "unicode/stringpiece.h" @@ -92,10 +94,11 @@ class U_I18N_API ConversionRates { // Encapsulates unitPreferenceData information from units resources, specifying // a sequence of output unit preferences. struct U_I18N_API UnitPreference : public UMemory { - UnitPreference() : geq(1) {} + // Set geq to 1.0 by default + UnitPreference() : geq(1.0) {} CharString unit; double geq; - CharString skeleton; + UnicodeString skeleton; }; /** diff --git a/icu4c/source/i18n/unitsrouter.cpp b/icu4c/source/i18n/unitsrouter.cpp index 5ca7b62e038..7381d550c5b 100644 --- a/icu4c/source/i18n/unitsrouter.cpp +++ b/icu4c/source/i18n/unitsrouter.cpp @@ -7,8 +7,11 @@ #include "charstr.h" #include "cmemory.h" +#include "cstring.h" +#include "measunit_impl.h" +#include "number_decimalquantity.h" +#include "resource.h" #include "unicode/measure.h" -#include "unitconverter.h" #include "unitsdata.h" #include "unitsrouter.h" @@ -22,7 +25,9 @@ UnitsRouter::UnitsRouter(MeasureUnit inputUnit, StringPiece region, StringPiece ConversionRates conversionRates(status); UnitPreferences prefs(status); - MeasureUnit baseUnit = extractCompoundBaseUnit(inputUnit, conversionRates, status); + MeasureUnitImpl inputUnitImpl = MeasureUnitImpl::forMeasureUnitMaybeCopy(inputUnit, status); + MeasureUnit baseUnit = + (extractCompoundBaseUnit(inputUnitImpl, conversionRates, status)).build(status); CharString category = getUnitCategory(baseUnit.getIdentifier(), status); const UnitPreference *const *unitPreferences; @@ -32,35 +37,57 @@ UnitsRouter::UnitsRouter(MeasureUnit inputUnit, StringPiece region, StringPiece for (int i = 0; i < preferencesCount; ++i) { const auto &preference = *unitPreferences[i]; - MeasureUnit complexTargetUnit = MeasureUnit::forIdentifier(preference.unit.data(), status); + MeasureUnitImpl complexTargetUnitImpl = + MeasureUnitImpl::forIdentifier(preference.unit.data(), status); if (U_FAILURE(status)) { return; } - outputUnits_.emplaceBackAndCheckErrorCode(status, complexTargetUnit); - converterPreferences_.emplaceBackAndCheckErrorCode(status, inputUnit, complexTargetUnit, - preference.geq, conversionRates, status); + UnicodeString precision = preference.skeleton; + + // For now, we only have "precision-increment" in Units Preferences skeleton. + // Therefore, we check if the skeleton starts with "precision-increment" and force the program to + // fail otherwise. + // NOTE: + // It is allowed to have an empty precision. + if (!precision.isEmpty() && !precision.startsWith(u"precision-increment", 19)) { + status = U_INTERNAL_PROGRAM_ERROR; + return; + } + + outputUnits_.emplaceBackAndCheckErrorCode(status, + complexTargetUnitImpl.copy(status).build(status)); + converterPreferences_.emplaceBackAndCheckErrorCode(status, inputUnitImpl, complexTargetUnitImpl, + preference.geq, std::move(precision), + conversionRates, status); + if (U_FAILURE(status)) { return; } } } -MaybeStackVector UnitsRouter::route(double quantity, UErrorCode &status) const { +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 converterPreference.converter.convert(quantity, status); + return RouteResult(converterPreference.converter.convert(quantity, status), // + converterPreference.precision // + ); } } // In case of the `quantity` does not fit in any converter limit, use the last converter. - const auto &lastConverter = (*converterPreferences_[converterPreferences_.length() - 1]).converter; - return lastConverter.convert(quantity, status); + const auto &lastConverterPreference = (*converterPreferences_[converterPreferences_.length() - 1]); + return RouteResult(lastConverterPreference.converter.convert(quantity, status), // + lastConverterPreference.precision // + ); } const MaybeStackVector *UnitsRouter::getOutputUnits() const { + // TODO: consider pulling this from converterPreferences_ and dropping + // outputUnits_? return &outputUnits_; } diff --git a/icu4c/source/i18n/unitsrouter.h b/icu4c/source/i18n/unitsrouter.h index c8b8a646f61..68284663293 100644 --- a/icu4c/source/i18n/unitsrouter.h +++ b/icu4c/source/i18n/unitsrouter.h @@ -11,6 +11,7 @@ #include "cmemory.h" #include "complexunitsconverter.h" +#include "measunit_impl.h" #include "unicode/measunit.h" #include "unicode/stringpiece.h" #include "unicode/uobject.h" @@ -23,6 +24,14 @@ class Measure; namespace units { +struct RouteResult : UMemory { + MaybeStackVector measures; + UnicodeString precision; + + RouteResult(MaybeStackVector measures, UnicodeString precision) + : measures(std::move(measures)), precision(std::move(precision)) {} +}; + /** * Contains the complex unit converter and the limit which representing the smallest value that the * converter should accept. For example, if the converter is converting to `foot+inch` and the limit @@ -35,15 +44,19 @@ namespace units { struct ConverterPreference : UMemory { ComplexUnitsConverter converter; double limit; + UnicodeString precision; - ConverterPreference(MeasureUnit source, MeasureUnit complexTarget, double limit, - const ConversionRates &ratesInfo, UErrorCode &status) - : converter(source, complexTarget, ratesInfo, status), limit(limit) {} + // 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) + : ConverterPreference(source, complexTarget, std::numeric_limits::lowest(), precision, + ratesInfo, status) {} - ConverterPreference(MeasureUnit source, MeasureUnit complexTarget, const ConversionRates &ratesInfo, + ConverterPreference(const MeasureUnitImpl &source, const MeasureUnitImpl &complexTarget, + double limit, UnicodeString precision, const ConversionRates &ratesInfo, UErrorCode &status) - : ConverterPreference(source, complexTarget, std::numeric_limits::lowest(), ratesInfo, - status) {} + : converter(source, complexTarget, ratesInfo, status), limit(limit), + precision(std::move(precision)) {} }; /** @@ -78,7 +91,7 @@ class U_I18N_API UnitsRouter { public: UnitsRouter(MeasureUnit inputUnit, StringPiece locale, StringPiece usage, UErrorCode &status); - MaybeStackVector route(double quantity, UErrorCode &status) const; + RouteResult route(double quantity, UErrorCode &status) const; /** * Returns the list of possible output units, i.e. the full set of @@ -90,7 +103,9 @@ class U_I18N_API UnitsRouter { const MaybeStackVector *getOutputUnits() const; private: - // List of possible output units + // List of possible output units. TODO: converterPreferences_ now also has + // this data available. Maybe drop outputUnits_ and have getOutputUnits + // construct a the list from data in converterPreferences_ instead? MaybeStackVector outputUnits_; MaybeStackVector converterPreferences_; diff --git a/icu4c/source/test/depstest/dependencies.txt b/icu4c/source/test/depstest/dependencies.txt index b35fa45327e..f746aabce1a 100644 --- a/icu4c/source/test/depstest/dependencies.txt +++ b/icu4c/source/test/depstest/dependencies.txt @@ -1078,7 +1078,7 @@ group: units group: unitsformatter unitsdata.o unitconverter.o complexunitsconverter.o unitsrouter.o deps - resourcebundle units_extra double_conversion number_representation formattable + resourcebundle units_extra double_conversion number_representation formattable sort group: decnumber decContext.o decNumber.o diff --git a/icu4c/source/test/intltest/unitstest.cpp b/icu4c/source/test/intltest/unitstest.cpp index ba208ae1a63..8309bed1add 100644 --- a/icu4c/source/test/intltest/unitstest.cpp +++ b/icu4c/source/test/intltest/unitstest.cpp @@ -44,6 +44,7 @@ class UnitsTest : public IntlTest { void testUnitConstantFreshness(); void testConversionCapability(); void testConversions(); + void testComplexUnitConverterSorting(); void testPreferences(); void testSiPrefixes(); void testMass(); @@ -61,6 +62,7 @@ void UnitsTest::runIndexedTest(int32_t index, UBool exec, const char *&name, cha TESTCASE_AUTO(testUnitConstantFreshness); TESTCASE_AUTO(testConversionCapability); TESTCASE_AUTO(testConversions); + TESTCASE_AUTO(testComplexUnitConverterSorting); TESTCASE_AUTO(testPreferences); TESTCASE_AUTO(testSiPrefixes); TESTCASE_AUTO(testMass); @@ -121,26 +123,27 @@ void UnitsTest::testConversionCapability() { struct TestCase { const char *const source; const char *const target; - const UnitsConvertibilityState expectedState; + const Convertibility expectedState; } testCases[]{ - {"meter", "foot", CONVERTIBLE}, // - {"kilometer", "foot", CONVERTIBLE}, // - {"hectare", "square-foot", CONVERTIBLE}, // - {"kilometer-per-second", "second-per-meter", RECIPROCAL}, // - {"square-meter", "square-foot", CONVERTIBLE}, // - {"kilometer-per-second", "foot-per-second", CONVERTIBLE}, // - {"square-hectare", "pow4-foot", CONVERTIBLE}, // - {"square-kilometer-per-second", "second-per-square-meter", RECIPROCAL}, // + {"meter", "foot", CONVERTIBLE}, // + {"kilometer", "foot", CONVERTIBLE}, // + {"hectare", "square-foot", CONVERTIBLE}, // + {"kilometer-per-second", "second-per-meter", RECIPROCAL}, // + {"square-meter", "square-foot", CONVERTIBLE}, // + {"kilometer-per-second", "foot-per-second", CONVERTIBLE}, // + {"square-hectare", "pow4-foot", CONVERTIBLE}, // + {"square-kilometer-per-second", "second-per-square-meter", RECIPROCAL}, // + {"cubic-kilometer-per-second-meter", "second-per-square-meter", RECIPROCAL}, // }; for (const auto &testCase : testCases) { UErrorCode status = U_ZERO_ERROR; - MeasureUnit source = MeasureUnit::forIdentifier(testCase.source, status); - MeasureUnit target = MeasureUnit::forIdentifier(testCase.target, status); + MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status); + MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status); ConversionRates conversionRates(status); - auto convertibility = checkConvertibility(source, target, conversionRates, status); + auto convertibility = extractConvertibility(source, target, conversionRates, status); assertEquals(UnicodeString("Conversion Capability: ") + testCase.source + " to " + testCase.target, @@ -171,8 +174,8 @@ void UnitsTest::testSiPrefixes() { for (const auto &testCase : testCases) { UErrorCode status = U_ZERO_ERROR; - MeasureUnit source = MeasureUnit::forIdentifier(testCase.source, status); - MeasureUnit target = MeasureUnit::forIdentifier(testCase.target, status); + MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status); + MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status); ConversionRates conversionRates(status); UnitConverter converter(source, target, conversionRates, status); @@ -206,8 +209,8 @@ void UnitsTest::testMass() { for (const auto &testCase : testCases) { UErrorCode status = U_ZERO_ERROR; - MeasureUnit source = MeasureUnit::forIdentifier(testCase.source, status); - MeasureUnit target = MeasureUnit::forIdentifier(testCase.target, status); + MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status); + MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status); ConversionRates conversionRates(status); UnitConverter converter(source, target, conversionRates, status); @@ -240,8 +243,8 @@ void UnitsTest::testTemperature() { for (const auto &testCase : testCases) { UErrorCode status = U_ZERO_ERROR; - MeasureUnit source = MeasureUnit::forIdentifier(testCase.source, status); - MeasureUnit target = MeasureUnit::forIdentifier(testCase.target, status); + MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status); + MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status); ConversionRates conversionRates(status); UnitConverter converter(source, target, conversionRates, status); @@ -278,8 +281,8 @@ void UnitsTest::testArea() { for (const auto &testCase : testCases) { UErrorCode status = U_ZERO_ERROR; - MeasureUnit source = MeasureUnit::forIdentifier(testCase.source, status); - MeasureUnit target = MeasureUnit::forIdentifier(testCase.target, status); + MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status); + MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status); ConversionRates conversionRates(status); UnitConverter converter(source, target, conversionRates, status); @@ -356,12 +359,12 @@ void unitsTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount, U return; } - MeasureUnit sourceUnit = MeasureUnit::forIdentifier(x, status); + MeasureUnitImpl sourceUnit = MeasureUnitImpl::forIdentifier(x, status); if (status.errIfFailureAndReset("forIdentifier(\"%.*s\")", x.length(), x.data())) { return; } - MeasureUnit targetUnit = MeasureUnit::forIdentifier(y, status); + MeasureUnitImpl targetUnit = MeasureUnitImpl::forIdentifier(y, status); if (status.errIfFailureAndReset("forIdentifier(\"%.*s\")", y.length(), y.data())) { return; } @@ -373,16 +376,16 @@ void unitsTestDataLineFn(void *context, char *fields[][2], int32_t fieldCount, U expected, commentConversionFormula.length(), commentConversionFormula.data()); // Convertibility: - auto convertibility = checkConvertibility(sourceUnit, targetUnit, *ctx->conversionRates, status); - if (status.errIfFailureAndReset("checkConvertibility(<%s>, <%s>, ...)", sourceUnit.getIdentifier(), - targetUnit.getIdentifier())) { + auto convertibility = extractConvertibility(sourceUnit, targetUnit, *ctx->conversionRates, status); + if (status.errIfFailureAndReset("extractConvertibility(<%s>, <%s>, ...)", + sourceUnit.identifier.data(), targetUnit.identifier.data())) { return; } CharString msg; msg.append("convertible: ", status) - .append(sourceUnit.getIdentifier(), status) + .append(sourceUnit.identifier.data(), status) .append(" -> ", status) - .append(targetUnit.getIdentifier(), status); + .append(targetUnit.identifier.data(), status); if (status.errIfFailureAndReset("msg construction")) { return; } @@ -391,7 +394,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.getIdentifier(), targetUnit.getIdentifier())) { + sourceUnit.identifier.data(), targetUnit.identifier.data())) { return; } double got = converter.convert(1000); @@ -428,6 +431,24 @@ void UnitsTest::testConversions() { } } +void UnitsTest::testComplexUnitConverterSorting() { + IcuTestErrorCode status(*this, "UnitsTest::testComplexUnitConverterSorting"); + + MeasureUnitImpl source = MeasureUnitImpl::forIdentifier("meter", status); + MeasureUnitImpl target = MeasureUnitImpl::forIdentifier("inch-and-foot", status); + ConversionRates conversionRates(status); + + ComplexUnitsConverter complexConverter(source, target, conversionRates, status); + 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()); + + assertEqualsNear("Sorted Data", 32, measures[0]->getNumber().getInt64(), 0.00001); + assertEqualsNear("Sorted Data", 9.7008, measures[1]->getNumber().getDouble(), 0.0001); +} + /** * This class represents the output fields from unitPreferencesTest.txt. Please * see the documentation at the top of that file for details. @@ -598,6 +619,7 @@ void unitPreferencesTestDataLineFn(void *context, char *fields[][2], int32_t fie if (U_FAILURE(status)) { return; } + UnitsRouter router(inputMeasureUnit, region, usage, status); if (status.errIfFailureAndReset("UnitsRouter(<%s>, \"%.*s\", \"%.*s\", status)", inputMeasureUnit.getIdentifier(), region.length(), region.data(), @@ -617,12 +639,12 @@ void unitPreferencesTestDataLineFn(void *context, char *fields[][2], int32_t fie if (status.errIfFailureAndReset("Failure before router.route")) { return; } - MaybeStackVector result = router.route(inputAmount, status); + auto routeResult = router.route(inputAmount, status); if (status.errIfFailureAndReset("router.route(inputAmount, ...)")) { return; } // TODO: revisit this experimentally chosen precision: - checkOutput(unitsTest, msg.data(), expected, result, 0.0000000001); + checkOutput(unitsTest, msg.data(), expected, routeResult.measures, 0.0000000001); } /** -- 2.40.0