ICU-21349 Add UnitsConverter.getConversionInfo(...)

See #1568
diff --git a/icu4c/source/i18n/units_converter.cpp b/icu4c/source/i18n/units_converter.cpp
index f4d8f90..710363e 100644
--- a/icu4c/source/i18n/units_converter.cpp
+++ b/icu4c/source/i18n/units_converter.cpp
@@ -593,6 +593,17 @@
     return result;
 }
 
+ConversionInfo UnitsConverter::getConversionInfo() const {
+    ConversionInfo result;
+    result.conversionRate = conversionRate_.factorNum / conversionRate_.factorDen;
+    result.offset =
+        (conversionRate_.sourceOffset * (conversionRate_.factorNum / conversionRate_.factorDen)) -
+        conversionRate_.targetOffset;
+    result.reciprocal = conversionRate_.reciprocal;
+
+    return result;
+}
+
 } // namespace units
 U_NAMESPACE_END
 
diff --git a/icu4c/source/i18n/units_converter.h b/icu4c/source/i18n/units_converter.h
index f492614..e8a0cae 100644
--- a/icu4c/source/i18n/units_converter.h
+++ b/icu4c/source/i18n/units_converter.h
@@ -82,6 +82,12 @@
     void substituteConstants();
 };
 
+struct U_I18N_API ConversionInfo {
+    double conversionRate;
+    double offset;
+    bool reciprocal;
+};
+
 /*
  * Adds a single factor element to the `Factor`. e.g "ft3m", "2.333" or "cup2m3". But not "cup2m3^3".
  */
@@ -181,6 +187,8 @@
      */
     double convertInverse(double inputValue) const;
 
+    ConversionInfo getConversionInfo() const;
+
   private:
     ConversionRate conversionRate_;
 };
diff --git a/icu4c/source/test/intltest/units_test.cpp b/icu4c/source/test/intltest/units_test.cpp
index 1ec8062..93b865a 100644
--- a/icu4c/source/test/intltest/units_test.cpp
+++ b/icu4c/source/test/intltest/units_test.cpp
@@ -45,6 +45,7 @@
 
     void testUnitConstantFreshness();
     void testExtractConvertibility();
+    void testConversionInfo();
     void testConverterWithCLDRTests();
     void testComplexUnitsConverter();
     void testComplexUnitsConverterSorting();
@@ -61,6 +62,7 @@
     TESTCASE_AUTO_BEGIN;
     TESTCASE_AUTO(testUnitConstantFreshness);
     TESTCASE_AUTO(testExtractConvertibility);
+    TESTCASE_AUTO(testConversionInfo);
     TESTCASE_AUTO(testConverterWithCLDRTests);
     TESTCASE_AUTO(testComplexUnitsConverter);
     TESTCASE_AUTO(testComplexUnitsConverterSorting);
@@ -173,6 +175,94 @@
     }
 }
 
+void UnitsTest::testConversionInfo() {
+    IcuTestErrorCode status(*this, "UnitsTest::testExtractConvertibility");
+    // Test Cases
+    struct TestCase {
+        const char *source;
+        const char *target;
+        const ConversionInfo expectedConversionInfo;
+    } testCases[]{
+        {
+            "meter",
+            "meter",
+            {1.0, 0, false},
+        },
+        {
+            "meter",
+            "foot",
+            {3.28084, 0, false},
+        },
+        {
+            "foot",
+            "meter",
+            {0.3048, 0, false},
+        },
+        {
+            "celsius",
+            "kelvin",
+            {1, 273.15, false},
+        },
+        {
+            "fahrenheit",
+            "kelvin",
+            {5.0 / 9.0, 255.372, false},
+        },
+        {
+            "fahrenheit",
+            "celsius",
+            {5.0 / 9.0, -17.7777777778, false},
+        },
+        {
+            "celsius",
+            "fahrenheit",
+            {9.0 / 5.0, 32, false},
+        },
+        {
+            "fahrenheit",
+            "fahrenheit",
+            {1.0, 0, false},
+        },
+        {
+            "mile-per-gallon",
+            "liter-per-100-kilometer",
+            {0.00425143707, 0, true},
+        },
+    };
+
+    ConversionRates rates(status);
+    for (const auto &testCase : testCases) {
+        auto sourceImpl = MeasureUnitImpl::forIdentifier(testCase.source, status);
+        auto targetImpl = MeasureUnitImpl::forIdentifier(testCase.target, status);
+        UnitsConverter unitsConverter(sourceImpl, targetImpl, rates, status);
+
+        if (status.errIfFailureAndReset()) {
+            continue;
+        }
+
+        ConversionInfo actualConversionInfo = unitsConverter.getConversionInfo();
+        UnicodeString message =
+            UnicodeString("testConverter: ") + testCase.source + " to " + testCase.target;
+
+        double maxDelta = 1e-6 * uprv_fabs(testCase.expectedConversionInfo.conversionRate);
+        if (testCase.expectedConversionInfo.conversionRate == 0) {
+            maxDelta = 1e-12;
+        }
+        assertEqualsNear(message + ", conversion rate: ", testCase.expectedConversionInfo.conversionRate,
+                         actualConversionInfo.conversionRate, maxDelta);
+
+        maxDelta = 1e-6 * uprv_fabs(testCase.expectedConversionInfo.offset);
+        if (testCase.expectedConversionInfo.offset == 0) {
+            maxDelta = 1e-12;
+        }
+        assertEqualsNear(message + ", offset: ", testCase.expectedConversionInfo.offset, actualConversionInfo.offset,
+                         maxDelta);
+
+        assertEquals(message + ", reciprocal: ", testCase.expectedConversionInfo.reciprocal,
+                     actualConversionInfo.reciprocal);
+    }
+}
+
 void UnitsTest::testConverter() {
     IcuTestErrorCode status(*this, "UnitsTest::testConverter");
 
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitsConverter.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitsConverter.java
index f4f0b38..59c37217d 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitsConverter.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitsConverter.java
@@ -127,6 +127,21 @@
         UNCONVERTIBLE,
     }
 
+    public ConversionInfo getConversionInfo() {
+        ConversionInfo result = new ConversionInfo();
+        result.conversionRate = this.conversionRate;
+        result.offset = this.offset;
+        result.reciprocal = this.reciprocal;
+
+        return result;
+    }
+
+    public static class ConversionInfo {
+        public BigDecimal conversionRate;
+        public BigDecimal offset;
+        public boolean reciprocal;
+    }
+
     /**
      * Responsible for all the Factor operation
      * NOTE:
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/impl/UnitsTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/impl/UnitsTest.java
index 0c03b38..8155be3 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/impl/UnitsTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/impl/UnitsTest.java
@@ -272,6 +272,93 @@
     }
 
     @Test
+    public void testConversionInfo() {
+        class TestData {
+            String source;
+            String target;
+            UnitsConverter.ConversionInfo expected = new UnitsConverter.ConversionInfo();
+
+            public TestData(String source, String target, double conversionRate, double offset, Boolean reciprocal) {
+                this.source = source;
+                this.target = target;
+                this.expected.conversionRate = BigDecimal.valueOf(conversionRate);
+                this.expected.offset = BigDecimal.valueOf(offset);
+                this.expected.reciprocal = reciprocal;
+            }
+        }
+
+        TestData[] tests = {
+                new TestData(
+                        "meter",
+                        "meter",
+                        1.0, 0, false),
+                new TestData(
+                        "meter",
+                        "foot",
+                        3.28084, 0, false),
+                new TestData(
+                        "foot",
+                        "meter",
+                        0.3048, 0, false),
+                new TestData(
+                        "celsius",
+                        "kelvin",
+                        1, 273.15, false),
+                new TestData(
+                        "fahrenheit",
+                        "kelvin",
+                        5.0 / 9.0, 255.372, false),
+                new TestData(
+                        "fahrenheit",
+                        "celsius",
+                        5.0 / 9.0, -17.7777777778, false),
+                new TestData(
+                        "celsius",
+                        "fahrenheit",
+                        9.0 / 5.0, 32, false),
+                new TestData(
+                        "fahrenheit",
+                        "fahrenheit",
+                        1.0, 0, false),
+                new TestData(
+                        "mile-per-gallon",
+                        "liter-per-100-kilometer",
+                        0.00425143707, 0, true),
+        };
+
+        ConversionRates conversionRates = new ConversionRates();
+        for (TestData test : tests) {
+            MeasureUnitImpl sourceImpl = MeasureUnitImpl.forIdentifier(test.source);
+            MeasureUnitImpl targetImpl = MeasureUnitImpl.forIdentifier(test.target);
+            UnitsConverter unitsConverter = new UnitsConverter(sourceImpl, targetImpl, conversionRates);
+
+            UnitsConverter.ConversionInfo actual = unitsConverter.getConversionInfo();
+
+            // Test conversion Rate
+            double maxDelta = 1e-6 * Math.abs(test.expected.conversionRate.doubleValue());
+            if (test.expected.conversionRate.doubleValue() == 0) {
+                maxDelta = 1e-12;
+            }
+            assertEquals("testConversionInfo for conversion rate: " + test.source + " to " + test.target,
+                    test.expected.conversionRate.doubleValue(), actual.conversionRate.doubleValue(),
+                    maxDelta);
+
+            // Test offset
+            maxDelta = 1e-6 * Math.abs(test.expected.offset.doubleValue());
+            if (test.expected.offset.doubleValue() == 0) {
+                maxDelta = 1e-12;
+            }
+            assertEquals("testConversionInfo for offset: " + test.source + " to " + test.target,
+                    test.expected.offset.doubleValue(), actual.offset.doubleValue(),
+                    maxDelta);
+
+            // Test Reciprocal
+            assertEquals("testConversionInfo for reciprocal: " + test.source + " to " + test.target,
+                    test.expected.reciprocal, actual.reciprocal);
+        }
+    }
+
+    @Test
     public void testConverter() {
         class TestData {
             MeasureUnitImpl source;