From c93036d5d9cac7a19774d41c24a60453dff900f6 Mon Sep 17 00:00:00 2001
From: Younies Mahmoud <younies.mahmoud@gmail.com>
Date: Tue, 31 Mar 2020 17:16:32 +0200
Subject: [PATCH] Create

---
 icu4c/source/i18n/unitconverter.cpp      | 376 +++++++++++++++++++++++
 icu4c/source/i18n/unitconverter.h        |  44 +++
 icu4c/source/test/intltest/intltest.cpp  |  17 +
 icu4c/source/test/intltest/intltest.h    |   1 +
 icu4c/source/test/intltest/unitstest.cpp | 327 ++++++++++++--------
 5 files changed, 630 insertions(+), 135 deletions(-)

diff --git a/icu4c/source/i18n/unitconverter.cpp b/icu4c/source/i18n/unitconverter.cpp
index fda67aea492..69ddfaa5923 100644
--- a/icu4c/source/i18n/unitconverter.cpp
+++ b/icu4c/source/i18n/unitconverter.cpp
@@ -5,7 +5,10 @@
 
 #if !UCONFIG_NO_FORMATTING
 
+#include <cmath>
+
 #include "charstr.h"
+#include "double-conversion.h"
 #include "measunit_impl.h"
 #include "unicode/errorcode.h"
 #include "unicode/measunit.h"
@@ -16,6 +19,125 @@ U_NAMESPACE_BEGIN
 
 namespace {
 
+/* Internal Structure */
+
+enum Constants {
+    CONSTANT_FT2M,    // ft2m stands for foot to meter.
+    CONSTANT_PI,      // PI
+    CONSTANT_GRAVITY, // Gravity
+    CONSTANT_G,
+    CONSTANT_GAL_IMP2M3, // Gallon imp to m3
+    CONSTANT_LB2KG,      // Pound to Kilogram
+
+    // Must be the last element.
+    CONSTANTS_COUNT
+};
+
+typedef enum SigNum {
+    NEGATIVE = -1,
+    POSITIVE = 1,
+} SigNum;
+
+/* Represents a conversion factor */
+struct Factor {
+    double factorNum = 1;
+    double factorDen = 1;
+    double offset = 0;
+    bool reciprocal = false;
+    int32_t constants[CONSTANTS_COUNT] = {};
+
+    void multiplyBy(const Factor &rhs) {
+        factorNum *= rhs.factorNum;
+        factorDen *= rhs.factorDen;
+        for (int i = 0; i < CONSTANTS_COUNT; i++)
+            constants[i] += rhs.constants[i];
+
+        offset += rhs.offset;
+    }
+
+    void divideBy(const Factor &rhs) {
+        factorNum *= rhs.factorDen;
+        factorDen *= rhs.factorNum;
+        for (int i = 0; i < CONSTANTS_COUNT; i++)
+            constants[i] -= rhs.constants[i];
+
+        offset += rhs.offset;
+    }
+
+    // Apply the power to the factor.
+    void power(int32_t power) {
+        // multiply all the constant by the power.
+        for (int i = 0; i < CONSTANTS_COUNT; i++)
+            constants[i] *= power;
+
+        bool shouldFlip = power < 0; // This means that after applying the absolute power, we should flip
+                                     // the Numerator and Denomerator.
+
+        factorNum = std::pow(factorNum, std::abs(power));
+        factorDen = std::pow(factorDen, std::abs(power));
+
+        if (shouldFlip) {
+            // Flip Numerator and Denomirator.
+            std::swap(factorNum, factorDen);
+        }
+    }
+
+    // Flip the `Factor`, for example, factor= 2/3, flippedFactor = 3/2
+    void flip() {
+        std::swap(factorNum, factorDen);
+
+        for (int i = 0; i < CONSTANTS_COUNT; i++) {
+            constants[i] *= -1;
+        }
+    }
+
+    // Apply SI prefix to the `Factor`
+    void applySiPrefix(UMeasureSIPrefix siPrefix) {
+        if (siPrefix == UMeasureSIPrefix::UMEASURE_SI_PREFIX_ONE) return; // No need to do anything
+
+        double siApplied = std::pow(10.0, std::abs(siPrefix));
+
+        if (siPrefix < 0) {
+            factorDen *= siApplied;
+            return;
+        }
+
+        factorNum *= siApplied;
+    }
+};
+
+/* Helpers */
+
+using icu::double_conversion::StringToDoubleConverter;
+
+// Returns `double` from a scientific number(i.e. "1", "2.01" or "3.09E+4")
+double strToDouble(StringPiece strNum) {
+    // We are processing well-formed input, so we don't need any special options to
+    // StringToDoubleConverter.
+    StringToDoubleConverter converter(0, 0, 0, "", "");
+    int32_t count;
+    return converter.StringToDouble(strNum.data(), strNum.length(), &count);
+}
+
+// Returns `double` from a scientific number that could has a division sign (i.e. "1", "2.01", "3.09E+4"
+// or "2E+2/3")
+double strHasDivideSignToDouble(StringPiece strWithDivide) {
+    int divisionSignInd = -1;
+    for (int i = 0, n = strWithDivide.length(); i < n; ++i) {
+        if (strWithDivide.data()[i] == '/') {
+            divisionSignInd = i;
+            break;
+        }
+    }
+
+    if (divisionSignInd >= 0) {
+        return strToDouble(strWithDivide.substr(0, divisionSignInd)) /
+               strToDouble(strWithDivide.substr(divisionSignInd + 1));
+    }
+
+    return strToDouble(strWithDivide);
+}
+
 const ConversionRateInfo &
 extractConversionInfo(StringPiece source,
                       const MaybeStackVector<ConversionRateInfo> &conversionRateInfoList,
@@ -58,6 +180,231 @@ MeasureUnit extractBaseUnit(const MeasureUnit &source,
     return result;
 }
 
+/*
+ * Adds a single factor element to the `Factor`. e.g "ft3m", "2.333" or "cup2m3". But not "cup2m3^3".
+ */
+void addSingleFactorConstant(Factor &factor, StringPiece baseStr, int32_t power, SigNum sigNum) {
+
+    if (baseStr == "ft_to_m") {
+        factor.constants[CONSTANT_FT2M] += power * sigNum;
+    } else if (baseStr == "ft2_to_m2") {
+        factor.constants[CONSTANT_FT2M] += 2 * power * sigNum;
+    } else if (baseStr == "ft3_to_m3") {
+        factor.constants[CONSTANT_FT2M] += 3 * power * sigNum;
+    } else if (baseStr == "in3_to_m3") {
+        factor.constants[CONSTANT_FT2M] += 3 * power * sigNum;
+        factor.factorDen *= 12 * 12 * 12;
+    } else if (baseStr == "gal_to_m3") {
+        factor.factorNum *= 231;
+        factor.constants[CONSTANT_FT2M] += 3 * power * sigNum;
+        factor.factorDen *= 12 * 12 * 12;
+    } else if (baseStr == "gal_imp_to_m3") {
+        factor.constants[CONSTANT_GAL_IMP2M3] += power * sigNum;
+    } else if (baseStr == "G") {
+        factor.constants[CONSTANT_G] += power * sigNum;
+    } else if (baseStr == "gravity") {
+        factor.constants[CONSTANT_GRAVITY] += power * sigNum;
+    } else if (baseStr == "lb_to_kg") {
+        factor.constants[CONSTANT_LB2KG] += power * sigNum;
+    } else if (baseStr == "PI") {
+        factor.constants[CONSTANT_PI] += power * sigNum;
+    } else {
+        if (sigNum == SigNum::NEGATIVE) {
+            factor.factorDen *= std::pow(strToDouble(baseStr), power);
+        } else {
+            factor.factorNum *= std::pow(strToDouble(baseStr), power);
+        }
+    }
+}
+
+/*
+  Adds single factor to a `Factor` object. Single factor means "23^2", "23.3333", "ft2m^3" ...etc.
+  However, complex factor are not included, such as "ft2m^3*200/3"
+*/
+void addFactorElement(Factor &factor, StringPiece elementStr, SigNum sigNum) {
+    StringPiece baseStr;
+    StringPiece powerStr;
+    int32_t power =
+        1; // In case the power is not written, then, the power is equal 1 ==> `ft2m^1` == `ft2m`
+
+    // Search for the power part
+    int32_t powerInd = -1;
+    for (int32_t i = 0, n = elementStr.length(); i < n; ++i) {
+        if (elementStr.data()[i] == '^') {
+            powerInd = i;
+            break;
+        }
+    }
+
+    if (powerInd > -1) {
+        // There is power
+        baseStr = elementStr.substr(0, powerInd);
+        powerStr = elementStr.substr(powerInd + 1);
+
+        power = static_cast<int32_t>(strToDouble(powerStr));
+    } else {
+        baseStr = elementStr;
+    }
+
+    addSingleFactorConstant(factor, baseStr, power, sigNum);
+}
+
+/*
+ * Extracts `Factor` from a complete string factor. e.g. "ft2m^3*1007/cup2m3*3"
+ */
+Factor extractFactorConversions(StringPiece stringFactor, UErrorCode &status) {
+    Factor result;
+    SigNum sigNum = SigNum::POSITIVE;
+    auto factorData = stringFactor.data();
+    for (int32_t i = 0, start = 0, n = stringFactor.length(); i < n; i++) {
+        if (factorData[i] == '*' || factorData[i] == '/') {
+            StringPiece factorElement = stringFactor.substr(start, i - start);
+            addFactorElement(result, factorElement, sigNum);
+
+            start = i + 1; // Set `start` to point to the start of the new element.
+        } else if (i == n - 1) {
+            // Last element
+            addFactorElement(result, stringFactor.substr(start, i + 1), sigNum);
+        }
+
+        if (factorData[i] == '/')
+            sigNum = SigNum::NEGATIVE; // Change the sigNum because we reached the Denominator.
+    }
+
+    return result;
+}
+
+// Load factor for a single source
+Factor loadSingleFactor(StringPiece source, const MaybeStackVector<ConversionRateInfo> &ratesInfo,
+                        UErrorCode &status) {
+    const auto &conversionUnit = extractConversionInfo(source, ratesInfo, status);
+    if (U_FAILURE(status)) return Factor();
+
+    auto result = extractFactorConversions(conversionUnit.factor.toStringPiece(), status);
+    result.offset = strHasDivideSignToDouble(conversionUnit.offset.toStringPiece());
+
+    // TODO: `reciprocal` should be added to the `ConversionRateInfo`.
+    // result.reciprocal = conversionUnit.reciprocal
+
+    return result;
+}
+
+// Load Factor for compound source
+Factor loadCompoundFactor(const MeasureUnit &source,
+                          const MaybeStackVector<ConversionRateInfo> &ratesInfo, UErrorCode &status) {
+
+    Factor result;
+    auto compoundSourceUnit = MeasureUnitImpl::forMeasureUnitMaybeCopy(source, 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 TempSingleUnit
+
+        Factor singleFactor = loadSingleFactor(singleUnit.identifier, ratesInfo, status);
+
+        // Apply SiPrefix before the power, because the power may be will flip the factor.
+        singleFactor.applySiPrefix(singleUnit.siPrefix);
+
+        // Apply the power of the `dimensionality`
+        singleFactor.power(singleUnit.dimensionality);
+
+        result.multiplyBy(singleFactor);
+    }
+
+    return result;
+}
+
+void substituteSingleConstant(Factor &factor, int32_t constantPower,
+                              double constantValue /* constant actual value, e.g. G= 9.88888 */) {
+    constantValue = std::pow(constantValue, std::abs(constantPower));
+
+    if (constantPower < 0) {
+        factor.factorDen *= constantValue;
+    } else {
+        factor.factorNum *= constantValue;
+    }
+}
+
+void substituteConstants(Factor &factor, UErrorCode &status) {
+    double constantsValues[CONSTANTS_COUNT];
+
+    constantsValues[CONSTANT_FT2M] = 0.3048;
+    constantsValues[CONSTANT_PI] = 411557987.0 / 131002976.0;
+    constantsValues[CONSTANT_GRAVITY] = 9.80665;
+    constantsValues[CONSTANT_G] = 6.67408E-11;
+    constantsValues[CONSTANT_LB2KG] = 0.45359237;
+    constantsValues[CONSTANT_GAL_IMP2M3] = 0.00454609;
+
+    for (int i = 0; i < CONSTANTS_COUNT; i++) {
+        if (factor.constants[i] == 0) continue;
+
+        substituteSingleConstant(factor, factor.constants[i], constantsValues[i]);
+        factor.constants[i] = 0;
+    }
+}
+
+/**
+ * Checks if the source unit and the target unit are simple. For example celsius or fahrenheit. But not
+ * square-celsius or square-fahrenheit.
+ */
+UBool checkSimpleUnit(const MeasureUnit &unit, UErrorCode &status) {
+    auto compoundSourceUnit = MeasureUnitImpl::forMeasureUnitMaybeCopy(unit, status);
+
+    if (U_FAILURE(status)) return false;
+
+    if (compoundSourceUnit.complexity != UMEASURE_UNIT_SINGLE) { return false; }
+
+    U_ASSERT(compoundSourceUnit.units.length() == 1);
+    auto singleUnit = *(compoundSourceUnit.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, UnitsMatchingState unitsState,
+                        const MaybeStackVector<ConversionRateInfo> &ratesInfo, UErrorCode &status) {
+    // Represents the conversion factor from the source to the target.
+    Factor finalFactor;
+
+    // Represents the conversion factor from the source to the base unit that specified in the conversion
+    // data which is considered as the root of the source and the target.
+    Factor sourceToBase = loadCompoundFactor(source, ratesInfo, status);
+    Factor targetToBase = loadCompoundFactor(target, ratesInfo, status);
+
+    // Merger Factors
+    finalFactor.multiplyBy(sourceToBase);
+    if (unitsState == UnitsMatchingState::CONVERTIBLE) {
+        finalFactor.divideBy(targetToBase);
+    } else if (unitsState == UnitsMatchingState::RECIPROCAL) {
+        finalFactor.multiplyBy(targetToBase);
+    } else {
+        status = UErrorCode::U_ARGUMENT_TYPE_MISMATCH;
+        return;
+    }
+
+    // Substitute constants
+    substituteConstants(finalFactor, status);
+
+    conversionRate.factorNum = finalFactor.factorNum;
+    conversionRate.factorDen = finalFactor.factorDen;
+
+    // In case of simple units (such as: celsius or fahrenheit), offsets are considered.
+    if (checkSimpleUnit(source, status) && checkSimpleUnit(target, status)) {
+        conversionRate.sourceOffset =
+            sourceToBase.offset * sourceToBase.factorDen / sourceToBase.factorNum;
+        conversionRate.targetOffset =
+            targetToBase.offset * targetToBase.factorDen / targetToBase.factorNum;
+    }
+
+    conversionRate.reciprocal = unitsState == UnitsMatchingState::RECIPROCAL;
+}
+
 } // namespace
 
 UnitsMatchingState U_I18N_API
@@ -74,6 +421,35 @@ checkUnitsState(const MeasureUnit &source, const MeasureUnit &target,
     return UNCONVERTIBLE;
 }
 
+UnitConverter::UnitConverter(MeasureUnit source, MeasureUnit target,
+                             const MaybeStackVector<ConversionRateInfo> &ratesInfo, UErrorCode &status) {
+    UnitsMatchingState unitsState = checkUnitsState(source, target, ratesInfo, status);
+    if (U_FAILURE(status)) return;
+    if (unitsState == UnitsMatchingState::UNCONVERTIBLE) {
+        status = U_INTERNAL_PROGRAM_ERROR;
+        return;
+    }
+
+    conversionRate_.source = source;
+    conversionRate_.target = target;
+
+    loadConversionRate(conversionRate_, source, target, unitsState, ratesInfo, status);
+}
+
+double UnitConverter::convert(double inputValue) const {
+    double result =
+        inputValue + conversionRate_.sourceOffset; // Reset the input to the target zero index.
+    // Convert the quantity to from the source scale to the target scale.
+    result *= conversionRate_.factorNum / conversionRate_.factorDen;
+
+    result -= conversionRate_.targetOffset; // Set the result to its index.
+
+    if (result == 0)
+        return 0.0; // If the result is zero, it does not matter if the conversion are reciprocal or not.
+    if (conversionRate_.reciprocal) { result = 1.0 / result; }
+    return result;
+}
+
 U_NAMESPACE_END
 
 #endif /* #if !UCONFIG_NO_FORMATTING */
\ No newline at end of file
diff --git a/icu4c/source/i18n/unitconverter.h b/icu4c/source/i18n/unitconverter.h
index 8f110e8ca06..0380483972d 100644
--- a/icu4c/source/i18n/unitconverter.h
+++ b/icu4c/source/i18n/unitconverter.h
@@ -21,10 +21,54 @@ enum U_I18N_API UnitsMatchingState {
     UNCONVERTIBLE,
 };
 
+/**
+ * Represents the conversion rate between `source` and `target`.
+ */
+struct ConversionRate {
+    MeasureUnit source;
+    MeasureUnit target;
+    double factorNum = 1;
+    double factorDen = 1;
+    double sourceOffset = 0;
+    double targetOffset = 0;
+    bool reciprocal = false;
+};
+
 UnitsMatchingState U_I18N_API
 checkUnitsState(const MeasureUnit &source, const MeasureUnit &target,
                 const MaybeStackVector<ConversionRateInfo> &conversionRateInfo, UErrorCode &status);
 
+/**
+ * Converts from a source `MeasureUnit` to a target `MeasureUnit`.
+ */
+class U_I18N_API UnitConverter {
+  public:
+    /**
+     * Constructor of `UnitConverter`.
+     * NOTE:
+     *   - source and target must be under the same category
+     *      - e.g. meter to mile --> both of them are length units.
+     *
+     * @param source represents the source unit.
+     * @param target represents the target unit.
+     * @param status
+     */
+    UnitConverter(MeasureUnit source, MeasureUnit target,
+                  const MaybeStackVector<ConversionRateInfo> &ratesInfo, UErrorCode &status);
+
+    /**
+     * Convert a value in the source unit to another value in the target unit.
+     *
+     * @param input_value the value that needs to be converted.
+     * @param output_value the value that holds the result of the conversion.
+     * @param status
+     */
+    double convert(double inputValue) const;
+
+  private:
+    ConversionRate conversionRate_;
+};
+
 U_NAMESPACE_END
 
 #endif //__UNITCONVERTER_H__
diff --git a/icu4c/source/test/intltest/intltest.cpp b/icu4c/source/test/intltest/intltest.cpp
index 15cd2d30776..51489e8053f 100644
--- a/icu4c/source/test/intltest/intltest.cpp
+++ b/icu4c/source/test/intltest/intltest.cpp
@@ -19,6 +19,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <cmath>
+#include <math.h>
 
 #include "unicode/ctest.h" // for str_timeDelta
 #include "unicode/curramt.h"
@@ -2155,6 +2156,22 @@ UBool IntlTest::assertEquals(const char* message,
     return TRUE;
 }
 
+UBool IntlTest::assertEqualsNear(const char *message, double expected, double actual, double precision) {
+    double diff = std::abs(expected - actual);
+    double diffPercent = expected != 0? diff / expected : diff; // If the expected is equals zero, we 
+
+    if (diffPercent > precision) {
+        errln((UnicodeString) "FAIL: " + message + "; got " + actual + "; expected " + expected);
+        return FALSE;
+    }
+#ifdef VERBOSE_ASSERTIONS
+    else {
+        logln((UnicodeString) "Ok: " + message + "; got " + expected);
+    }
+#endif
+    return TRUE;
+}
+
 static char ASSERT_BUF[256];
 
 static const char* extractToAssertBuf(const UnicodeString& message) {
diff --git a/icu4c/source/test/intltest/intltest.h b/icu4c/source/test/intltest/intltest.h
index 027ebaa45e3..9f4f2790c3d 100644
--- a/icu4c/source/test/intltest/intltest.h
+++ b/icu4c/source/test/intltest/intltest.h
@@ -303,6 +303,7 @@ public:
     UBool assertEquals(const char* message, const UnicodeSet& expected, const UnicodeSet& actual);
     UBool assertEquals(const char* message,
         const std::vector<std::string>& expected, const std::vector<std::string>& actual);
+    UBool assertEqualsNear(const char* message, double expected, double actual, double precision);     
 #if !UCONFIG_NO_FORMATTING
     UBool assertEquals(const char* message, const Formattable& expected,
                        const Formattable& actual, UBool possibleDataError=FALSE);
diff --git a/icu4c/source/test/intltest/unitstest.cpp b/icu4c/source/test/intltest/unitstest.cpp
index ab71310ee73..1a7563d80b8 100644
--- a/icu4c/source/test/intltest/unitstest.cpp
+++ b/icu4c/source/test/intltest/unitstest.cpp
@@ -29,11 +29,11 @@ class UnitsTest : public IntlTest {
     void testConversionCapability();
     void testConversions();
     void testPreferences();
-    // void testBasic();
-    // void testSiPrefixes();
-    // void testMass();
-    // void testTemperature();
-    // void testArea();
+    void testBasic();
+    void testSiPrefixes();
+    void testMass();
+    void testTemperature();
+    void testArea();
 };
 
 extern IntlTest *createUnitsTest() { return new UnitsTest(); }
@@ -44,23 +44,14 @@ void UnitsTest::runIndexedTest(int32_t index, UBool exec, const char *&name, cha
     TESTCASE_AUTO(testConversionCapability);
     TESTCASE_AUTO(testConversions);
     TESTCASE_AUTO(testPreferences);
-    // TESTCASE_AUTO(testBasic);
-    // TESTCASE_AUTO(testSiPrefixes);
-    // TESTCASE_AUTO(testMass);
-    // TESTCASE_AUTO(testTemperature);
-    // TESTCASE_AUTO(testArea);
+    TESTCASE_AUTO(testBasic);
+    TESTCASE_AUTO(testSiPrefixes);
+    TESTCASE_AUTO(testMass);
+    TESTCASE_AUTO(testTemperature);
+    TESTCASE_AUTO(testArea);
     TESTCASE_AUTO_END;
 }
 
-// Just for testing quick conversion ability.
-double testConvert(UnicodeString source, UnicodeString target, double input) {
-    if (source == u"meter" && target == u"foot" && input == 1.0) return 3.28084;
-
-    if (source == u"kilometer" && target == u"foot" && input == 1.0) return 328.084;
-
-    return -1;
-}
-
 void UnitsTest::testConversionCapability() {
     struct TestCase {
         const StringPiece source;
@@ -94,122 +85,188 @@ void UnitsTest::testConversionCapability() {
     }
 }
 
-// void UnitsTest::testBasic() {
-//     IcuTestErrorCode status(*this, "Units testBasic");
-
-//     // Test Cases
-//     struct TestCase {
-//         const char16_t *source;
-//         const char16_t *target;
-//         const double inputValue;
-//         const double expectedValue;
-//     } testCases[]{{u"meter", u"foot", 1.0, 3.28084}, {u"kilometer", u"foot", 1.0, 328.084}};
-
-//     for (const auto &testCase : testCases) {
-//         assertEquals("test convert", testConvert(testCase.source, testCase.target,
-//         testCase.inputValue),
-//                      testCase.expectedValue);
-//     }
-// }
-
-// void UnitsTest::testSiPrefixes() {
-//     IcuTestErrorCode status(*this, "Units testSiPrefixes");
-//     // Test Cases
-//     struct TestCase {
-//         const char16_t *source;
-//         const char16_t *target;
-//         const double inputValue;
-//         const double expectedValue;
-//     } testCases[]{
-//         {u"gram", u"kilogram", 1.0, 0.001},            //
-//         {u"milligram", u"kilogram", 1.0, 0.000001},    //
-//         {u"microgram", u"kilogram", 1.0, 0.000000001}, //
-//         {u"megawatt", u"watt", 1, 1000000},            //
-//         {u"megawatt", u"kilowatt", 1.0, 1000},         //
-//         {u"gigabyte", u"byte", 1, 1000000000}          //
-//     };
-
-//     for (const auto &testCase : testCases) {
-//         assertEquals("test convert", testConvert(testCase.source, testCase.target,
-//         testCase.inputValue),
-//                      testCase.expectedValue);
-//     }
-// }
-
-// void UnitsTest::testMass() {
-//     IcuTestErrorCode status(*this, "Units testMass");
-
-//     // Test Cases
-//     struct TestCase {
-//         const char16_t *source;
-//         const char16_t *target;
-//         const double inputValue;
-//         const double expectedValue;
-//     } testCases[]{
-//         {u"gram", u"kilogram", 1.0, 0.001},      //
-//         {u"pound", u"kilogram", 1.0, 0.453592},  //
-//         {u"pound", u"kilogram", 2.0, 0.907185},  //
-//         {u"ounce", u"pound", 16.0, 1.0},         //
-//         {u"ounce", u"kilogram", 16.0, 0.453592}, //
-//         {u"ton", u"pound", 1.0, 2000},           //
-//         {u"stone", u"pound", 1.0, 14},           //
-//         {u"stone", u"kilogram", 1.0, 6.35029}    //
-//     };
-
-//     for (const auto &testCase : testCases) {
-//         assertEquals("test convert", testConvert(testCase.source, testCase.target,
-//         testCase.inputValue),
-//                      testCase.expectedValue);
-//     }
-// }
-
-// void UnitsTest::testTemperature() {
-//     IcuTestErrorCode status(*this, "Units testTemperature");
-//     // Test Cases
-//     struct TestCase {
-//         const char16_t *source;
-//         const char16_t *target;
-//         const double inputValue;
-//         const double expectedValue;
-//     } testCases[]{
-//         {u"celsius", u"fahrenheit", 0.0, 32.0},   //
-//         {u"celsius", u"fahrenheit", 10.0, 50.0},  //
-//         {u"fahrenheit", u"celsius", 32.0, 0.0},   //
-//         {u"fahrenheit", u"celsius", 89.6, 32},    //
-//         {u"kelvin", u"fahrenheit", 0.0, -459.67}, //
-//         {u"kelvin", u"fahrenheit", 300, 80.33},   //
-//         {u"kelvin", u"celsius", 0.0, -273.15},    //
-//         {u"kelvin", u"celsius", 300.0, 26.85}     //
-//     };
-
-//     for (const auto &testCase : testCases) {
-//         assertEquals("test convert", testConvert(testCase.source, testCase.target,
-//         testCase.inputValue),
-//                      testCase.expectedValue);
-//     }
-// }
-
-// void UnitsTest::testArea() {
-//     IcuTestErrorCode status(*this, "Units Area");
-
-//     // Test Cases
-//     struct TestCase {
-//         const char16_t *source;
-//         const char16_t *target;
-//         const double inputValue;
-//         const double expectedValue;
-//     } testCases[]{
-//         {u"square-meter", u"square-yard", 10.0, 11.9599}, //
-//         {u"hectare", u"square-yard", 1.0, 11959.9},       //
-//         {u"square-mile", u"square-foot", 0.0001, 2787.84} //
-//     };
-
-//     for (const auto &testCase : testCases) {
-//         assertEquals("test convert", testConvert(testCase.source, testCase.target,
-//         testCase.inputValue),
-//                      testCase.expectedValue);
-//     }
-// }
+void UnitsTest::testBasic() {
+    IcuTestErrorCode status(*this, "Units testBasic");
+
+    // Test Cases
+    struct TestCase {
+        StringPiece source;
+        StringPiece target;
+        const double inputValue;
+        const double expectedValue;
+    } testCases[]{
+        {"meter", "foot", 1.0, 3.28084},     //
+        {"kilometer", "foot", 1.0, 3280.84}, //
+    };
+
+    for (const auto &testCase : testCases) {
+        UErrorCode status = U_ZERO_ERROR;
+
+        MeasureUnit source = MeasureUnit::forIdentifier(testCase.source, status);
+        MeasureUnit target = MeasureUnit::forIdentifier(testCase.target, status);
+
+        MaybeStackVector<MeasureUnit> units;
+        units.emplaceBack(source);
+        units.emplaceBack(target);
+
+        const auto &conversionRateInfoList = getConversionRatesInfo(units, status);
+
+        UnitConverter converter(source, target, conversionRateInfoList, status);
+
+        assertEqualsNear("test conversion", testCase.expectedValue,
+                         converter.convert(testCase.inputValue), 0.001);
+    }
+}
+
+void UnitsTest::testSiPrefixes() {
+    IcuTestErrorCode status(*this, "Units testSiPrefixes");
+    // Test Cases
+    struct TestCase {
+        StringPiece source;
+        StringPiece target;
+        const double inputValue;
+        const double expectedValue;
+    } testCases[]{
+        {"gram", "kilogram", 1.0, 0.001},            //
+        {"milligram", "kilogram", 1.0, 0.000001},    //
+        {"microgram", "kilogram", 1.0, 0.000000001}, //
+        {"megagram", "gram", 1.0, 1000000},          //
+        {"megagram", "kilogram", 1.0, 1000},         //
+        {"gigabyte", "byte", 1.0, 1000000000},       //
+        // TODO: Fix `watt` probelms.
+        // {"megawatt", "watt", 1.0, 1000000},          //
+        // {"megawatt", "kilowatt", 1.0, 1000},         //
+    };
+
+    for (const auto &testCase : testCases) {
+        UErrorCode status = U_ZERO_ERROR;
+
+        MeasureUnit source = MeasureUnit::forIdentifier(testCase.source, status);
+        MeasureUnit target = MeasureUnit::forIdentifier(testCase.target, status);
+
+        MaybeStackVector<MeasureUnit> units;
+        units.emplaceBack(source);
+        units.emplaceBack(target);
+
+        const auto &conversionRateInfoList = getConversionRatesInfo(units, status);
+
+        UnitConverter converter(source, target, conversionRateInfoList, status);
+
+        assertEqualsNear("test conversion", testCase.expectedValue,
+                         converter.convert(testCase.inputValue), 0.001);
+    }
+}
+
+void UnitsTest::testMass() {
+    IcuTestErrorCode status(*this, "Units testMass");
+
+    // Test Cases
+    struct TestCase {
+        StringPiece source;
+        StringPiece target;
+        const double inputValue;
+        const double expectedValue;
+    } testCases[]{
+        {"gram", "kilogram", 1.0, 0.001},      //
+        {"pound", "kilogram", 1.0, 0.453592},  //
+        {"pound", "kilogram", 2.0, 0.907185},  //
+        {"ounce", "pound", 16.0, 1.0},         //
+        {"ounce", "kilogram", 16.0, 0.453592}, //
+        {"ton", "pound", 1.0, 2000},           //
+        {"stone", "pound", 1.0, 14},           //
+        {"stone", "kilogram", 1.0, 6.35029}    //
+    };
+
+    for (const auto &testCase : testCases) {
+        UErrorCode status = U_ZERO_ERROR;
+
+        MeasureUnit source = MeasureUnit::forIdentifier(testCase.source, status);
+        MeasureUnit target = MeasureUnit::forIdentifier(testCase.target, status);
+
+        MaybeStackVector<MeasureUnit> units;
+        units.emplaceBack(source);
+        units.emplaceBack(target);
+
+        const auto &conversionRateInfoList = getConversionRatesInfo(units, status);
+
+        UnitConverter converter(source, target, conversionRateInfoList, status);
+
+        assertEqualsNear("test conversion", testCase.expectedValue,
+                         converter.convert(testCase.inputValue), 0.001);
+    }
+}
+
+void UnitsTest::testTemperature() {
+    IcuTestErrorCode status(*this, "Units testTemperature");
+    // Test Cases
+    struct TestCase {
+        StringPiece source;
+        StringPiece target;
+        const double inputValue;
+        const double expectedValue;
+    } testCases[]{
+        {"celsius", "fahrenheit", 0.0, 32.0},   //
+        {"celsius", "fahrenheit", 10.0, 50.0},  //
+        {"fahrenheit", "celsius", 32.0, 0.0},   //
+        {"fahrenheit", "celsius", 89.6, 32},    //
+        {"kelvin", "fahrenheit", 0.0, -459.67}, //
+        {"kelvin", "fahrenheit", 300, 80.33},   //
+        {"kelvin", "celsius", 0.0, -273.15},    //
+        {"kelvin", "celsius", 300.0, 26.85}     //
+    };
+
+    for (const auto &testCase : testCases) {
+        UErrorCode status = U_ZERO_ERROR;
+
+        MeasureUnit source = MeasureUnit::forIdentifier(testCase.source, status);
+        MeasureUnit target = MeasureUnit::forIdentifier(testCase.target, status);
+
+        MaybeStackVector<MeasureUnit> units;
+        units.emplaceBack(source);
+        units.emplaceBack(target);
+
+        const auto &conversionRateInfoList = getConversionRatesInfo(units, status);
+
+        UnitConverter converter(source, target, conversionRateInfoList, status);
+
+        assertEqualsNear("test conversion", testCase.expectedValue,
+                         converter.convert(testCase.inputValue), 0.001);
+    }
+}
+
+void UnitsTest::testArea() {
+    IcuTestErrorCode status(*this, "Units Area");
+
+    // Test Cases
+    struct TestCase {
+        StringPiece source;
+        StringPiece target;
+        const double inputValue;
+        const double expectedValue;
+    } testCases[]{
+        {"square-meter", "square-yard", 10.0, 11.9599}, //
+        {"hectare", "square-yard", 1.0, 11959.9},       //
+        {"square-mile", "square-foot", 0.0001, 2787.84} //
+    };
+
+    for (const auto &testCase : testCases) {
+        UErrorCode status = U_ZERO_ERROR;
+
+        MeasureUnit source = MeasureUnit::forIdentifier(testCase.source, status);
+        MeasureUnit target = MeasureUnit::forIdentifier(testCase.target, status);
+
+        MaybeStackVector<MeasureUnit> units;
+        units.emplaceBack(source);
+        units.emplaceBack(target);
+
+        const auto &conversionRateInfoList = getConversionRatesInfo(units, status);
+
+        UnitConverter converter(source, target, conversionRateInfoList, status);
+
+        assertEqualsNear("test conversion", testCase.expectedValue,
+                         converter.convert(testCase.inputValue), 0.001);
+    }
+}
 
 /**
  * Trims whitespace (spaces only) off of the specified string.
-- 
2.40.0