ICU-21389 Merge maint/maint-68 to master

diff --git a/KEYS b/KEYS
index b732868..3e293a7 100644
--- a/KEYS
+++ b/KEYS
@@ -898,3 +898,65 @@
 DH2mVgW7fxtB1JdUbJPU1i9vzAONu6ZKyrK4d6/MTGduyO+zZISqVKS7eg==
 =DEHc
 -----END PGP PUBLIC KEY BLOCK-----
+
+pub   rsa3072 2020-10-14 [SC]
+      0E51E7F06EF719FBD072782A5F56E5AFA63CCD33
+uid           [ultimate] Craig Cornelius (For use with ICU releases) <ccornelius@google.com>
+sig 3        5F56E5AFA63CCD33 2020-10-14  Craig Cornelius (For use with ICU releases) <ccornelius@google.com>
+sig 3        9B432B27D1BA20D7 2020-10-14  Fredrik Roubert <fredrik@roubert.name>
+sub   rsa3072 2020-10-14 [E]
+sig          5F56E5AFA63CCD33 2020-10-14  Craig Cornelius (For use with ICU releases) <ccornelius@google.com>
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQGNBF+HGz4BDAC00mXlPPgW5JI9ECxI9juSX/xpzAhM/2NvXzc4Smq7efJMzwvp
+PkfBwdfLavred6rxqjVfXJZuPL4rAY2fKBiMq2ANBRsx6t0Bl1jbZbI/MGb18Qcl
+5EQ/n1I0WslugsZpSX7crj2A+bO42BRWOgroQdpLTrdTjdeS774MaOvt7i3S2Cy/
+YT+U6ysrz0F7wWdiOMUftHEBOrXPJ0s7P3bJGDYsca0sV1o5EmefQWh7cymhroHQ
+UzUnsthmU0Qv8BQ7xc7zsCBrJR0V0ryMww8u/mLexz7i+W522olx78Vj887yC//l
+lAZYI+ORG9hTr3QvcVaF/FaZsbwrA3FPKB+LFWQr/CFa0eSuXWoY5u2g3aL5KoCy
+G/l7x7YlMx7Rw+65YIssZYv39AiZEU3nei+qsaneNc8v6HTX39mMXVdMc5/iWV6A
+pBo+1+wTgaOWMFf5zOg38DyzdQaDz6TNoAgwHbQhz7MDHqLZssy2U/ATnqAPi/Xf
+8oGmQtTh/8zupCsAEQEAAbRDQ3JhaWcgQ29ybmVsaXVzIChGb3IgdXNlIHdpdGgg
+SUNVIHJlbGVhc2VzKSA8Y2Nvcm5lbGl1c0Bnb29nbGUuY29tPokBzgQTAQoAOBYh
+BA5R5/Bu9xn70HJ4Kl9W5a+mPM0zBQJfhxs+AhsDBQsJCAcCBhUKCQgLAgQWAgMB
+Ah4BAheAAAoJEF9W5a+mPM0zJ5IL/2XJ6VHdSdMstzjpnCDjQTYuhFkwEfGSlMrh
+NTZftoQ1qYXuU8DKISgEo1OlguuK61xePn5wl8BfT6XfF2XCZpVUah2fom1m9ZAI
+2gkt/Bwn1ulBKQd3NQpAQKPSKzOVJ+0BBOoBCwoCy+hHr8oeRJGgxz+j8aux10IV
+OHviRkBizx0vnVcy3dN41QLNvtq2cZcKig68sAJPM3t4//co89pH3kjKzBFVCqxF
++INFyaGMyhT1zEhEYB+o6fvYGS4zX9qGRpLzx64beA84DZRmv6aLvjwzhSfA74Ey
+f+FLiAMYae1opWLS+to54WEB5hK2Ak251PqOfWCuR1KVGmQaa4sabT3uQQ4bRh6Y
+yGSXD9qSRJC/W2vNRwkEnKrjwFgH6w2XgHpVLlJiC6Ir7U57AkCfUDVdRCcWQoGN
+ZTuPIHfoj7IehJgR8NZvbHAWViC3FP9DZEm+1druAbc62PngbTUlF6zJrZ/zl90s
+kDg8cNNhQtCdgDKdL/cZwBm/G8RzUIkCMwQTAQgAHRYhBP+pEpoYDXZbelvqHJtD
+KyfRuiDXBQJfhzz/AAoJEJtDKyfRuiDXegYQALsvJzFyxsYUsgr7VYTlyEmjLx2k
+S87toZTgD4zDHprjGT/mRQvzKaMqgp6cwGtnGYLbJi3W17E6ykMk523CWfrqAbqV
+RpMH6nQvxNEbjHCAOAlXZc2zk78lqHkd+UePpDW0Lzwllyf5AK/hf9AvHGZlRSij
+OZEpvRIinwczPcKCyy1RdwO2aeKyjrk98e8SKjpjdl/OXFiuWpLWUXJE/QOkIwbk
+lwUUgHQuauuKZFDDu1T4AGBEAv5yUWU7m6PMa0riGQ7m1pSvTVX9wmV0KgkQ3/sW
+VDqYf3X1VL/NVt4GUBAFTCw04Xzqdb94ERlYde5yFn/wJm7Pnv5pDIlxvGvL+uA6
+e9oGc7msHA2PQipDaAPcvmDT6IQLLuhmO9PA85vZ8egGjKKBey5FE+zRrowWfsLm
+WRXeBPohHjHY2KRto3onFJM4JEydKLtpBAGQ04NAGAhth3opd3jpaPSu6u4rhx4i
+HLiBoJzsWtRTE0lTAeA6Wm0MoH9JQQEt4rgi5kd9BcYMsDfZiR9Fsq2FwEkPwFbi
+1sSNaxW5BWYFKSBhYHOkAFzsxRrD1OAj/anPO/QYfRUu08hUyYi1StTa/0zZ+Cf3
+NY/Q3YJLe/6PJrzxv4wnE7QZs5OSUDaTqL9kOB1mDz6Z3iLnPeOnkBuL6JFF5IUv
+lc9Y1w/UOFefZgGguQGNBF+HGz4BDADO2pKzANvXFSpT5IeMCcAvOIAf8vgJpO84
+BnjQ9/PgiOZMmQW03TIdrY+9wKATOxvUTpC7G+070M438UwlGJX86zjY7CxX+xdi
+yqL9ArK5QJlvaXSvhx1qcArgLIiW+X3qs/U/Y/qXOINS2gZUSYaMBnWHuXutIXSQ
+Liqi3l662nSPvKi7WjpHgi87TV2zG+B9xF23AxMZWth/0vtIevIheoB8vk0mJoos
+8Hmx1qknknDqICT/Flnxm9f1UyDOY+IwWMRyvZZUr+r08vg7qg0TIHkYeMwRMovn
+TZcAPoNvFT1yHsxTdf7mA1yTHCymBvP5aUuXh+FJ+tfPmKGvz65bs0VmlcU10Kl+
+EntF7TniVhCp/QB5M8MA6HKM0zxORFMGZ7xnV9gu6nRPcR+8PvEFxY2wgEcf7pfj
+6wGdS+Pri/dK3+53abE8RLi/aJW+wi2XNd/aRWjpSC2KBmHUk17Zgp3Jb6Iijb+e
+7bcTf+REXgi0+PskNaeCyU4LzStZCwUAEQEAAYkBtgQYAQoAIBYhBA5R5/Bu9xn7
+0HJ4Kl9W5a+mPM0zBQJfhxs+AhsMAAoJEF9W5a+mPM0zULYL/27+ED5a8s43q24Z
+COQQPZflXy+0MnzW4Fwj9f7WsxD/fkj651p/MoRVmugytRti3T8+A4ULUuKxatCZ
++Q+SIobXbWHD0H+hVosBohU6HmWX5Nbyi18wk5YDNWzN4n0duhv6lb4tn3M9utX9
+WdQWnudXNjKzddNiixalc5eU9vMEpEnwKLcmZ/efSimqfGXldMjTw+FIh6wHhBcn
+uVXQqj/+Ie20l4RXt6VxUltZwaQJnH6nf/4T1B4sllNo6Sz8RHjnrc+SjuIREaga
+nRNZ0BF6aAj6qk9RTlkmBGiAdmvtCuGRqqsG2i8OrTv0xArCGEs13Vv5BE6JfZI/
+YdzPuq2u0JM9dsEM6v3LLxhlfxxOmM9OZM4+GBP+JMB3TydK8t7vfLW0kDRvymVM
+uVrQ13BH7D1TWlzM7jN1dToIySXIjGRcMpqT/2LqYDwHfrJCdQZGKDBl5vNWXwNs
+OC7PFtJwtXNl8p7Jvk3lplU5rNSSkTF8hSFgtxu4cpJ5VAY8nQ==
+=FdGa
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/icu4c/source/common/cmemory.h b/icu4c/source/common/cmemory.h
index 210bc76..a9d9424 100644
--- a/icu4c/source/common/cmemory.h
+++ b/icu4c/source/common/cmemory.h
@@ -725,9 +725,14 @@
     }
 
     MemoryPool& operator=(MemoryPool&& other) U_NOEXCEPT {
-        fCount = other.fCount;
-        fPool = std::move(other.fPool);
-        other.fCount = 0;
+        // Since `this` may contain instances that need to be deleted, we can't
+        // just throw them away and replace them with `other`. The normal way of
+        // dealing with this in C++ is to swap `this` and `other`, rather than
+        // simply overwrite: the destruction of `other` can then take care of
+        // running MemoryPool::~MemoryPool() over the still-to-be-deallocated
+        // instances.
+        std::swap(fCount, other.fCount);
+        std::swap(fPool, other.fPool);
         return *this;
     }
 
@@ -796,9 +801,6 @@
 template<typename T, int32_t stackCapacity = 8>
 class MaybeStackVector : protected MemoryPool<T, stackCapacity> {
 public:
-    using MemoryPool<T, stackCapacity>::MemoryPool;
-    using MemoryPool<T, stackCapacity>::operator=;
-
     template<typename... Args>
     T* emplaceBack(Args&&... args) {
         return this->create(args...);
diff --git a/icu4c/source/i18n/measunit.cpp b/icu4c/source/i18n/measunit.cpp
index 02624f8..dab3abb 100644
--- a/icu4c/source/i18n/measunit.cpp
+++ b/icu4c/source/i18n/measunit.cpp
@@ -33,8 +33,7 @@
 // update this code, refer to:
 // http://site.icu-project.org/design/formatting/measureformat/updating-measure-unit
 //
-// Start generated code
-// TODO(ICU-21076): improve how this generated code is produced.
+// Start generated code for measunit.cpp
 
 // Maps from Type ID to offset in gSubTypes.
 static const int32_t gOffsets[] = {
@@ -55,44 +54,15 @@
     404,
     408,
     423,
-    426,
-    432,
-    442,
-    446,
+    424,
+    430,
+    440,
+    444,
+    448,
     450,
-    452,
-    486
+    484
 };
 
-// TODO: FIX CODE GENERATION - leaving this here but commented-out to make it
-// clear that we no longer want this array. We needed it for only one thing: efficient checking of "currency".
-//
-// static const int32_t gIndexes[] = {
-//     0,
-//     2,
-//     7,
-//     17,
-//     25,
-//     29,
-//     29,
-//     40,
-//     56,
-//     60,
-//     69,
-//     71,
-//     75,
-//     83,
-//     105,
-//     109,
-//     124,
-//     127,
-//     133,
-//     143,
-//     147,
-//     151,
-//     153,
-//     187
-// };
 static const int32_t kCurrencyOffset = 5;
 
 // Must be sorted alphabetically.
@@ -547,9 +517,7 @@
     "solar-mass",
     "stone",
     "ton",
-    "", // TODO(ICU-21076): manual edit of what should have been generated by Java.
-    "percent", // TODO(ICU-21076): regenerate, deal with duplication.
-    "permille", // TODO(ICU-21076): regenerate, deal with duplication.
+    "",
     "gigawatt",
     "horsepower",
     "kilowatt",
@@ -612,8 +580,6 @@
     "teaspoon"
 };
 
-// unitPerUnitToSingleUnit no longer in use! TODO: remove from code-generation code.
-
 // Shortcuts to the base unit in order to make the default constructor fast
 static const int32_t kBaseTypeIdx = 16;
 static const int32_t kBaseSubTypeIdx = 0;
@@ -2090,7 +2056,7 @@
     return MeasureUnit(22, 33);
 }
 
-// End generated code
+// End generated code for measunit.cpp
 
 static int32_t binarySearch(
         const char * const * array, int32_t start, int32_t end, StringPiece key) {
@@ -2271,9 +2237,11 @@
 }
 
 bool MeasureUnit::findBySubType(StringPiece subType, MeasureUnit* output) {
+    // Sanity checking kCurrencyOffset and final entry in gOffsets
+    U_ASSERT(uprv_strcmp(gTypes[kCurrencyOffset], "currency") == 0);
+    U_ASSERT(gOffsets[UPRV_LENGTHOF(gOffsets) - 1] == UPRV_LENGTHOF(gSubTypes));
+
     for (int32_t t = 0; t < UPRV_LENGTHOF(gOffsets) - 1; t++) {
-        // Ensure kCurrencyOffset is set correctly
-        U_ASSERT(uprv_strcmp(gTypes[kCurrencyOffset], "currency") == 0);
         // Skip currency units
         if (t == kCurrencyOffset) {
             continue;
@@ -2304,7 +2272,7 @@
     fTypeId = result;
     result = binarySearch(gSubTypes, gOffsets[fTypeId], gOffsets[fTypeId + 1], timeId);
     U_ASSERT(result != -1);
-    fSubTypeId = result - gOffsets[fTypeId]; 
+    fSubTypeId = result - gOffsets[fTypeId];
 }
 
 void MeasureUnit::initCurrency(StringPiece isoCurrency) {
diff --git a/icu4c/source/i18n/number_formatimpl.cpp b/icu4c/source/i18n/number_formatimpl.cpp
index 9a9f3c8..f7e1fcb 100644
--- a/icu4c/source/i18n/number_formatimpl.cpp
+++ b/icu4c/source/i18n/number_formatimpl.cpp
@@ -389,8 +389,12 @@
                 fMixedUnitLongNameHandler.getAlias(), status);
             chain = fMixedUnitLongNameHandler.getAlias();
         } else {
+            MeasureUnit unit = macros.unit;
+            if (!utils::unitIsBaseUnit(macros.perUnit)) {
+                unit = unit.product(macros.perUnit.reciprocal(status), status);
+            }
             fLongNameHandler.adoptInsteadAndCheckErrorCode(new LongNameHandler(), status);
-            LongNameHandler::forMeasureUnit(macros.locale, macros.unit, macros.perUnit, unitWidth,
+            LongNameHandler::forMeasureUnit(macros.locale, unit, unitWidth,
                                             resolvePluralRules(macros.rules, macros.locale, status),
                                             chain, fLongNameHandler.getAlias(), status);
             chain = fLongNameHandler.getAlias();
diff --git a/icu4c/source/i18n/number_longnames.cpp b/icu4c/source/i18n/number_longnames.cpp
index 3891d53..0fa56c7 100644
--- a/icu4c/source/i18n/number_longnames.cpp
+++ b/icu4c/source/i18n/number_longnames.cpp
@@ -218,38 +218,38 @@
 } // namespace
 
 void LongNameHandler::forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef,
-                                     const MeasureUnit &perUnit, const UNumberUnitWidth &width,
-                                     const PluralRules *rules, const MicroPropsGenerator *parent,
-                                     LongNameHandler *fillIn, UErrorCode &status) {
+                                     const UNumberUnitWidth &width, const PluralRules *rules,
+                                     const MicroPropsGenerator *parent, LongNameHandler *fillIn,
+                                     UErrorCode &status) {
     // Not valid for mixed units that aren't built-in units, and there should
     // not be any built-in mixed units!
     U_ASSERT(uprv_strcmp(unitRef.getType(), "") != 0 ||
              unitRef.getComplexity(status) != UMEASURE_UNIT_MIXED);
     U_ASSERT(fillIn != nullptr);
 
-    MeasureUnit unit = unitRef;
-    if (uprv_strcmp(perUnit.getType(), "none") != 0) {
-        // Compound unit: first try to simplify (e.g., meters per second is its own unit).
-        MeasureUnit simplified = unit.product(perUnit.reciprocal(status), status);
-        if (uprv_strcmp(simplified.getType(), "") != 0) {
-            unit = simplified;
-        } else {
-            // No simplified form is available.
-            forCompoundUnit(loc, unit, perUnit, width, rules, parent, fillIn, status);
-            return;
+    if (uprv_strcmp(unitRef.getType(), "") == 0) {
+        // Not a built-in unit. Split it up, since we can already format
+        // "builtin-per-builtin".
+        // TODO(ICU-20941): support more generic case than builtin-per-builtin.
+        MeasureUnitImpl fullUnit = MeasureUnitImpl::forMeasureUnitMaybeCopy(unitRef, status);
+        MeasureUnitImpl unit;
+        MeasureUnitImpl perUnit;
+        for (int32_t i = 0; i < fullUnit.units.length(); i++) {
+            SingleUnitImpl *subUnit = fullUnit.units[i];
+            if (subUnit->dimensionality > 0) {
+                unit.append(*subUnit, status);
+            } else {
+                subUnit->dimensionality *= -1;
+                perUnit.append(*subUnit, status);
+            }
         }
-    }
-
-    if (uprv_strcmp(unit.getType(), "") == 0) {
-        // TODO(ICU-20941): Unsanctioned unit. Not yet fully supported. Set an
-        // error code. Once we support not-built-in units here, unitRef may be
-        // anything, but if not built-in, perUnit has to be "none".
-        status = U_UNSUPPORTED_ERROR;
+        forCompoundUnit(loc, std::move(unit).build(status), std::move(perUnit).build(status), width,
+                        rules, parent, fillIn, status);
         return;
     }
 
     UnicodeString simpleFormats[ARRAY_LENGTH];
-    getMeasureData(loc, unit, width, simpleFormats, status);
+    getMeasureData(loc, unitRef, width, simpleFormats, status);
     if (U_FAILURE(status)) {
         return;
     }
@@ -574,7 +574,7 @@
             result->fHandlers[i] = mlnh;
         } else {
             LongNameHandler *lnh = result->fLongNameHandlers.createAndCheckErrorCode(status);
-            LongNameHandler::forMeasureUnit(loc, unit, MeasureUnit(), width, rules, NULL, lnh, status);
+            LongNameHandler::forMeasureUnit(loc, unit, width, rules, NULL, lnh, status);
             result->fHandlers[i] = lnh;
         }
         if (U_FAILURE(status)) {
diff --git a/icu4c/source/i18n/number_longnames.h b/icu4c/source/i18n/number_longnames.h
index 67f2316..66eb9a9 100644
--- a/icu4c/source/i18n/number_longnames.h
+++ b/icu4c/source/i18n/number_longnames.h
@@ -38,9 +38,6 @@
     /**
      * Construct a localized LongNameHandler for the specified MeasureUnit.
      *
-     * Compound units can be constructed via `unit` and `perUnit`. Both of these
-     * must then be built-in units.
-     *
      * Mixed units are not supported, use MixedUnitLongNameHandler::forMeasureUnit.
      *
      * This function uses a fillIn intead of returning a pointer, because we
@@ -48,15 +45,13 @@
      * didn't create itself).
      *
      * @param loc The desired locale.
-     * @param unit The measure unit to construct a LongNameHandler for. If
-     *     `perUnit` is also defined, `unit` must not be a mixed unit.
-     * @param perUnit If `unit` is a mixed unit, `perUnit` must be "none".
+     * @param unitRef The measure unit to construct a LongNameHandler for.
      * @param width Specifies the desired unit rendering.
      * @param rules Does not take ownership.
      * @param parent Does not take ownership.
      * @param fillIn Required.
      */
-    static void forMeasureUnit(const Locale &loc, const MeasureUnit &unit, const MeasureUnit &perUnit,
+    static void forMeasureUnit(const Locale &loc, const MeasureUnit &unitRef,
                                const UNumberUnitWidth &width, const PluralRules *rules,
                                const MicroPropsGenerator *parent, LongNameHandler *fillIn,
                                UErrorCode &status);
diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp
index 028525a..958bf12 100644
--- a/icu4c/source/i18n/number_skeletons.cpp
+++ b/icu4c/source/i18n/number_skeletons.cpp
@@ -1041,37 +1041,12 @@
     SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status);
 
     ErrorCode internalStatus;
-    auto fullUnit = MeasureUnitImpl::forIdentifier(buffer.toStringPiece(), internalStatus);
+    macros.unit = MeasureUnit::forIdentifier(buffer.toStringPiece(), internalStatus);
     if (internalStatus.isFailure()) {
         // throw new SkeletonSyntaxException("Invalid core unit identifier", segment, e);
         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
         return;
     }
-
-    // Mixed units can only be represented by full MeasureUnit instances, so we
-    // don't split the denominator into macros.perUnit.
-    if (fullUnit.complexity == UMEASURE_UNIT_MIXED) {
-        macros.unit = std::move(fullUnit).build(status);
-        return;
-    }
-
-    // When we have a built-in unit (e.g. meter-per-second), we don't split it up
-    MeasureUnit testBuiltin = fullUnit.copy(status).build(status);
-    if (uprv_strcmp(testBuiltin.getType(), "") != 0) {
-        macros.unit = std::move(testBuiltin);
-        return;
-    }
-
-    // TODO(ICU-20941): Clean this up.
-    for (int32_t i = 0; i < fullUnit.units.length(); i++) {
-        SingleUnitImpl* subUnit = fullUnit.units[i];
-        if (subUnit->dimensionality > 0) {
-            macros.unit = macros.unit.product(subUnit->build(status), status);
-        } else {
-            subUnit->dimensionality *= -1;
-            macros.perUnit = macros.perUnit.product(subUnit->build(status), status);
-        }
-    }
 }
 
 void blueprint_helpers::parseUnitUsageOption(const StringSegment &segment, MacroProps &macros,
diff --git a/icu4c/source/i18n/unicode/measunit.h b/icu4c/source/i18n/unicode/measunit.h
index d86bab3..b9f732a 100644
--- a/icu4c/source/i18n/unicode/measunit.h
+++ b/icu4c/source/i18n/unicode/measunit.h
@@ -23,10 +23,10 @@
 #include "unicode/localpointer.h"
 
 /**
- * \file 
+ * \file
  * \brief C++ API: A unit for measuring a quantity.
  */
- 
+
 U_NAMESPACE_BEGIN
 
 class StringEnumeration;
@@ -35,7 +35,7 @@
 #ifndef U_HIDE_DRAFT_API
 /**
  * Enumeration for unit complexity. There are three levels:
- * 
+ *
  * - SINGLE: A single unit, optionally with a power and/or SI prefix. Examples: hectare,
  *           square-kilometer, kilojoule, per-second.
  * - COMPOUND: A unit composed of the product of multiple single units. Examples:
@@ -58,7 +58,7 @@
 
     /**
      * A compound unit, like meter-per-second.
-     * 
+     *
      * @draft ICU 67
      */
     UMEASURE_UNIT_COMPOUND,
@@ -243,7 +243,7 @@
      * @stable ICU 3.0
      */
     MeasureUnit();
-    
+
     /**
      * Copy constructor.
      * @stable ICU 3.0
@@ -3519,7 +3519,6 @@
      */
     static MeasureUnit getTeaspoon();
 
-
 // End generated createXXX methods
 
  protected:
diff --git a/icu4c/source/i18n/units_complexconverter.cpp b/icu4c/source/i18n/units_complexconverter.cpp
index 27f835e..645c9c0 100644
--- a/icu4c/source/i18n/units_complexconverter.cpp
+++ b/icu4c/source/i18n/units_complexconverter.cpp
@@ -37,28 +37,18 @@
         outputUnits_.emplaceBackAndCheckErrorCode(status, units_[i]->copy(status).build(status));
     }
 
-    // NOTE:
-    //  This comparator is used to sort the units in a descending order. Therefore, we return -1 if
-    //  the left is bigger than right and so on.
+    // Sorts units in 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;
-        }
-
-        return 1; // Less Than
+        return -1 * UnitConverter::compareTwoUnits(**leftPointer,                                  //
+                                                   **rightPointer,                                 //
+                                                   *static_cast<const ConversionRates *>(context), //
+                                                   status);
     };
 
     uprv_sortArray(units_.getAlias(),                                                                  //
diff --git a/icu4c/source/i18n/units_converter.cpp b/icu4c/source/i18n/units_converter.cpp
index a777d02..9c16353 100644
--- a/icu4c/source/i18n/units_converter.cpp
+++ b/icu4c/source/i18n/units_converter.cpp
@@ -502,6 +502,52 @@
                        ratesInfo, status);
 }
 
+int32_t UnitConverter::compareTwoUnits(const MeasureUnitImpl &firstUnit,
+                                       const MeasureUnitImpl &secondUnit,
+                                       const ConversionRates &ratesInfo, UErrorCode &status) {
+    if (U_FAILURE(status)) {
+        return 0;
+    }
+
+    if (firstUnit.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED ||
+        secondUnit.complexity == UMeasureUnitComplexity::UMEASURE_UNIT_MIXED) {
+        status = U_INTERNAL_PROGRAM_ERROR;
+        return 0;
+    }
+
+    Convertibility unitsState = extractConvertibility(firstUnit, secondUnit, ratesInfo, status);
+    if (U_FAILURE(status)) {
+        return 0;
+    }
+
+    if (unitsState == Convertibility::UNCONVERTIBLE || unitsState == Convertibility::RECIPROCAL) {
+        status = U_INTERNAL_PROGRAM_ERROR;
+        return 0;
+    }
+
+    // Represents the conversion factor from the firstUnit to the base unit that specified in the
+    // conversion data which is considered as the root of the firstUnit and the secondUnit.
+    Factor firstUnitToBase = loadCompoundFactor(firstUnit, ratesInfo, status);
+    Factor secondUnitToBase = loadCompoundFactor(secondUnit, ratesInfo, status);
+
+    firstUnitToBase.substituteConstants();
+    secondUnitToBase.substituteConstants();
+
+    double firstUnitToBaseConversionRate = firstUnitToBase.factorNum / firstUnitToBase.factorDen;
+    double secondUnitToBaseConversionRate = secondUnitToBase.factorNum / secondUnitToBase.factorDen;
+
+    double diff = firstUnitToBaseConversionRate - secondUnitToBaseConversionRate;
+    if (diff > 0) {
+        return 1;
+    }
+
+    if (diff < 0) {
+        return -1;
+    }
+
+    return 0;
+}
+
 double UnitConverter::convert(double inputValue) const {
     double result =
         inputValue + conversionRate_.sourceOffset; // Reset the input to the target zero index.
diff --git a/icu4c/source/i18n/units_converter.h b/icu4c/source/i18n/units_converter.h
index 7650131..b2c1ce8 100644
--- a/icu4c/source/i18n/units_converter.h
+++ b/icu4c/source/i18n/units_converter.h
@@ -144,6 +144,16 @@
                   const ConversionRates &ratesInfo, UErrorCode &status);
 
     /**
+     * Compares two single units and returns 1 if the first one is greater, -1 if the second
+     * one is greater and 0 if they are equal.
+     *
+     * NOTE:
+     *  Compares only single units that are convertible.
+     */
+    static int32_t compareTwoUnits(const MeasureUnitImpl &firstUnit, const MeasureUnitImpl &SecondUnit,
+                                   const ConversionRates &ratesInfo, UErrorCode &status);
+
+    /**
      * Convert a measurement expressed in the source unit to a measurement
      * expressed in the target unit.
      *
diff --git a/icu4c/source/test/intltest/measfmttest.cpp b/icu4c/source/test/intltest/measfmttest.cpp
index deada9a..7d5330f 100644
--- a/icu4c/source/test/intltest/measfmttest.cpp
+++ b/icu4c/source/test/intltest/measfmttest.cpp
@@ -17,6 +17,9 @@
 
 #if !UCONFIG_NO_FORMATTING
 
+#include "charstr.h"
+#include "cstr.h"
+#include "measunit_impl.h"
 #include "unicode/decimfmt.h"
 #include "unicode/measfmt.h"
 #include "unicode/measure.h"
@@ -25,8 +28,6 @@
 #include "unicode/tmunit.h"
 #include "unicode/plurrule.h"
 #include "unicode/ustring.h"
-#include "charstr.h"
-#include "cstr.h"
 #include "unicode/reldatefmt.h"
 #include "unicode/rbnf.h"
 
@@ -88,6 +89,7 @@
     void TestDimensionlessBehaviour();
     void Test21060_AddressSanitizerProblem();
     void Test21223_FrenchDuration();
+    void TestInternalMeasureUnitImpl();
 
     void verifyFormat(
         const char *description,
@@ -216,6 +218,7 @@
     TESTCASE_AUTO(TestDimensionlessBehaviour);
     TESTCASE_AUTO(Test21060_AddressSanitizerProblem);
     TESTCASE_AUTO(Test21223_FrenchDuration);
+    TESTCASE_AUTO(TestInternalMeasureUnitImpl);
     TESTCASE_AUTO_END;
 }
 
@@ -4037,6 +4040,56 @@
     // }
 }
 
+void MeasureFormatTest::TestInternalMeasureUnitImpl() {
+    IcuTestErrorCode status(*this, "TestInternalMeasureUnitImpl");
+    MeasureUnitImpl mu1 = MeasureUnitImpl::forIdentifier("meter", status);
+    status.assertSuccess();
+    assertEquals("mu1 initial identifier", "", mu1.identifier.data());
+    assertEquals("mu1 initial complexity", UMEASURE_UNIT_SINGLE, mu1.complexity);
+    assertEquals("mu1 initial units length", 1, mu1.units.length());
+    assertEquals("mu1 initial units[0]", "meter", mu1.units[0]->getSimpleUnitID());
+
+    // Producing identifier via build(): the std::move() means mu1 gets modified
+    // while it also gets assigned to tmp's internal fImpl.
+    MeasureUnit tmp = std::move(mu1).build(status);
+    status.assertSuccess();
+    assertEquals("mu1 post-move-build identifier", "meter", mu1.identifier.data());
+    assertEquals("mu1 post-move-build complexity", UMEASURE_UNIT_SINGLE, mu1.complexity);
+    assertEquals("mu1 post-move-build units length", 1, mu1.units.length());
+    assertEquals("mu1 post-move-build units[0]", "meter", mu1.units[0]->getSimpleUnitID());
+    assertEquals("MeasureUnit tmp identifier", "meter", tmp.getIdentifier());
+
+    // This temporary variable is used when forMeasureUnit's first parameter
+    // lacks an fImpl instance:
+    MeasureUnitImpl tmpMemory;
+    const MeasureUnitImpl &tmpImplRef = MeasureUnitImpl::forMeasureUnit(tmp, tmpMemory, status);
+    status.assertSuccess();
+    assertEquals("tmpMemory identifier", "", tmpMemory.identifier.data());
+    assertEquals("tmpMemory complexity", UMEASURE_UNIT_SINGLE, tmpMemory.complexity);
+    assertEquals("tmpMemory units length", 1, tmpMemory.units.length());
+    assertEquals("tmpMemory units[0]", "meter", tmpMemory.units[0]->getSimpleUnitID());
+    assertEquals("tmpImplRef identifier", "", tmpImplRef.identifier.data());
+    assertEquals("tmpImplRef complexity", UMEASURE_UNIT_SINGLE, tmpImplRef.complexity);
+
+    MeasureUnitImpl mu2 = MeasureUnitImpl::forIdentifier("newton-meter", status);
+    status.assertSuccess();
+    mu1 = std::move(mu2);
+    assertEquals("mu1 = move(mu2): identifier", "", mu1.identifier.data());
+    assertEquals("mu1 = move(mu2): complexity", UMEASURE_UNIT_COMPOUND, mu1.complexity);
+    assertEquals("mu1 = move(mu2): units length", 2, mu1.units.length());
+    assertEquals("mu1 = move(mu2): units[0]", "newton", mu1.units[0]->getSimpleUnitID());
+    assertEquals("mu1 = move(mu2): units[1]", "meter", mu1.units[1]->getSimpleUnitID());
+
+    mu1 = MeasureUnitImpl::forIdentifier("hour-and-minute-and-second", status);
+    status.assertSuccess();
+    assertEquals("mu1 = HMS: identifier", "", mu1.identifier.data());
+    assertEquals("mu1 = HMS: complexity", UMEASURE_UNIT_MIXED, mu1.complexity);
+    assertEquals("mu1 = HMS: units length", 3, mu1.units.length());
+    assertEquals("mu1 = HMS: units[0]", "hour", mu1.units[0]->getSimpleUnitID());
+    assertEquals("mu1 = HMS: units[1]", "minute", mu1.units[1]->getSimpleUnitID());
+    assertEquals("mu1 = HMS: units[2]", "second", mu1.units[2]->getSimpleUnitID());
+}
+
 
 void MeasureFormatTest::verifyFieldPosition(
         const char *description,
diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp
index e653fbf..a57cf3d 100644
--- a/icu4c/source/test/intltest/numbertest_api.cpp
+++ b/icu4c/source/test/intltest/numbertest_api.cpp
@@ -876,22 +876,21 @@
             u"0.008765 J/fur",
             u"0 J/fur");
 
-    // TODO(ICU-20941): Support constructions such as this one.
-    // assertFormatDescending(
-    //         u"Joules Per Furlong Short with unit identifier via API",
-    //         u"measure-unit/energy-joule per-measure-unit/length-furlong",
-    //         u"unit/joule-per-furlong",
-    //         NumberFormatter::with().unit(MeasureUnit::forIdentifier("joule-per-furlong", status)),
-    //         Locale::getEnglish(),
-    //         u"87,650 J/fur",
-    //         u"8,765 J/fur",
-    //         u"876.5 J/fur",
-    //         u"87.65 J/fur",
-    //         u"8.765 J/fur",
-    //         u"0.8765 J/fur",
-    //         u"0.08765 J/fur",
-    //         u"0.008765 J/fur",
-    //         u"0 J/fur");
+    assertFormatDescending(
+            u"Joules Per Furlong Short with unit identifier via API",
+            u"measure-unit/energy-joule per-measure-unit/length-furlong",
+            u"unit/joule-per-furlong",
+            NumberFormatter::with().unit(MeasureUnit::forIdentifier("joule-per-furlong", status)),
+            Locale::getEnglish(),
+            u"87,650 J/fur",
+            u"8,765 J/fur",
+            u"876.5 J/fur",
+            u"87.65 J/fur",
+            u"8.765 J/fur",
+            u"0.8765 J/fur",
+            u"0.08765 J/fur",
+            u"0.008765 J/fur",
+            u"0 J/fur");
 
     assertFormatDescending(
             u"Pounds per Square Inch: composed",
diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp
index dc193f2..add630a 100644
--- a/icu4c/source/test/intltest/numfmtst.cpp
+++ b/icu4c/source/test/intltest/numfmtst.cpp
@@ -248,6 +248,7 @@
   TESTCASE_AUTO(Test21134_ToNumberFormatter);
   TESTCASE_AUTO(Test13733_StrictAndLenient);
   TESTCASE_AUTO(Test21232_ParseTimeout);
+  TESTCASE_AUTO(Test10997_FormatCurrency);
   TESTCASE_AUTO_END;
 }
 
@@ -9183,7 +9184,7 @@
   pFormat->format(2.155, actual);
   assertEquals("Should round percent toward even number", "216%", actual);
 }
-  
+
 void NumberFormatTest::Test11839() {
     IcuTestErrorCode errorCode(*this, "Test11839");
     // Ticket #11839: DecimalFormat does not respect custom plus sign
@@ -10063,4 +10064,27 @@
     // Should not hang
 }
 
+void NumberFormatTest::Test10997_FormatCurrency() {
+    IcuTestErrorCode status(*this, "Test10997_FormatCurrency");
+
+    UErrorCode error = U_ZERO_ERROR;
+    NumberFormat* fmt = NumberFormat::createCurrencyInstance(Locale::getUS(), error);
+    fmt->setMinimumFractionDigits(4);
+    fmt->setMaximumFractionDigits(4);
+
+    FieldPosition fp;
+
+    UnicodeString str;
+    Formattable usdAmnt(new CurrencyAmount(123.45, u"USD", status));
+    fmt->format(usdAmnt, str, fp, status);
+    assertEquals("minFrac 4 should be respected in default currency", u"$123.4500", str);
+
+    UnicodeString str2;
+    Formattable eurAmnt(new CurrencyAmount(123.45, u"EUR", status));
+    fmt->format(eurAmnt, str2, fp, status);
+    assertEquals("minFrac 4 should be respected in different currency", u"€123.4500", str2);
+
+    delete fmt;
+}
+
 #endif /* #if !UCONFIG_NO_FORMATTING */
diff --git a/icu4c/source/test/intltest/numfmtst.h b/icu4c/source/test/intltest/numfmtst.h
index 24ca15a..c92067a 100644
--- a/icu4c/source/test/intltest/numfmtst.h
+++ b/icu4c/source/test/intltest/numfmtst.h
@@ -304,6 +304,7 @@
     void Test21134_ToNumberFormatter();
     void Test13733_StrictAndLenient();
     void Test21232_ParseTimeout();
+    void Test10997_FormatCurrency();
 
  private:
     UBool testFormattableAsUFormattable(const char *file, int line, Formattable &f);
diff --git a/icu4c/source/test/intltest/units_test.cpp b/icu4c/source/test/intltest/units_test.cpp
index 5e248d5..c88116a 100644
--- a/icu4c/source/test/intltest/units_test.cpp
+++ b/icu4c/source/test/intltest/units_test.cpp
@@ -16,6 +16,7 @@
 #include "putilimp.h"
 #include "unicode/ctest.h"
 #include "unicode/measunit.h"
+#include "unicode/measure.h"
 #include "unicode/unistr.h"
 #include "unicode/unum.h"
 #include "unicode/ures.h"
@@ -43,15 +44,12 @@
     void runIndexedTest(int32_t index, UBool exec, const char *&name, char *par = NULL);
 
     void testUnitConstantFreshness();
-    void testConversionCapability();
-    void testConversions();
+    void testExtractConvertibility();
+    void testConverterWithCLDRTests();
     void testComplexUnitsConverter();
     void testComplexUnitConverterSorting();
-    void testPreferences();
-    void testSiPrefixes();
-    void testMass();
-    void testTemperature();
-    void testArea();
+    void testUnitPreferencesWithCLDRTests();
+    void testConverter();
 };
 
 extern IntlTest *createUnitsTest() { return new UnitsTest(); }
@@ -62,15 +60,12 @@
     }
     TESTCASE_AUTO_BEGIN;
     TESTCASE_AUTO(testUnitConstantFreshness);
-    TESTCASE_AUTO(testConversionCapability);
-    TESTCASE_AUTO(testConversions);
+    TESTCASE_AUTO(testExtractConvertibility);
+    TESTCASE_AUTO(testConverterWithCLDRTests);
     TESTCASE_AUTO(testComplexUnitsConverter);
     TESTCASE_AUTO(testComplexUnitConverterSorting);
-    TESTCASE_AUTO(testPreferences);
-    TESTCASE_AUTO(testSiPrefixes);
-    TESTCASE_AUTO(testMass);
-    TESTCASE_AUTO(testTemperature);
-    TESTCASE_AUTO(testArea);
+    TESTCASE_AUTO(testUnitPreferencesWithCLDRTests);
+    TESTCASE_AUTO(testConverter);
     TESTCASE_AUTO_END;
 }
 
@@ -122,7 +117,9 @@
     }
 }
 
-void UnitsTest::testConversionCapability() {
+void UnitsTest::testExtractConvertibility() {
+    IcuTestErrorCode status(*this, "UnitsTest::testExtractConvertibility");
+
     struct TestCase {
         const char *const source;
         const char *const target;
@@ -137,16 +134,37 @@
         {"square-hectare", "pow4-foot", CONVERTIBLE},                                //
         {"square-kilometer-per-second", "second-per-square-meter", RECIPROCAL},      //
         {"cubic-kilometer-per-second-meter", "second-per-square-meter", RECIPROCAL}, //
+        {"square-meter-per-square-hour", "hectare-per-square-second", CONVERTIBLE},  //
+        {"hertz", "revolution-per-second", CONVERTIBLE},                             //
+        {"millimeter", "meter", CONVERTIBLE},                                        //
+        {"yard", "meter", CONVERTIBLE},                                              //
+        {"ounce-troy", "kilogram", CONVERTIBLE},                                     //
+        {"percent", "portion", CONVERTIBLE},                                         //
+        {"ofhg", "kilogram-per-square-meter-square-second", CONVERTIBLE},            //
+        {"second-per-meter", "meter-per-second", RECIPROCAL},                        //
     };
 
     for (const auto &testCase : testCases) {
-        UErrorCode status = U_ZERO_ERROR;
-
         MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status);
+        if (status.errIfFailureAndReset("source MeasureUnitImpl::forIdentifier(\"%s\", ...)",
+                                        testCase.source)) {
+            continue;
+        }
         MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status);
+        if (status.errIfFailureAndReset("target MeasureUnitImpl::forIdentifier(\"%s\", ...)",
+                                        testCase.target)) {
+            continue;
+        }
 
         ConversionRates conversionRates(status);
+        if (status.errIfFailureAndReset("conversionRates(status)")) {
+            continue;
+        }
         auto convertibility = extractConvertibility(source, target, conversionRates, status);
+        if (status.errIfFailureAndReset("extractConvertibility(<%s>, <%s>, ...)", testCase.source,
+                                        testCase.target)) {
+            continue;
+        }
 
         assertEquals(UnicodeString("Conversion Capability: ") + testCase.source + " to " +
                          testCase.target,
@@ -154,43 +172,8 @@
     }
 }
 
-void UnitsTest::testSiPrefixes() {
-    IcuTestErrorCode status(*this, "Units testSiPrefixes");
-    // Test Cases
-    struct TestCase {
-        const char *source;
-        const char *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;
-
-        MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status);
-        MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status);
-
-        ConversionRates conversionRates(status);
-        UnitConverter converter(source, target, conversionRates, status);
-
-        assertEqualsNear(UnicodeString("testSiPrefixes: ") + testCase.source + " to " + testCase.target,
-                         testCase.expectedValue, converter.convert(testCase.inputValue),
-                         0.0001 * testCase.expectedValue);
-    }
-}
-
-void UnitsTest::testMass() {
-    IcuTestErrorCode status(*this, "Units testMass");
+void UnitsTest::testConverter() {
+    IcuTestErrorCode status(*this, "UnitsTest::testConverter");
 
     // Test Cases
     struct TestCase {
@@ -199,100 +182,77 @@
         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}    //
+        // SI Prefixes
+        {"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},
+        {"megawatt", "watt", 1.0, 1000000},
+        {"megawatt", "kilowatt", 1.0, 1000},
+        // Mass
+        {"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},
+        // Temperature
+        {"celsius", "fahrenheit", 0.0, 32.0},
+        {"celsius", "fahrenheit", 10.0, 50.0},
+        {"celsius", "fahrenheit", 1000, 1832},
+        {"fahrenheit", "celsius", 32.0, 0.0},
+        {"fahrenheit", "celsius", 89.6, 32},
+        {"fahrenheit", "fahrenheit", 1000, 1000},
+        {"kelvin", "fahrenheit", 0.0, -459.67},
+        {"kelvin", "fahrenheit", 300, 80.33},
+        {"kelvin", "celsius", 0.0, -273.15},
+        {"kelvin", "celsius", 300.0, 26.85},
+        // Area
+        {"square-meter", "square-yard", 10.0, 11.9599},
+        {"hectare", "square-yard", 1.0, 11959.9},
+        {"square-mile", "square-foot", 0.0001, 2787.84},
+        {"hectare", "square-yard", 1.0, 11959.9},
+        {"hectare", "square-meter", 1.0, 10000},
+        {"hectare", "square-meter", 0.0, 0.0},
+        {"square-mile", "square-foot", 0.0001, 2787.84},
+        {"square-yard", "square-foot", 10, 90},
+        {"square-yard", "square-foot", 0, 0},
+        {"square-yard", "square-foot", 0.000001, 0.000009},
+        {"square-mile", "square-foot", 0.0, 0.0},
     };
 
     for (const auto &testCase : testCases) {
-        UErrorCode status = U_ZERO_ERROR;
-
         MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status);
+        if (status.errIfFailureAndReset("source MeasureUnitImpl::forIdentifier(\"%s\", ...)",
+                                        testCase.source)) {
+            continue;
+        }
         MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status);
+        if (status.errIfFailureAndReset("target MeasureUnitImpl::forIdentifier(\"%s\", ...)",
+                                        testCase.target)) {
+            continue;
+        }
 
         ConversionRates conversionRates(status);
+        if (status.errIfFailureAndReset("conversionRates(status)")) {
+            continue;
+        }
         UnitConverter converter(source, target, conversionRates, status);
+        if (status.errIfFailureAndReset("UnitConverter(<%s>, <%s>, ...)", testCase.source,
+                                        testCase.target)) {
+            continue;
+        }
 
-        assertEqualsNear(UnicodeString("testMass: ") + testCase.source + " to " + testCase.target,
-                         testCase.expectedValue, converter.convert(testCase.inputValue),
-                         0.0001 * testCase.expectedValue);
-    }
-}
-
-void UnitsTest::testTemperature() {
-    IcuTestErrorCode status(*this, "Units testTemperature");
-    // Test Cases
-    struct TestCase {
-        const char *source;
-        const char *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;
-
-        MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status);
-        MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status);
-
-        ConversionRates conversionRates(status);
-        UnitConverter converter(source, target, conversionRates, status);
-
-        assertEqualsNear(UnicodeString("testTemperature: ") + testCase.source + " to " + testCase.target,
-                         testCase.expectedValue, converter.convert(testCase.inputValue),
-                         0.0001 * uprv_fabs(testCase.expectedValue));
-    }
-}
-
-void UnitsTest::testArea() {
-    IcuTestErrorCode status(*this, "Units Area");
-
-    // Test Cases
-    struct TestCase {
-        const char *source;
-        const char *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},    //
-        {"hectare", "square-yard", 1.0, 11959.9},           //
-        {"hectare", "square-meter", 1.0, 10000},            //
-        {"hectare", "square-meter", 0.0, 0.0},              //
-        {"square-mile", "square-foot", 0.0001, 2787.84},    //
-        {"square-yard", "square-foot", 10, 90},             //
-        {"square-yard", "square-foot", 0, 0},               //
-        {"square-yard", "square-foot", 0.000001, 0.000009}, //
-        {"square-mile", "square-foot", 0.0, 0.0},           //
-    };
-
-    for (const auto &testCase : testCases) {
-        UErrorCode status = U_ZERO_ERROR;
-
-        MeasureUnitImpl source = MeasureUnitImpl::forIdentifier(testCase.source, status);
-        MeasureUnitImpl target = MeasureUnitImpl::forIdentifier(testCase.target, status);
-
-        ConversionRates conversionRates(status);
-        UnitConverter converter(source, target, conversionRates, status);
-
-        assertEqualsNear(UnicodeString("testArea: ") + testCase.source + " to " + testCase.target,
-                         testCase.expectedValue, converter.convert(testCase.inputValue),
-                         0.0001 * testCase.expectedValue);
+        double maxDelta = 1e-6 * uprv_fabs(testCase.expectedValue);
+        if (testCase.expectedValue == 0) {
+            maxDelta = 1e-12;
+        }
+        assertEqualsNear(UnicodeString("testConverter: ") + testCase.source + " to " + testCase.target,
+                         testCase.expectedValue, converter.convert(testCase.inputValue), maxDelta);
     }
 }
 
@@ -400,8 +360,8 @@
 
     // Conversion:
     UnitConverter converter(sourceUnit, targetUnit, *ctx->conversionRates, status);
-    if (status.errIfFailureAndReset("constructor: UnitConverter(<%s>, <%s>, status)",
-                                    sourceIdent.data(), targetIdent.data())) {
+    if (status.errIfFailureAndReset("UnitConverter(<%s>, <%s>, ...)", sourceIdent.data(),
+                                    targetIdent.data())) {
         return;
     }
     double got = converter.convert(1000);
@@ -418,12 +378,12 @@
  * Runs data-driven unit tests for unit conversion. It looks for the test cases
  * in source/test/testdata/cldr/units/unitsTest.txt, which originates in CLDR.
  */
-void UnitsTest::testConversions() {
+void UnitsTest::testConverterWithCLDRTests() {
     const char *filename = "unitsTest.txt";
     const int32_t kNumFields = 5;
     char *fields[kNumFields][2];
 
-    IcuTestErrorCode errorCode(*this, "UnitsTest::testConversions");
+    IcuTestErrorCode errorCode(*this, "UnitsTest::testConverterWithCLDRTests");
     const char *sourceTestDataPath = getSourceTestData(errorCode);
     if (errorCode.errIfFailureAndReset("unable to find the source/test/testdata "
                                        "folder (getSourceTestData())")) {
@@ -444,96 +404,129 @@
 
 void UnitsTest::testComplexUnitsConverter() {
     IcuTestErrorCode status(*this, "UnitsTest::testComplexUnitsConverter");
+
+    struct TestCase {
+        const char* msg;
+        const char* input;
+        const char* output;
+        double value;
+        Measure expected[2];
+        int32_t expectedCount;
+        // For mixed units, accuracy of the smallest unit
+        double accuracy;
+    } testCases[]{
+        // Significantly less than 2.0.
+        {"1.9999",
+         "foot",
+         "foot-and-inch",
+         1.9999,
+         {Measure(1, MeasureUnit::createFoot(status), status),
+          Measure(11.9988, MeasureUnit::createInch(status), status)},
+         2,
+         0},
+
+        // TODO(icu-units#108): reconsider whether desireable to round up:
+        // A minimal nudge under 2.0, rounding up to 2.0 ft, 0 in.
+        {"2-eps",
+         "foot",
+         "foot-and-inch",
+         2.0 - DBL_EPSILON,
+         {Measure(2, MeasureUnit::createFoot(status), status),
+          Measure(0, MeasureUnit::createInch(status), status)},
+         2,
+         0},
+
+        // Testing precision with meter and light-year. 1e-16 light years is
+        // 0.946073 meters, and double precision can provide only ~15 decimal
+        // digits, so we don't expect to get anything less than 1 meter.
+
+        // TODO(icu-units#108): reconsider whether desireable to round up:
+        // A nudge under 2.0 light years, rounding up to 2.0 ly, 0 m.
+        {"2-eps",
+         "light-year",
+         "light-year-and-meter",
+         2.0 - DBL_EPSILON,
+         {Measure(2, MeasureUnit::createLightYear(status), status),
+          Measure(0, MeasureUnit::createMeter(status), status)},
+         2,
+         0},
+
+        // TODO(icu-units#108): reconsider whether desireable to round up:
+        // A nudge under 1.0 light years, rounding up to 1.0 ly, 0 m.
+        {"1-eps",
+         "light-year",
+         "light-year-and-meter",
+         1.0 - DBL_EPSILON,
+         {Measure(1, MeasureUnit::createLightYear(status), status),
+          Measure(0, MeasureUnit::createMeter(status), status)},
+         2,
+         0},
+
+        // 1e-15 light years is 9.46073 meters (calculated using "bc" and the
+        // CLDR conversion factor). With double-precision maths in C++, we get
+        // 10.5. In this case, we're off by a bit more than 1 meter. With Java
+        // BigDecimal, we get accurate results.
+        {"1 + 1e-15",
+         "light-year",
+         "light-year-and-meter",
+         1.0 + 1e-15,
+         {Measure(1, MeasureUnit::createLightYear(status), status),
+          Measure(9.46073, MeasureUnit::createMeter(status), status)},
+         2,
+         1.5 /* meters, precision */},
+
+        // TODO(icu-units#108): reconsider whether epsilon rounding is desirable:
+        //
+        // 2e-16 light years is 1.892146 meters. For C++ double, we consider
+        // this in the noise, and thus expect a 0. (This test fails when
+        // 2e-16 is increased to 4e-16.) For Java, using BigDecimal, we
+        // actually get a good result.
+        {"1 + 2e-16",
+         "light-year",
+         "light-year-and-meter",
+         1.0 + 2e-16,
+         {Measure(1, MeasureUnit::createLightYear(status), status),
+          Measure(0, MeasureUnit::createMeter(status), status)},
+         2,
+         0},
+    };
+    status.assertSuccess();
+
     ConversionRates rates(status);
-    MeasureUnit input = MeasureUnit::getFoot();
-    MeasureUnit output = MeasureUnit::forIdentifier("foot-and-inch", status);
+    MeasureUnit input, output;
     MeasureUnitImpl tempInput, tempOutput;
-    const MeasureUnitImpl &inputImpl = MeasureUnitImpl::forMeasureUnit(input, tempInput, status);
-    const MeasureUnitImpl &outputImpl = MeasureUnitImpl::forMeasureUnit(output, tempOutput, status);
-    auto converter = ComplexUnitsConverter(inputImpl, outputImpl, rates, status);
+    MaybeStackVector<Measure> measures;
+    for (const TestCase &testCase : testCases) {
+        input = MeasureUnit::forIdentifier(testCase.input, status);
+        output = MeasureUnit::forIdentifier(testCase.output, status);
+        const MeasureUnitImpl& inputImpl = MeasureUnitImpl::forMeasureUnit(input, tempInput, status);
+        const MeasureUnitImpl& outputImpl = MeasureUnitImpl::forMeasureUnit(output, tempOutput, status);
+        auto converter = ComplexUnitsConverter(inputImpl, outputImpl, rates, status);
+        measures = converter.convert(testCase.value, nullptr, status);
 
-    // Significantly less than 2.0.
-    MaybeStackVector<Measure> measures = converter.convert(1.9999, nullptr, status);
-    assertEquals("measures length", 2, measures.length());
-    if (2 == measures.length()) {
-        assertEquals("1.9999: measures[0] value", 1.0, measures[0]->getNumber().getDouble(status));
-        assertEquals("1.9999: measures[0] unit", MeasureUnit::getFoot().getIdentifier(),
-                     measures[0]->getUnit().getIdentifier());
-        assertEqualsNear("1.9999: measures[1] value", 11.9988, measures[1]->getNumber().getDouble(status), 0.0001);
-        assertEquals("1.9999: measures[1] unit", MeasureUnit::getInch().getIdentifier(),
-                     measures[1]->getUnit().getIdentifier());
+        CharString msg;
+        msg.append(testCase.msg, status);
+        msg.append(" ", status);
+        msg.append(testCase.input, status);
+        msg.append(" -> ", status);
+        msg.append(testCase.output, status);
+
+        CharString msgCount(msg, status);
+        msgCount.append(", measures.length()", status);
+        assertEquals(msgCount.data(), testCase.expectedCount, measures.length());
+        for (int i = 0; i < measures.length() && i < testCase.expectedCount; i++) {
+            if (i == testCase.expectedCount-1) {
+                assertEqualsNear(msg.data(), testCase.expected[i].getNumber().getDouble(status),
+                                 measures[i]->getNumber().getDouble(status), testCase.accuracy);
+            } else {
+                assertEquals(msg.data(), testCase.expected[i].getNumber().getDouble(status),
+                             measures[i]->getNumber().getDouble(status));
+            }
+            assertEquals(msg.data(), testCase.expected[i].getUnit().getIdentifier(),
+                         measures[i]->getUnit().getIdentifier());
+        }
     }
-
-    // TODO(icu-units#100): consider factoring out the set of tests to make this function more
-    // data-driven, *after* dealing appropriately with the memory leaks that can
-    // be demonstrated by this code.
-
-    // TODO(icu-units#100): reusing measures results in a leak.
-    // A minimal nudge under 2.0.
-    MaybeStackVector<Measure> measures2 = converter.convert((2.0 - DBL_EPSILON), nullptr, status);
-    assertEquals("measures length", 2, measures2.length());
-    if (2 == measures2.length()) {
-        assertEquals("1 - eps: measures[0] value", 2.0, measures2[0]->getNumber().getDouble(status));
-        assertEquals("1 - eps: measures[0] unit", MeasureUnit::getFoot().getIdentifier(),
-                     measures2[0]->getUnit().getIdentifier());
-        assertEquals("1 - eps: measures[1] value", 0.0, measures2[1]->getNumber().getDouble(status));
-        assertEquals("1 - eps: measures[1] unit", MeasureUnit::getInch().getIdentifier(),
-                     measures2[1]->getUnit().getIdentifier());
-    }
-
-    // Testing precision with meter and light-year. 1e-16 light years is
-    // 0.946073 meters, and double precision can provide only ~15 decimal
-    // digits, so we don't expect to get anything less than 1 meter.
-
-    // An epsilon's nudge under one light-year: should give 1 ly, 0 m.
-    input = MeasureUnit::getLightYear();
-    output = MeasureUnit::forIdentifier("light-year-and-meter", status);
-    // TODO(icu-units#100): reusing tempInput and tempOutput results in a leak.
-    MeasureUnitImpl tempInput3, tempOutput3;
-    const MeasureUnitImpl &inputImpl3 = MeasureUnitImpl::forMeasureUnit(input, tempInput3, status);
-    const MeasureUnitImpl &outputImpl3 = MeasureUnitImpl::forMeasureUnit(output, tempOutput3, status);
-    // TODO(icu-units#100): reusing converter results in a leak.
-    ComplexUnitsConverter converter3 = ComplexUnitsConverter(inputImpl3, outputImpl3, rates, status);
-    // TODO(icu-units#100): reusing measures results in a leak.
-    MaybeStackVector<Measure> measures3 = converter3.convert((2.0 - DBL_EPSILON), nullptr, status);
-    assertEquals("measures length", 2, measures3.length());
-    if (2 == measures3.length()) {
-        assertEquals("light-year test: measures[0] value", 2.0, measures3[0]->getNumber().getDouble(status));
-        assertEquals("light-year test: measures[0] unit", MeasureUnit::getLightYear().getIdentifier(),
-                     measures3[0]->getUnit().getIdentifier());
-        assertEquals("light-year test: measures[1] value", 0.0, measures3[1]->getNumber().getDouble(status));
-        assertEquals("light-year test: measures[1] unit", MeasureUnit::getMeter().getIdentifier(),
-                     measures3[1]->getUnit().getIdentifier());
-    }
-
-    // 1e-15 light years is 9.46073 meters (calculated using "bc" and the CLDR
-    // conversion factor). With double-precision maths, we get 10.5. In this
-    // case, we're off by almost 1 meter.
-    MaybeStackVector<Measure> measures4 = converter3.convert((1.0 + 1e-15), nullptr, status);
-    assertEquals("measures length", 2, measures4.length());
-    if (2 == measures4.length()) {
-        assertEquals("light-year test: measures[0] value", 1.0, measures4[0]->getNumber().getDouble(status));
-        assertEquals("light-year test: measures[0] unit", MeasureUnit::getLightYear().getIdentifier(),
-                     measures4[0]->getUnit().getIdentifier());
-        assertEqualsNear("light-year test: measures[1] value", 10,
-                         measures4[1]->getNumber().getDouble(status), 1);
-        assertEquals("light-year test: measures[1] unit", MeasureUnit::getMeter().getIdentifier(),
-                     measures4[1]->getUnit().getIdentifier());
-    }
-
-    // 2e-16 light years is 1.892146 meters. We consider this in the noise, and
-    // thus expect a 0. (This test fails when 2e-16 is increased to 4e-16.)
-    MaybeStackVector<Measure> measures5 = converter3.convert((1.0 + 2e-16), nullptr, status);
-    assertEquals("measures length", 2, measures5.length());
-    if (2 == measures5.length()) {
-        assertEquals("light-year test: measures[0] value", 1.0, measures5[0]->getNumber().getDouble(status));
-        assertEquals("light-year test: measures[0] unit", MeasureUnit::getLightYear().getIdentifier(),
-                     measures5[0]->getUnit().getIdentifier());
-        assertEquals("light-year test: measures[1] value", 0.0,
-                         measures5[1]->getNumber().getDouble(status));
-        assertEquals("light-year test: measures[1] unit", MeasureUnit::getMeter().getIdentifier(),
-                     measures5[1]->getUnit().getIdentifier());
-    }
+    status.assertSuccess();
 
     // TODO(icu-units#63): test negative numbers!
 }
@@ -862,12 +855,12 @@
  * in source/test/testdata/cldr/units/unitPreferencesTest.txt, which originates
  * in CLDR.
  */
-void UnitsTest::testPreferences() {
+void UnitsTest::testUnitPreferencesWithCLDRTests() {
     const char *filename = "unitPreferencesTest.txt";
     const int32_t maxFields = 11;
     char *fields[maxFields][2];
 
-    IcuTestErrorCode errorCode(*this, "UnitsTest::testPreferences");
+    IcuTestErrorCode errorCode(*this, "UnitsTest::testUnitPreferencesWithCLDRTests");
     const char *sourceTestDataPath = getSourceTestData(errorCode);
     if (errorCode.errIfFailureAndReset("unable to find the source/test/testdata "
                                        "folder (getSourceTestData())")) {
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java
index 9d972e4..13084a7 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameHandler.java
@@ -13,6 +13,8 @@
 import com.ibm.icu.impl.StandardPlural;
 import com.ibm.icu.impl.UResource;
 import com.ibm.icu.impl.number.Modifier.Signum;
+import com.ibm.icu.impl.units.MeasureUnitImpl;
+import com.ibm.icu.impl.units.SingleUnitImpl;
 import com.ibm.icu.number.NumberFormatter.UnitWidth;
 import com.ibm.icu.text.NumberFormat;
 import com.ibm.icu.text.PluralRules;
@@ -195,15 +197,10 @@
     /**
      * Construct a localized LongNameHandler for the specified MeasureUnit.
      * <p>
-     * Compound units can be constructed via `unit` and `perUnit`. Both of these
-     * must then be built-in units.
-     * <p>
      * Mixed units are not supported, use MixedUnitLongNameHandler.forMeasureUnit.
      *
      * @param locale The desired locale.
-     * @param unit The measure unit to construct a LongNameHandler for. If
-     *     `perUnit` is also defined, `unit` must not be a mixed unit.
-     * @param perUnit If `unit` is a mixed unit, `perUnit` must be null.
+     * @param unit The measure unit to construct a LongNameHandler for.
      * @param width Specifies the desired unit rendering.
      * @param rules Plural rules.
      * @param parent Plural rules.
@@ -211,25 +208,34 @@
     public static LongNameHandler forMeasureUnit(
             ULocale locale,
             MeasureUnit unit,
-            MeasureUnit perUnit,
             UnitWidth width,
             PluralRules rules,
             MicroPropsGenerator parent) {
-        if (perUnit != null) {
-            // Compound unit: first try to simplify (e.g., meters per second is its own unit).
-            MeasureUnit simplified = unit.product(perUnit.reciprocal());
-            if (simplified.getType() != null) {
-                unit = simplified;
-            } else {
-                // No simplified form is available.
-                return forCompoundUnit(locale, unit, perUnit, width, rules, parent);
-            }
-        }
-
         if (unit.getType() == null) {
-            // TODO(ICU-20941): Unsanctioned unit. Not yet fully supported.
-            throw new UnsupportedOperationException("Unsanctioned unit, not yet supported: " +
-                                                    unit.getIdentifier());
+            // Not a built-in unit. Split it up, since we can already format
+            // "builtin-per-builtin".
+            // TODO(ICU-20941): support more generic case than builtin-per-builtin.
+            MeasureUnitImpl fullUnit = unit.getCopyOfMeasureUnitImpl();
+            unit = null;
+            MeasureUnit perUnit = null;
+            for (SingleUnitImpl subUnit : fullUnit.getSingleUnits()) {
+                if (subUnit.getDimensionality() > 0) {
+                    if (unit == null) {
+                        unit = subUnit.build();
+                    } else {
+                        unit = unit.product(subUnit.build());
+                    }
+                } else {
+                    // It's okay to mutate fullUnit, we made a temporary copy:
+                    subUnit.setDimensionality(subUnit.getDimensionality() * -1);
+                    if (perUnit == null) {
+                        perUnit = subUnit.build();
+                    } else {
+                        perUnit = perUnit.product(subUnit.build());
+                    }
+                }
+            }
+            return forCompoundUnit(locale, unit, perUnit, width, rules, parent);
         }
 
         String[] simpleFormats = new String[ARRAY_LENGTH];
@@ -336,6 +342,7 @@
      * Does not call parent.processQuantity, so cannot get a MicroProps instance
      * that way. Instead, the instance is passed in as a parameter.
      */
+    @Override
     public MicroProps processQuantityWithMicros(DecimalQuantity quantity, MicroProps micros) {
         StandardPlural pluralForm = RoundingUtils.getPluralSafe(micros.rounder, rules, quantity);
         micros.modOuter = modifiers.get(pluralForm);
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameMultiplexer.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameMultiplexer.java
index 963f637..044a48f 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameMultiplexer.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/LongNameMultiplexer.java
@@ -8,7 +8,6 @@
 import com.ibm.icu.number.NumberFormatter;
 import com.ibm.icu.text.PluralRules;
 import com.ibm.icu.util.MeasureUnit;
-import com.ibm.icu.util.NoUnit;
 import com.ibm.icu.util.ULocale;
 
 /**
@@ -64,8 +63,7 @@
                         .forMeasureUnit(locale, unit, width, rules, null);
                 result.fHandlers.add(mlnh);
             } else {
-                LongNameHandler lnh = LongNameHandler
-                        .forMeasureUnit(locale, unit, NoUnit.BASE, width, rules, null);
+                LongNameHandler lnh = LongNameHandler.forMeasureUnit(locale, unit, width, rules, null);
                 result.fHandlers.add(lnh);
             }
         }
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/MeasureUnitImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/MeasureUnitImpl.java
index ba1dae1..cdaca18 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/MeasureUnitImpl.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/MeasureUnitImpl.java
@@ -745,8 +745,9 @@
 
         @Override
         public int compare(MeasureUnitImpl o1, MeasureUnitImpl o2) {
-            UnitConverter fromO1toO2 = new UnitConverter(o1, o2, conversionRates);
-            return fromO1toO2.convert(BigDecimal.valueOf(1)).compareTo(BigDecimal.valueOf(1));
+            BigDecimal factor1 = this.conversionRates.getFactorToBase(o1).getConversionRate();
+            BigDecimal factor2 = this.conversionRates.getFactorToBase(o2).getConversionRate();
+            return factor1.compareTo(factor2);
         }
     }
 
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java
index b639c8f..abcb141 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java
@@ -400,12 +400,11 @@
                         pluralRules,
                         chain);
             } else {
-                chain = LongNameHandler.forMeasureUnit(macros.loc,
-                        macros.unit,
-                        macros.perUnit,
-                        unitWidth,
-                        pluralRules,
-                        chain);
+                MeasureUnit unit = macros.unit;
+                if (macros.perUnit != null) {
+                    unit = unit.product(macros.perUnit.reciprocal());
+                }
+                chain = LongNameHandler.forMeasureUnit(macros.loc, unit, unitWidth, pluralRules, chain);
             }
         } else if (isCurrency && unitWidth == UnitWidth.FULL_NAME) {
             if (rules == null) {
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java
index 9b0f1ed..ab40477 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java
@@ -12,8 +12,6 @@
 import com.ibm.icu.impl.StringSegment;
 import com.ibm.icu.impl.number.MacroProps;
 import com.ibm.icu.impl.number.RoundingUtils;
-import com.ibm.icu.impl.units.MeasureUnitImpl;
-import com.ibm.icu.impl.units.SingleUnitImpl;
 import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
 import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
 import com.ibm.icu.number.NumberFormatter.SignDisplay;
@@ -1070,45 +1068,11 @@
          * specified via a "unit/" concise skeleton.
          */
         private static void parseIdentifierUnitOption(StringSegment segment, MacroProps macros) {
-            MeasureUnitImpl fullUnit;
             try {
-                fullUnit = MeasureUnitImpl.forIdentifier(segment.asString());
+                macros.unit = MeasureUnit.forIdentifier(segment.asString());
             } catch (IllegalArgumentException e) {
                 throw new SkeletonSyntaxException("Invalid unit stem", segment);
             }
-
-            // Mixed units can only be represented by full MeasureUnit instances, so we
-            // don't split the denominator into macros.perUnit.
-            if (fullUnit.getComplexity() == MeasureUnit.Complexity.MIXED) {
-                macros.unit = fullUnit.build();
-                return;
-            }
-
-            // When we have a built-in unit (e.g. meter-per-second), we don't split it up
-            MeasureUnit testBuiltin = fullUnit.build();
-            if (testBuiltin.getType() != null) {
-                macros.unit = testBuiltin;
-                return;
-            }
-
-            // TODO(ICU-20941): Clean this up.
-            for (SingleUnitImpl subUnit : fullUnit.getSingleUnits()) {
-                if (subUnit.getDimensionality() > 0) {
-                    if (macros.unit == null) {
-                        macros.unit = subUnit.build();
-                    } else {
-                        macros.unit = macros.unit.product(subUnit.build());
-                    }
-                } else {
-                    // It's okay to mutate fullUnit, we're throwing it away after this:
-                    subUnit.setDimensionality(subUnit.getDimensionality() * -1);
-                    if (macros.perUnit == null) {
-                        macros.perUnit = subUnit.build();
-                    } else {
-                        macros.perUnit = macros.perUnit.product(subUnit.build());
-                    }
-                }
-            }
         }
 
         private static void parseUnitUsageOption(StringSegment segment, MacroProps macros) {
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/util/MeasureUnit.java b/icu4j/main/classes/core/src/com/ibm/icu/util/MeasureUnit.java
index e3de81b..40c0770 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/util/MeasureUnit.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/util/MeasureUnit.java
@@ -542,7 +542,7 @@
             return implCopy.build();
         }
 
-        final MeasureUnitImpl otherImplRef = other.getMayBeReferenceOfMeasureUnitImpl();
+        final MeasureUnitImpl otherImplRef = other.getMaybeReferenceOfMeasureUnitImpl();
         if (implCopy.getComplexity() == Complexity.MIXED || otherImplRef.getComplexity() == Complexity.MIXED) {
             throw new UnsupportedOperationException();
         }
@@ -571,7 +571,8 @@
      * @provisional This API might change or be removed in a future release.
      */
     public List<MeasureUnit> splitToSingleUnits() {
-        final ArrayList<SingleUnitImpl> singleUnits = getMayBeReferenceOfMeasureUnitImpl().getSingleUnits();
+        final ArrayList<SingleUnitImpl> singleUnits =
+            getMaybeReferenceOfMeasureUnitImpl().getSingleUnits();
         List<MeasureUnit> result = new ArrayList<>(singleUnits.size());
         for (SingleUnitImpl singleUnit : singleUnits) {
             result.add(singleUnit.build());
@@ -1306,7 +1307,7 @@
      * Constant for unit of graphics: dot
      * @draft ICU 68
      * @provisional This API might change or be removed in a future release.
-    */
+     */
     public static final MeasureUnit DOT = MeasureUnit.internalGetInstance("graphics", "dot");
 
     /**
@@ -1373,7 +1374,7 @@
      * Constant for unit of length: earth-radius
      * @draft ICU 68
      * @provisional This API might change or be removed in a future release.
-    */
+     */
     public static final MeasureUnit EARTH_RADIUS = MeasureUnit.internalGetInstance("length", "earth-radius");
 
     /**
@@ -1488,14 +1489,14 @@
      * Constant for unit of light: candela
      * @draft ICU 68
      * @provisional This API might change or be removed in a future release.
-    */
+     */
     public static final MeasureUnit CANDELA = MeasureUnit.internalGetInstance("light", "candela");
 
     /**
      * Constant for unit of light: lumen
      * @draft ICU 68
      * @provisional This API might change or be removed in a future release.
-    */
+     */
     public static final MeasureUnit LUMEN = MeasureUnit.internalGetInstance("light", "lumen");
 
     /**
@@ -1532,7 +1533,7 @@
      * Constant for unit of mass: grain
      * @draft ICU 68
      * @provisional This API might change or be removed in a future release.
-    */
+     */
     public static final MeasureUnit GRAIN = MeasureUnit.internalGetInstance("mass", "grain");
 
     /**
@@ -1845,28 +1846,28 @@
      * Constant for unit of volume: dessert-spoon
      * @draft ICU 68
      * @provisional This API might change or be removed in a future release.
-    */
+     */
     public static final MeasureUnit DESSERT_SPOON = MeasureUnit.internalGetInstance("volume", "dessert-spoon");
 
     /**
      * Constant for unit of volume: dessert-spoon-imperial
      * @draft ICU 68
      * @provisional This API might change or be removed in a future release.
-    */
+     */
     public static final MeasureUnit DESSERT_SPOON_IMPERIAL = MeasureUnit.internalGetInstance("volume", "dessert-spoon-imperial");
 
     /**
      * Constant for unit of volume: dram
      * @draft ICU 68
      * @provisional This API might change or be removed in a future release.
-    */
+     */
     public static final MeasureUnit DRAM = MeasureUnit.internalGetInstance("volume", "dram");
 
     /**
      * Constant for unit of volume: drop
      * @draft ICU 68
      * @provisional This API might change or be removed in a future release.
-    */
+     */
     public static final MeasureUnit DROP = MeasureUnit.internalGetInstance("volume", "drop");
 
     /**
@@ -1903,7 +1904,7 @@
      * Constant for unit of volume: jigger
      * @draft ICU 68
      * @provisional This API might change or be removed in a future release.
-    */
+     */
     public static final MeasureUnit JIGGER = MeasureUnit.internalGetInstance("volume", "jigger");
 
     /**
@@ -1928,7 +1929,7 @@
      * Constant for unit of volume: pinch
      * @draft ICU 68
      * @provisional This API might change or be removed in a future release.
-    */
+     */
     public static final MeasureUnit PINCH = MeasureUnit.internalGetInstance("volume", "pinch");
 
     /**
@@ -1953,7 +1954,7 @@
      * Constant for unit of volume: quart-imperial
      * @draft ICU 68
      * @provisional This API might change or be removed in a future release.
-    */
+     */
     public static final MeasureUnit QUART_IMPERIAL = MeasureUnit.internalGetInstance("volume", "quart-imperial");
 
     /**
@@ -1968,9 +1969,8 @@
      */
     public static final MeasureUnit TEASPOON = MeasureUnit.internalGetInstance("volume", "teaspoon");
 
-    // unitPerUnitToSingleUnit no longer in use! TODO: remove from code-generation code.
-
     // End generated MeasureUnit constants
+
     /* Private */
 
     private Object writeReplace() throws ObjectStreamException {
@@ -1993,8 +1993,11 @@
     /**
      *
      * @return this object in a MeasureUnitImpl form.
+     * @internal
+     * @deprecated This API is ICU internal only.
      */
-    private MeasureUnitImpl getCopyOfMeasureUnitImpl() {
+    @Deprecated
+    public MeasureUnitImpl getCopyOfMeasureUnitImpl() {
         return this.measureUnitImpl == null ?
                 MeasureUnitImpl.forIdentifier(getIdentifier()) :
                 this.measureUnitImpl.copy();
@@ -2004,7 +2007,7 @@
      *
      * @return this object in a MeasureUnitImpl form.
      */
-    private MeasureUnitImpl getMayBeReferenceOfMeasureUnitImpl(){
+    private MeasureUnitImpl getMaybeReferenceOfMeasureUnitImpl() {
         return this.measureUnitImpl == null ?
                 MeasureUnitImpl.forIdentifier(getIdentifier()) :
                 this.measureUnitImpl;
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/MeasureUnitTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/MeasureUnitTest.java
index a7f706c..3033f85 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/MeasureUnitTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/MeasureUnitTest.java
@@ -2898,6 +2898,7 @@
     // for MeasureFormat during the release process.
     static void generateCXXHConstants(String thisVersion) {
         Map<String, MeasureUnit> seen = new HashMap<>();
+        System.out.println("// Start generated createXXX methods");
         System.out.println();
         TreeMap<String, List<MeasureUnit>> allUnits = getAllUnits();
         for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
@@ -2929,13 +2930,15 @@
                 System.out.println("    /**");
                 System.out.println("     * Returns by value, unit of " + type + ": " + code + ".");
                 System.out.printf("     * Also see {@link #create%s()}.\n", name);
-                // TODO: When the get* methods become stable in ICU 66, update their
-                // @draft code to be more like that for the create* methods above.
                 String getterVersion = getVersion(javaName, thisVersion);
                 if (Integer.valueOf(getterVersion) < 64) {
                     getterVersion = "64";
                 }
-                System.out.println("     * @draft ICU " + getterVersion);
+                if (isDraft(javaName)) {
+                    System.out.println("     * @draft ICU " + getterVersion);
+                } else {
+                    System.out.println("     * @stable ICU " + getterVersion);
+                }
                 System.out.println("     */");
                 System.out.printf("    static MeasureUnit get%s();\n", name);
                 if (isDraft(javaName)) {
@@ -2944,6 +2947,7 @@
                 System.out.println("");
             }
         }
+        System.out.println("// End generated createXXX methods");
     }
 
     private static void checkForDup(
@@ -2998,34 +3002,34 @@
     // DO NOT DELETE THIS FUNCTION! It may appear as dead code, but we use this to generate code
     // for MeasureFormat during the release process.
     static void generateCXXConstants() {
+        System.out.println("// Start generated code for measunit.cpp");
         System.out.println("");
         TreeMap<String, List<MeasureUnit>> allUnits = getAllUnits();
 
-        // Hack: for C++, add NoUnits here, but ignore them when printing the create methods.
-        // ALso keep track of the base unit offset to make the C++ default constructor faster.
-        allUnits.put("none", Arrays.asList(new MeasureUnit[]{NoUnit.BASE, NoUnit.PERCENT, NoUnit.PERMILLE}));
+        // Hack: for C++, add base unit here, but ignore them when printing the create methods.
+        // Also keep track of the base unit offset to make the C++ default constructor faster.
+        allUnits.put("none", Arrays.asList(new MeasureUnit[] {NoUnit.BASE}));
         int baseTypeIdx = -1;
         int baseSubTypeIdx = -1;
 
+        System.out.println("// Maps from Type ID to offset in gSubTypes.");
         System.out.println("static const int32_t gOffsets[] = {");
         int index = 0;
+        int typeCount = 0;
+        int currencyIndex = -1;
         for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
             System.out.printf("    %d,\n", index);
+            if (entry.getKey() == "currency") {
+                currencyIndex = typeCount;
+            }
+            typeCount++;
             index += entry.getValue().size();
         }
+        assertTrue("currency present", currencyIndex >= 0);
         System.out.printf("    %d\n", index);
         System.out.println("};");
         System.out.println();
-        System.out.println("static const int32_t gIndexes[] = {");
-        index = 0;
-        for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
-            System.out.printf("    %d,\n", index);
-            if (!entry.getKey().equals("currency")) {
-                index += entry.getValue().size();
-            }
-        }
-        System.out.printf("    %d\n", index);
-        System.out.println("};");
+        System.out.println("static const int32_t kCurrencyOffset = " + currencyIndex + ";");
         System.out.println();
         System.out.println("// Must be sorted alphabetically.");
         System.out.println("static const char * const gTypes[] = {");
@@ -3054,7 +3058,12 @@
                 if (!first) {
                     System.out.println(",");
                 }
-                System.out.print("    \"" + unit.getSubtype() + "\"");
+                if (unit != null) {
+                    System.out.print("    \"" + unit.getSubtype() + "\"");
+                } else {
+                    assertEquals("unit only null for \"none\" type", "none", entry.getKey());
+                    System.out.print("    \"\"");
+                }
                 first = false;
                 measureUnitToOffset.put(unit, offset);
                 measureUnitToTypeSubType.put(unit, Pair.of(typeIdx, subTypeIdx));
@@ -3085,27 +3094,6 @@
                     measureUnitToTypeSubType.get(entry.getKey()));
         }
 
-        System.out.println("// Must be sorted by first value and then second value.");
-        System.out.println("static int32_t unitPerUnitToSingleUnit[][4] = {");
-        first = true;
-        for (Map.Entry<OrderedPair<Integer, Integer>, Pair<Integer, Integer>> entry
-                : unitPerUnitOffsetsToTypeSubType.entrySet()) {
-            if (!first) {
-                System.out.println(",");
-            }
-            first = false;
-            OrderedPair<Integer, Integer> unitPerUnitOffsets = entry.getKey();
-            Pair<Integer, Integer> typeSubType = entry.getValue();
-            System.out.printf("        {%d, %d, %d, %d}",
-                    unitPerUnitOffsets.first,
-                    unitPerUnitOffsets.second,
-                    typeSubType.first,
-                    typeSubType.second);
-        }
-        System.out.println();
-        System.out.println("};");
-        System.out.println();
-
         // Print out the fast-path for the default constructor
         System.out.println("// Shortcuts to the base unit in order to make the default constructor fast");
         System.out.println("static const int32_t kBaseTypeIdx = " + baseTypeIdx + ";");
@@ -3138,6 +3126,7 @@
                 System.out.println();
             }
         }
+        System.out.println("// End generated code for measunit.cpp");
     }
 
     private static String toCamelCase(MeasureUnit unit) {
@@ -3243,6 +3232,7 @@
     // DO NOT DELETE THIS FUNCTION! It may appear as dead code, but we use this to generate code
     // for MeasureFormat during the release process.
     static void generateConstants(String thisVersion) {
+        System.out.println("    // Start generated MeasureUnit constants");
         System.out.println();
         Map<String, MeasureUnit> seen = new HashMap<>();
         TreeMap<String, List<MeasureUnit>> allUnits = getAllUnits();
@@ -3269,7 +3259,7 @@
                 } else {
                     System.out.println("     * @stable ICU " + getVersion(name, thisVersion));
                 }
-                System.out.println("    */");
+                System.out.println("     */");
                 if ("duration".equals(type) && TIME_CODES.contains(code)) {
                     System.out.println("    public static final TimeUnit " + name + " = (TimeUnit) MeasureUnit.internalGetInstance(\"" +
                             type +
@@ -3286,16 +3276,7 @@
                 System.out.println();
             }
         }
-        System.out.println("    private static HashMap<Pair<MeasureUnit, MeasureUnit>, MeasureUnit>unitPerUnitToSingleUnit =");
-        System.out.println("            new HashMap<Pair<MeasureUnit, MeasureUnit>, MeasureUnit>();");
-        System.out.println();
-        System.out.println("    static {");
-        for (Map.Entry<MeasureUnit, Pair<MeasureUnit, MeasureUnit>> unitPerUnitEntry
-                : getUnitsToPerParts().entrySet()) {
-            Pair<MeasureUnit, MeasureUnit> unitPerUnit = unitPerUnitEntry.getValue();
-            System.out.println("        unitPerUnitToSingleUnit.put(Pair.<MeasureUnit, MeasureUnit>of(MeasureUnit." + toJAVAName(unitPerUnit.first) + ", MeasureUnit." + toJAVAName(unitPerUnit.second) + "), MeasureUnit." + toJAVAName(unitPerUnitEntry.getKey()) + ");");
-        }
-        System.out.println("    }");
+        System.out.println("    // End generated MeasureUnit constants");
     }
 
     private static String getVersion(String javaName, String thisVersion) {
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 564debe..554ed6b 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
@@ -40,88 +40,106 @@
 
     @Test
     public void testComplexUnitsConverter() {
+        class TestCase {
+            String input;
+            String output;
+            BigDecimal value;
+            Measure[] expected;
+            // For mixed units, accuracy of the smallest unit
+            double accuracy;
+
+            TestCase(String input, String output, BigDecimal value, Measure[] expected, double accuracy) {
+                this.input = input;
+                this.output = output;
+                this.value = value;
+                this.expected = expected;
+                this.accuracy = accuracy;
+            }
+        }
+        TestCase[] testCases = new TestCase[] {
+            // Significantly less than 2.0.
+            new TestCase(
+                "foot", "foot-and-inch", BigDecimal.valueOf(1.9999),
+                new Measure[] {new Measure(1, MeasureUnit.FOOT), new Measure(11.9988, MeasureUnit.INCH)},
+                0),
+
+            // A minimal nudge under 2.0, rounding up to 2.0 ft, 0 in.
+            new TestCase(
+                "foot", "foot-and-inch", BigDecimal.valueOf(2.0).subtract(ComplexUnitsConverter.EPSILON),
+                new Measure[] {new Measure(2, MeasureUnit.FOOT), new Measure(0, MeasureUnit.INCH)}, 0),
+
+            // Testing precision with meter and light-year. 1e-16 light years is
+            // 0.946073 meters, and double precision can provide only ~15 decimal
+            // digits, so we don't expect to get anything less than 1 meter.
+
+            // TODO(icu-units#108): figure out precision thresholds for BigDecimal?
+            // A nudge under 2.0 light years, rounding up to 2.0 ly, 0 m.
+            new TestCase("light-year", "light-year-and-meter",
+                         BigDecimal.valueOf(2.0).subtract(ComplexUnitsConverter.EPSILON),
+                         new Measure[] {new Measure(2, MeasureUnit.LIGHT_YEAR),
+                                        new Measure(0, MeasureUnit.METER)},
+                         0),
+
+            // // TODO(icu-units#108): figure out precision thresholds for BigDecimal?
+            // // This test is passing in C++ but failing in Java:
+            // // A nudge under 1.0 light years, rounding up to 1.0 ly, 0 m.
+            // new TestCase("light-year", "light-year-and-meter",
+            //              BigDecimal.valueOf(1.0).subtract(ComplexUnitsConverter.EPSILON),
+            //              new Measure[] {new Measure(1, MeasureUnit.LIGHT_YEAR),
+            //                             new Measure(0, MeasureUnit.METER)},
+            //              0),
+
+            // 1e-15 light years is 9.46073 meters (calculated using "bc" and
+            // the CLDR conversion factor). With double-precision maths in C++,
+            // we get 10.5. In this case, we're off by a bit more than 1 meter.
+            // With Java BigDecimal, we get accurate results.
+            new TestCase("light-year", "light-year-and-meter", BigDecimal.valueOf(1.0 + 1e-15),
+                         new Measure[] {new Measure(1, MeasureUnit.LIGHT_YEAR),
+                                        new Measure(9.46073, MeasureUnit.METER)},
+                         0 /* meters, precision */),
+
+            // TODO(icu-units#108): reconsider whether epsilon rounding is desirable:
+            //
+            // 2e-16 light years is 1.892146 meters. For C++ double, we consider
+            // this in the noise, and thus expect a 0. (This test fails when
+            // 2e-16 is increased to 4e-16.) For Java, using BigDecimal, we
+            // actually get a good result.
+            new TestCase("light-year", "light-year-and-meter", BigDecimal.valueOf(1.0 + 2e-16),
+                         new Measure[] {new Measure(1, MeasureUnit.LIGHT_YEAR),
+                                        new Measure(1.892146, MeasureUnit.METER)},
+                         0),
+        };
+
         ConversionRates rates = new ConversionRates();
-        MeasureUnit input = MeasureUnit.FOOT;
-        MeasureUnit output = MeasureUnit.forIdentifier("foot-and-inch");
-        final MeasureUnitImpl inputImpl = MeasureUnitImpl.forIdentifier(input.getIdentifier());
-        final MeasureUnitImpl outputImpl = MeasureUnitImpl.forIdentifier(output.getIdentifier());
-        ComplexUnitsConverter converter = new ComplexUnitsConverter(inputImpl, outputImpl, rates);
+        MeasureUnit input, output;
+        List<Measure> measures;
+        for (TestCase testCase : testCases) {
+            input = MeasureUnit.forIdentifier(testCase.input);
+            output = MeasureUnit.forIdentifier(testCase.output);
+            final MeasureUnitImpl inputImpl = MeasureUnitImpl.forIdentifier(input.getIdentifier());
+            final MeasureUnitImpl outputImpl = MeasureUnitImpl.forIdentifier(output.getIdentifier());
+            ComplexUnitsConverter converter = new ComplexUnitsConverter(inputImpl, outputImpl, rates);
+            measures = converter.convert(testCase.value, null);
 
-        // Significantly less than 2.0.
-        List<Measure> measures = converter.convert(BigDecimal.valueOf(1.9999), null);
-        assertEquals("measures length", 2, measures.size());
-        assertEquals("1.9999: measures[0] value", BigDecimal.valueOf(1), measures.get(0).getNumber());
-        assertEquals("1.9999: measures[0] unit", MeasureUnit.FOOT.getIdentifier(),
-                measures.get(0).getUnit().getIdentifier());
-
-        assertTrue("1.9999: measures[1] value", compareTwoBigDecimal(BigDecimal.valueOf(11.9988),
-                BigDecimal.valueOf(measures.get(1).getNumber().doubleValue()), BigDecimal.valueOf(0.0001)));
-        assertEquals("1.9999: measures[1] unit", MeasureUnit.INCH.getIdentifier(),
-                measures.get(1).getUnit().getIdentifier());
-
-        // TODO(icu-units#100): consider factoring out the set of tests to make
-        // this function more data-driven, *after* dealing appropriately with
-        // the C++ memory leaks that can be demonstrated by the C++ version of
-        // this code.
-
-        // A minimal nudge under 2.0.
-        List<Measure> measures2 =
-            converter.convert(BigDecimal.valueOf(2.0).subtract(ComplexUnitsConverter.EPSILON), null);
-        assertEquals("measures length", 2, measures2.size());
-        assertEquals("1 - eps: measures[0] value", BigDecimal.valueOf(2), measures2.get(0).getNumber());
-        assertEquals("1 - eps: measures[0] unit", MeasureUnit.FOOT.getIdentifier(),
-                measures2.get(0).getUnit().getIdentifier());
-        assertEquals("1 - eps: measures[1] value", BigDecimal.ZERO, measures2.get(1).getNumber());
-        assertEquals("1 - eps: measures[1] unit", MeasureUnit.INCH.getIdentifier(),
-                measures2.get(1).getUnit().getIdentifier());
-
-        // Testing precision with meter and light-year. 1e-16 light years is
-        // 0.946073 meters, and double precision can provide only ~15 decimal
-        // digits, so we don't expect to get anything less than 1 meter.
-
-        // An epsilon's nudge under one light-year: should give 1 ly, 0 m.
-        input = MeasureUnit.LIGHT_YEAR;
-        output = MeasureUnit.forIdentifier("light-year-and-meter");
-        final MeasureUnitImpl inputImpl3 = MeasureUnitImpl.forIdentifier(input.getIdentifier());
-        final MeasureUnitImpl outputImpl3 = MeasureUnitImpl.forIdentifier(output.getIdentifier());
-
-        ComplexUnitsConverter converter3 = new ComplexUnitsConverter(inputImpl3, outputImpl3, rates);
-
-        List<Measure> measures3 =
-            converter3.convert(BigDecimal.valueOf(2.0).subtract(ComplexUnitsConverter.EPSILON), null);
-        assertEquals("measures length", 2, measures3.size());
-        assertEquals("light-year test: measures[0] value", BigDecimal.valueOf(2), measures3.get(0).getNumber());
-        assertEquals("light-year test: measures[0] unit", MeasureUnit.LIGHT_YEAR.getIdentifier(),
-                measures3.get(0).getUnit().getIdentifier());
-        assertEquals("light-year test: measures[1] value", BigDecimal.ZERO, measures3.get(1).getNumber());
-        assertEquals("light-year test: measures[1] unit", MeasureUnit.METER.getIdentifier(),
-                measures3.get(1).getUnit().getIdentifier());
-
-        // 1e-15 light years is 9.46073 meters (calculated using "bc" and the CLDR
-        // conversion factor). With double-precision maths, we get 10.5. In this
-        // case, we're off by almost 1 meter.
-        List<Measure> measures4 = converter3.convert(BigDecimal.valueOf(1.0 + 1e-15), null);
-        assertEquals("measures length", 2, measures4.size());
-        assertEquals("light-year test: measures[0] value", BigDecimal.ONE, measures4.get(0).getNumber());
-        assertEquals("light-year test: measures[0] unit", MeasureUnit.LIGHT_YEAR.getIdentifier(),
-                measures4.get(0).getUnit().getIdentifier());
-        assertTrue("light-year test: measures[1] value", compareTwoBigDecimal(BigDecimal.valueOf(10),
-                BigDecimal.valueOf(measures4.get(1).getNumber().doubleValue()),
-                BigDecimal.valueOf(1)));
-        assertEquals("light-year test: measures[1] unit", MeasureUnit.METER.getIdentifier(),
-                measures4.get(1).getUnit().getIdentifier());
-
-        // 2e-16 light years is 1.892146 meters. We consider this in the noise, and
-        // thus expect a 0. (This test fails when 2e-16 is increased to 4e-16.)
-        List<Measure> measures5 = converter3.convert(BigDecimal.valueOf(1.0 + 2e-17), null);
-        assertEquals("measures length", 2, measures5.size());
-        assertEquals("light-year test: measures[0] value", BigDecimal.ONE, measures5.get(0).getNumber());
-        assertEquals("light-year test: measures[0] unit", MeasureUnit.LIGHT_YEAR.getIdentifier(),
-                measures5.get(0).getUnit().getIdentifier());
-        assertEquals("light-year test: measures[1] value", BigDecimal.valueOf(0.0),
-                measures5.get(1).getNumber());
-        assertEquals("light-year test: measures[1] unit", MeasureUnit.METER.getIdentifier(),
-                measures5.get(1).getUnit().getIdentifier());
+            assertEquals("measures length", testCase.expected.length, measures.size());
+            int i = 0;
+            for (Measure measure : measures) {
+                double accuracy = 0.0;
+                if (i == testCase.expected.length - 1) {
+                    accuracy = testCase.accuracy;
+                }
+                assertTrue("input " + testCase.value + ", output measure " + i + ": expected " +
+                               testCase.expected[i] + ", expected unit " +
+                               testCase.expected[i].getUnit() + " got unit " + measure.getUnit(),
+                           testCase.expected[i].getUnit().equals(measure.getUnit()));
+                assertEquals("input " + testCase.value + ", output measure " + i + ": expected " +
+                                 testCase.expected[i] + ", expected number " +
+                                 testCase.expected[i].getNumber() + " got number " + measure.getNumber(),
+                             testCase.expected[i].getNumber().doubleValue(),
+                             measure.getNumber().doubleValue(), accuracy);
+                i++;
+            }
+        }
 
         // TODO(icu-units#63): test negative numbers!
     }
@@ -162,6 +180,14 @@
 
         TestData[] tests = {
                 new TestData("meter", "foot", UnitConverter.Convertibility.CONVERTIBLE),
+                new TestData("kilometer", "foot", UnitConverter.Convertibility.CONVERTIBLE),
+                new TestData("hectare", "square-foot", UnitConverter.Convertibility.CONVERTIBLE),
+                new TestData("kilometer-per-second", "second-per-meter", UnitConverter.Convertibility.RECIPROCAL),
+                new TestData("square-meter", "square-foot", UnitConverter.Convertibility.CONVERTIBLE),
+                new TestData("kilometer-per-second", "foot-per-second", UnitConverter.Convertibility.CONVERTIBLE),
+                new TestData("square-hectare", "pow4-foot", UnitConverter.Convertibility.CONVERTIBLE),
+                new TestData("square-kilometer-per-second", "second-per-square-meter", UnitConverter.Convertibility.RECIPROCAL),
+                new TestData("cubic-kilometer-per-second-meter", "second-per-square-meter", UnitConverter.Convertibility.RECIPROCAL),
                 new TestData("square-meter-per-square-hour", "hectare-per-square-second", UnitConverter.Convertibility.CONVERTIBLE),
                 new TestData("hertz", "revolution-per-second", UnitConverter.Convertibility.CONVERTIBLE),
                 new TestData("millimeter", "meter", UnitConverter.Convertibility.CONVERTIBLE),
@@ -169,7 +195,6 @@
                 new TestData("ounce-troy", "kilogram", UnitConverter.Convertibility.CONVERTIBLE),
                 new TestData("percent", "portion", UnitConverter.Convertibility.CONVERTIBLE),
                 new TestData("ofhg", "kilogram-per-square-meter-square-second", UnitConverter.Convertibility.CONVERTIBLE),
-
                 new TestData("second-per-meter", "meter-per-second", UnitConverter.Convertibility.RECIPROCAL),
         };
         ConversionRates conversionRates = new ConversionRates();
@@ -180,6 +205,7 @@
         }
     }
 
+    // TODO(icu-units#92): add UnitsTest::testConverter(), to replace or extend this test.
     @Test
     public void testConverterForTemperature() {
         class TestData {
@@ -213,7 +239,7 @@
     }
 
     @Test
-    public void testConverterFromUnitTests() throws IOException {
+    public void testConverterWithCLDRTests() throws IOException {
         class TestCase {
             String category;
             String sourceString;
@@ -290,7 +316,7 @@
     }
 
     @Test
-    public void testUnitPreferencesFromUnitTests() throws IOException {
+    public void testUnitPreferencesWithCLDRTests() throws IOException {
         class TestCase {
 
             final ArrayList<Pair<String, MeasureUnitImpl>> outputUnitInOrder = new ArrayList<>();
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
index 1b2c54d..3e14dc0 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
@@ -833,22 +833,21 @@
                 "0.008765 J/fur",
                 "0 J/fur");
 
-        // // TODO(ICU-20941): Support constructions such as this one.
-        // assertFormatDescending(
-        //         "Joules Per Furlong Short with unit identifier via API",
-        //         "measure-unit/energy-joule per-measure-unit/length-furlong",
-        //         "unit/joule-per-furlong",
-        //         NumberFormatter.with().unit(MeasureUnit.forIdentifier("joule-per-furlong")),
-        //         ULocale.ENGLISH,
-        //         "87,650 J/fur",
-        //         "8,765 J/fur",
-        //         "876.5 J/fur",
-        //         "87.65 J/fur",
-        //         "8.765 J/fur",
-        //         "0.8765 J/fur",
-        //         "0.08765 J/fur",
-        //         "0.008765 J/fur",
-        //         "0 J/fur");
+        assertFormatDescending(
+                "Joules Per Furlong Short with unit identifier via API",
+                "measure-unit/energy-joule per-measure-unit/length-furlong",
+                "unit/joule-per-furlong",
+                NumberFormatter.with().unit(MeasureUnit.forIdentifier("joule-per-furlong")),
+                ULocale.ENGLISH,
+                "87,650 J/fur",
+                "8,765 J/fur",
+                "876.5 J/fur",
+                "87.65 J/fur",
+                "8.765 J/fur",
+                "0.8765 J/fur",
+                "0.08765 J/fur",
+                "0.008765 J/fur",
+                "0 J/fur");
 
         assertFormatDescending(
                 "Pounds per Square Inch: composed",