]> granicus.if.org Git - icu/commitdiff
ICU-20568 Use `Impl` libraries, add precision UnitsRouter#route output
authoryounies <younies@chromium.org>
Tue, 23 Jun 2020 12:46:50 +0000 (14:46 +0200)
committerHugo van der Merwe <17109322+hugovdm@users.noreply.github.com>
Thu, 10 Sep 2020 20:39:18 +0000 (22:39 +0200)
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

13 files changed:
icu4c/source/i18n/complexunitsconverter.cpp
icu4c/source/i18n/complexunitsconverter.h
icu4c/source/i18n/measunit_extra.cpp
icu4c/source/i18n/measunit_impl.h
icu4c/source/i18n/number_usageprefs.cpp
icu4c/source/i18n/unitconverter.cpp
icu4c/source/i18n/unitconverter.h
icu4c/source/i18n/unitsdata.cpp
icu4c/source/i18n/unitsdata.h
icu4c/source/i18n/unitsrouter.cpp
icu4c/source/i18n/unitsrouter.h
icu4c/source/test/depstest/dependencies.txt
icu4c/source/test/intltest/unitstest.cpp

index 4890f748c332db8bb34c93acbb3b848d1bf9ce03..7ad1c5fcf9ef0da3c83ad318cb5347e668001181 100644 (file)
@@ -5,32 +5,63 @@
 
 #if !UCONFIG_NO_FORMATTING
 
-#include <math.h>
+#include <cmath>
 
 #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<const MeasureUnitImpl *const *>(left);
+        const auto *rightPointer = static_cast<const MeasureUnitImpl *const *>(right);
+
+        UnitConverter fromLeftToRight(**leftPointer,                                  //
+                                      **rightPointer,                                 //
+                                      *static_cast<const ConversionRates *>(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<MeasureUnit> singleUnitsInOrder;
-    for (int i = 0; i < length; ++i) {
-        /**
-         *  TODO(younies): ensure units being in order by the biggest unit at first.
-         * 
-         * HINT:
-         *  MaybeStackVector<SingleUnitImpl> 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<Measure> 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<Measure> 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);
         }
     }
 
index fbae2b1b6e3ed04a8541a3073e19c94ecb3580a3..1cf2eb4a95b5ac63a10883ad89aa5bc3033e9378 100644 (file)
@@ -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<UnitConverter> unitConverters_;
-    MaybeStackVector<MeasureUnit> units_;
+    MaybeStackVector<MeasureUnitImpl> units_;
 };
 
 } // namespace units
index c702e13e3d7edf4b01287fdbb198a2d7f1e30980..83a4de83cd357fc1c5f1644264254bd19378176b 100644 (file)
@@ -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> MeasureUnitImpl::extractIndividualUnits(UErrorCode &status) const {
+    MaybeStackVector<MeasureUnitImpl> 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);
 }
index 3ec1e9bc2139b787e5ca3a7be1092b9110d51a2b..3cc2cd0476f75a83da451f9469c021696bf3ff40 100644 (file)
@@ -134,6 +134,13 @@ template class U_I18N_API MaybeStackVector<SingleUnitImpl, 8>;
  * 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<MeasureUnitImpl> extractIndividualUnits(UErrorCode &status) const;
+
     /** Mutates this MeasureUnitImpl to take the reciprocal. */
     void takeReciprocal(UErrorCode& status);
 
index 3dcc8ef7f518af3187d8929a4a0e1a5560a589e6..8a2669ada2d0dab1ca03fc9064ab177ea67e1498 100644 (file)
@@ -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?
index 97a5e3d5a73112e11d06967be08eef2a2a7ae89a..bf472f5886081ac2390327e3f7d2b98ddb8ccd67 100644 (file)
@@ -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 <algorithm>
@@ -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<UnitIndexAndDimension> &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<UnitIndexAndDimension> &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<UnitIndexAndDimension> &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<UnitIndexAndDimension> convertible;
+    MaybeStackVector<UnitIndexAndDimension> 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 {
index d19994b9d676e891daa8e71d34e5b0ba1f855d64..3b66dbdfb12d898fb1111073df50dbca0c838a49 100644 (file)
@@ -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.
index 7b7f7dff648f3e88f0a221781516613303c5e389..098c9f560bca75c8439447ab548e0e43d669fd7f 100644 (file)
@@ -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);
                             }
                         }
                     }
index 6f1c1ecfd9c6c7a6062f98b83caa4ca4c06a5cf8..df5f591be2aa36bb1b2b91f7e6c096461fc29a83 100644 (file)
@@ -7,6 +7,8 @@
 #ifndef __GETUNITSDATA_H__
 #define __GETUNITSDATA_H__
 
+#include <limits>
+
 #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;
 };
 
 /**
index 5ca7b62e03831847f5cf0feb2605dfba5d2d9f60..7381d550c5b798c2368d3397194abe4f02bc2dc1 100644 (file)
@@ -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<Measure> 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<MeasureUnit> *UnitsRouter::getOutputUnits() const {
+    // TODO: consider pulling this from converterPreferences_ and dropping
+    // outputUnits_?
     return &outputUnits_;
 }
 
index c8b8a646f61570e5b2e72afbf6685d46510870fb..68284663293efdd7a7852e47e2a45dad52ec7455 100644 (file)
@@ -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<Measure> measures;
+    UnicodeString precision;
+
+    RouteResult(MaybeStackVector<Measure> 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<double>::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<double>::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<Measure> 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<MeasureUnit> *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<MeasureUnit> outputUnits_;
 
     MaybeStackVector<ConverterPreference> converterPreferences_;
index b35fa45327e7ed755cb23b315b9fe0dfe52e0853..f746aabce1ac4b9ebf82d31235ef546c584680b1 100644 (file)
@@ -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
index ba208ae1a63c9bb01ab402fdb5c24938138cccbc..8309bed1addd3e5e4bf5c3596347c416e1235fd9 100644 (file)
@@ -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<Measure> 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);
 }
 
 /**