ICU-20568 Fix "1 foot 12 inches" behaviour

See #1337
diff --git a/icu4c/source/i18n/number_rounding.cpp b/icu4c/source/i18n/number_rounding.cpp
index 1489667..a8fd6bc 100644
--- a/icu4c/source/i18n/number_rounding.cpp
+++ b/icu4c/source/i18n/number_rounding.cpp
@@ -24,10 +24,9 @@
 using double_conversion::DoubleToStringConverter;
 using icu::StringSegment;
 
-// Most blueprint_helpers live in number_skeletons.cpp. This one is in
-// number_rounding.cpp for dependency reasons.
-void blueprint_helpers::parseIncrementOption(const StringSegment &segment, MacroProps &macros,
-                                             UErrorCode &status) {
+void number::impl::parseIncrementOption(const StringSegment &segment,
+                                        Precision &outPrecision,
+                                        UErrorCode &status) {
     // Need to do char <-> UChar conversion...
     U_ASSERT(U_SUCCESS(status));
     CharString buffer;
@@ -50,10 +49,10 @@
         decimalOffset++;
     }
     if (decimalOffset == segment.length()) {
-        macros.precision = Precision::increment(increment);
+        outPrecision = Precision::increment(increment);
     } else {
         int32_t fractionLength = segment.length() - decimalOffset - 1;
-        macros.precision = Precision::increment(increment).withMinFraction(fractionLength);
+        outPrecision = Precision::increment(increment).withMinFraction(fractionLength);
     }
 }
 
diff --git a/icu4c/source/i18n/number_roundingutils.h b/icu4c/source/i18n/number_roundingutils.h
index c6e5c77..e85cbae 100644
--- a/icu4c/source/i18n/number_roundingutils.h
+++ b/icu4c/source/i18n/number_roundingutils.h
@@ -8,6 +8,7 @@
 #define __NUMBER_ROUNDINGUTILS_H__
 
 #include "number_types.h"
+#include "string_segment.h"
 
 U_NAMESPACE_BEGIN
 namespace number {
@@ -192,12 +193,20 @@
     bool fPassThrough = true;  // default value
 
     // Permits access to fPrecision.
-    friend class UsagePrefsHandler;
+    friend class units::UnitsRouter;
 
     // Permits access to fPrecision.
     friend class UnitConversionHandler;
 };
 
+/**
+ * Parses Precision-related skeleton strings without knowledge of MacroProps
+ * - see blueprint_helpers::parseIncrementOption().
+ *
+ * Referencing MacroProps means needing to pull in the .o files that have the
+ * destructors for the SymbolsWrapper, Usage, and Scale classes.
+ */
+void parseIncrementOption(const StringSegment &segment, Precision &outPrecision, UErrorCode &status);
 
 } // namespace impl
 } // namespace number
diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp
index 5a23bd3..fd7d7ca 100644
--- a/icu4c/source/i18n/number_skeletons.cpp
+++ b/icu4c/source/i18n/number_skeletons.cpp
@@ -10,6 +10,7 @@
 #define UNISTR_FROM_STRING_EXPLICIT
 
 #include "number_decnum.h"
+#include "number_roundingutils.h"
 #include "number_skeletons.h"
 #include "umutex.h"
 #include "ucln_in.h"
@@ -1333,8 +1334,10 @@
     return true;
 }
 
-// blueprint_helpers::parseIncrementOption lives in number_rounding.cpp for
-// dependencies reasons.
+void blueprint_helpers::parseIncrementOption(const StringSegment &segment, MacroProps &macros,
+                                             UErrorCode &status) {
+    number::impl::parseIncrementOption(segment, macros.precision, status);
+}
 
 void blueprint_helpers::generateIncrementOption(double increment, int32_t trailingZeros, UnicodeString& sb,
                                                 UErrorCode&) {
diff --git a/icu4c/source/i18n/number_usageprefs.cpp b/icu4c/source/i18n/number_usageprefs.cpp
index 494b968..0d9cb06 100644
--- a/icu4c/source/i18n/number_usageprefs.cpp
+++ b/icu4c/source/i18n/number_usageprefs.cpp
@@ -157,7 +157,7 @@
     }
 
     quantity.roundToInfinity(); // Enables toDouble
-    const auto routed = fUnitsRouter.route(quantity.toDouble(), status);
+    const units::RouteResult routed = fUnitsRouter.route(quantity.toDouble(), &micros.rounder, status);
     if (U_FAILURE(status)) {
         return;
     }
@@ -168,38 +168,6 @@
     }
 
     mixedMeasuresToMicros(routedMeasures, &quantity, &micros, status);
-
-    UnicodeString precisionSkeleton = routed.precision;
-    if (micros.rounder.fPrecision.isBogus()) {
-        if (precisionSkeleton.length() > 0) {
-            micros.rounder.fPrecision = parseSkeletonToPrecision(precisionSkeleton, status);
-        } else {
-            // We use the same rounding mode as COMPACT notation: known to be a
-            // human-friendly rounding mode: integers, but add a decimal digit
-            // as needed to ensure we have at least 2 significant digits.
-            micros.rounder.fPrecision = Precision::integer().withMinDigits(2);
-        }
-    }
-}
-
-Precision UsagePrefsHandler::parseSkeletonToPrecision(icu::UnicodeString precisionSkeleton,
-                                                      UErrorCode status) {
-    if (U_FAILURE(status)) {
-        // As a member of UsagePrefsHandler, which is a friend of Precision, we
-        // get access to the default constructor.
-        return {};
-    }
-    constexpr int32_t kSkelPrefixLen = 20;
-    if (!precisionSkeleton.startsWith(UNICODE_STRING_SIMPLE("precision-increment/"))) {
-        status = U_INVALID_FORMAT_ERROR;
-        return {};
-    }
-    U_ASSERT(precisionSkeleton[kSkelPrefixLen - 1] == u'/');
-    StringSegment segment(precisionSkeleton, false);
-    segment.adjustOffset(kSkelPrefixLen);
-    MacroProps macros;
-    blueprint_helpers::parseIncrementOption(segment, macros, status);
-    return macros.precision;
 }
 
 UnitConversionHandler::UnitConversionHandler(const MeasureUnit &inputUnit, const MeasureUnit &outputUnit,
@@ -227,19 +195,14 @@
         return;
     }
     quantity.roundToInfinity(); // Enables toDouble
-    MaybeStackVector<Measure> measures = fUnitConverter->convert(quantity.toDouble(), status);
+    MaybeStackVector<Measure> measures =
+        fUnitConverter->convert(quantity.toDouble(), &micros.rounder, status);
     micros.outputUnit = fOutputUnit;
     if (U_FAILURE(status)) {
         return;
     }
 
     mixedMeasuresToMicros(measures, &quantity, &micros, status);
-
-    // TODO: add tests to explore behaviour that may suggest a more
-    // human-centric default rounder?
-    // if (micros.rounder.fPrecision.isBogus()) {
-    //     micros.rounder.fPrecision = Precision::integer().withMinDigits(2);
-    // }
 }
 
 #endif /* #if !UCONFIG_NO_FORMATTING */
diff --git a/icu4c/source/i18n/number_usageprefs.h b/icu4c/source/i18n/number_usageprefs.h
index b773979..9e8bd93 100644
--- a/icu4c/source/i18n/number_usageprefs.h
+++ b/icu4c/source/i18n/number_usageprefs.h
@@ -60,8 +60,6 @@
   private:
     UnitsRouter fUnitsRouter;
     const MicroPropsGenerator *fParent;
-
-    static Precision parseSkeletonToPrecision(icu::UnicodeString precisionSkeleton, UErrorCode status);
 };
 
 } // namespace impl
diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h
index c5aeeca..b0c63dc 100644
--- a/icu4c/source/i18n/unicode/numberformatter.h
+++ b/icu4c/source/i18n/unicode/numberformatter.h
@@ -99,6 +99,13 @@
 }
 }
 
+namespace units {
+
+// Forward declarations:
+class UnitsRouter;
+
+} // namespace units
+
 namespace number {  // icu::number
 
 // Forward declarations:
@@ -158,7 +165,6 @@
 class MutablePatternModifier;
 class ImmutablePatternModifier;
 struct DecimalFormatWarehouse;
-class UsagePrefsHandler;
 
 /**
  * Used for NumberRangeFormatter and implemented in numrange_fluent.cpp.
@@ -764,7 +770,7 @@
     friend class impl::GeneratorHelpers;
 
     // To allow access to isBogus and the default (bogus) constructor:
-    friend class impl::UsagePrefsHandler;
+    friend class units::UnitsRouter;
 };
 
 /**
diff --git a/icu4c/source/i18n/units_complexconverter.cpp b/icu4c/source/i18n/units_complexconverter.cpp
index f145030..9775b6a 100644
--- a/icu4c/source/i18n/units_complexconverter.cpp
+++ b/icu4c/source/i18n/units_complexconverter.cpp
@@ -8,6 +8,8 @@
 #include <cmath>
 
 #include "cmemory.h"
+#include "number_decimalquantity.h"
+#include "number_roundingutils.h"
 #include "uarrsort.h"
 #include "uassert.h"
 #include "unicode/fmtable.h"
@@ -105,11 +107,24 @@
     return newQuantity >= limit;
 }
 
-MaybeStackVector<Measure> ComplexUnitsConverter::convert(double quantity, UErrorCode &status) const {
+MaybeStackVector<Measure> ComplexUnitsConverter::convert(double quantity,
+                                                         icu::number::impl::RoundingImpl *rounder,
+                                                         UErrorCode &status) const {
     // TODO(icu-units#63): test negative numbers!
     // TODO(hugovdm): return an error for "foot-and-foot"?
     MaybeStackVector<Measure> result;
 
+    // For N converters:
+    // - the first converter converts from the input unit to the largest unit,
+    // - N-1 converters convert to bigger units for which we want integers,
+    // - the Nth converter (index N-1) converts to the smallest unit, for which
+    //   we keep a double.
+    MaybeStackArray<int64_t, 5> intValues(unitConverters_.length() - 1, status);
+    if (U_FAILURE(status)) {
+        return result;
+    }
+    uprv_memset(intValues.getAlias(), 0, (unitConverters_.length() - 1) * sizeof(int64_t));
+
     for (int i = 0, n = unitConverters_.length(); i < n; ++i) {
         quantity = (*unitConverters_[i]).convert(quantity);
         if (i < n - 1) {
@@ -120,11 +135,7 @@
             // original values to ensure unbiased accuracy (to the extent of
             // double's capabilities).
             int64_t roundedQuantity = floor(quantity * (1 + DBL_EPSILON));
-            Formattable formattableNewQuantity(roundedQuantity);
-
-            // NOTE: Measure would own its MeasureUnit.
-            MeasureUnit *type = new MeasureUnit(units_[i]->copy(status).build(status));
-            result.emplaceBackAndCheckErrorCode(status, formattableNewQuantity, type, status);
+            intValues[i] = roundedQuantity;
 
             // Keep the residual of the quantity.
             //   For example: `3.6 feet`, keep only `0.6 feet`
@@ -137,11 +148,76 @@
                 quantity -= roundedQuantity;
             }
         } else { // LAST ELEMENT
-            Formattable formattableQuantity(quantity);
+            if (rounder == nullptr) {
+                // Nothing to do for the last element.
+                break;
+            }
 
-            // NOTE: Measure would own its MeasureUnit.
+            // Round the last value
+            // TODO(ICU-21288): get smarter about precision for mixed units.
+            number::impl::DecimalQuantity quant;
+            quant.setToDouble(quantity);
+            rounder->apply(quant, status);
+            if (U_FAILURE(status)) {
+                return result;
+            }
+            quantity = quant.toDouble();
+            if (i == 0) {
+                // Last element is also the first element, so we're done
+                break;
+            }
+
+            // Check if there's a carry, and bubble it back up the resulting intValues.
+            int64_t carry = floor(unitConverters_[i]->convertInverse(quantity) * (1 + DBL_EPSILON));
+            if (carry <= 0) {
+                break;
+            }
+            quantity -= unitConverters_[i]->convert(carry);
+            intValues[i - 1] += carry;
+
+            // We don't use the first converter: that one is for the input unit
+            for (int32_t j = i - 1; j > 0; j--) {
+                carry = floor(unitConverters_[j]->convertInverse(intValues[j]) * (1 + DBL_EPSILON));
+                if (carry <= 0) {
+                    break;
+                }
+                intValues[j] -= round(unitConverters_[j]->convert(carry));
+                intValues[j - 1] += carry;
+            }
+        }
+    }
+
+    // Package values into Measure instances in result:
+    for (int i = 0, n = unitConverters_.length(); i < n; ++i) {
+        if (i < n - 1) {
+            Formattable formattableQuantity(intValues[i]);
+            // Measure takes ownership of the MeasureUnit*
             MeasureUnit *type = new MeasureUnit(units_[i]->copy(status).build(status));
-            result.emplaceBackAndCheckErrorCode(status, formattableQuantity, type, status);
+            if (result.emplaceBackAndCheckErrorCode(status, formattableQuantity, type, status) ==
+                nullptr) {
+                // Ownership wasn't taken
+                U_ASSERT(U_FAILURE(status));
+                delete type;
+            }
+            if (U_FAILURE(status)) {
+                return result;
+            }
+        } else { // LAST ELEMENT
+            // Add the last element, not an integer:
+            Formattable formattableQuantity(quantity);
+            // Measure takes ownership of the MeasureUnit*
+            MeasureUnit *type = new MeasureUnit(units_[i]->copy(status).build(status));
+            if (result.emplaceBackAndCheckErrorCode(status, formattableQuantity, type, status) ==
+                nullptr) {
+                // Ownership wasn't taken
+                U_ASSERT(U_FAILURE(status));
+                delete type;
+            }
+            if (U_FAILURE(status)) {
+                return result;
+            }
+            U_ASSERT(result.length() == i + 1);
+            U_ASSERT(result[i] != nullptr);
         }
     }
 
@@ -153,6 +229,7 @@
     for (int32_t i = 0; i < unitsCount; i++) {
         for (int32_t j = i; j < unitsCount; j++) {
             // Find the next expected unit, and swap it into place.
+            U_ASSERT(result[j] != nullptr);
             if (result[j]->getUnit() == *outputUnits_[i]) {
                 if (j != i) {
                     Measure *tmp = arr[j];
diff --git a/icu4c/source/i18n/units_complexconverter.h b/icu4c/source/i18n/units_complexconverter.h
index fc355d9..83c5b94 100644
--- a/icu4c/source/i18n/units_complexconverter.h
+++ b/icu4c/source/i18n/units_complexconverter.h
@@ -9,6 +9,7 @@
 
 #include "cmemory.h"
 #include "measunit_impl.h"
+#include "number_roundingutils.h"
 #include "unicode/errorcode.h"
 #include "unicode/measure.h"
 #include "units_converter.h"
@@ -73,7 +74,8 @@
     //         NOTE:
     //           the smallest element is the only element that could have fractional values. And all
     //           other elements are floored to the nearest integer
-    MaybeStackVector<Measure> convert(double quantity, UErrorCode &status) const;
+    MaybeStackVector<Measure>
+    convert(double quantity, icu::number::impl::RoundingImpl *rounder, UErrorCode &status) const;
 
   private:
     MaybeStackVector<UnitConverter> unitConverters_;
diff --git a/icu4c/source/i18n/units_converter.cpp b/icu4c/source/i18n/units_converter.cpp
index 3463387..a777d02 100644
--- a/icu4c/source/i18n/units_converter.cpp
+++ b/icu4c/source/i18n/units_converter.cpp
@@ -510,15 +510,36 @@
 
     result -= conversionRate_.targetOffset; // Set the result to its index.
 
-    if (result == 0)
-        return 0.0; // If the result is zero, it does not matter if the conversion are reciprocal or not.
     if (conversionRate_.reciprocal) {
+        if (result == 0) {
+            // TODO: demonstrate the resulting behaviour in tests... and figure
+            // out desired behaviour. (Theoretical result should be infinity,
+            // not 0.)
+            return 0.0;
+        }
         result = 1.0 / result;
     }
 
     return result;
 }
 
+double UnitConverter::convertInverse(double inputValue) const {
+    double result = inputValue;
+    if (conversionRate_.reciprocal) {
+        if (result == 0) {
+            // TODO: demonstrate the resulting behaviour in tests... and figure
+            // out desired behaviour. (Theoretical result should be infinity,
+            // not 0.)
+            return 0.0;
+        }
+        result = 1.0 / result;
+    }
+    result += conversionRate_.targetOffset;
+    result *= conversionRate_.factorDen / conversionRate_.factorNum;
+    result -= conversionRate_.sourceOffset;
+    return result;
+}
+
 } // namespace units
 U_NAMESPACE_END
 
diff --git a/icu4c/source/i18n/units_converter.h b/icu4c/source/i18n/units_converter.h
index 2f5dad8..8676405 100644
--- a/icu4c/source/i18n/units_converter.h
+++ b/icu4c/source/i18n/units_converter.h
@@ -144,14 +144,23 @@
                   const ConversionRates &ratesInfo, UErrorCode &status);
 
     /**
-     * Convert a value in the source unit to another value in the target unit.
+     * Convert a measurement expressed in the source unit to a measurement
+     * expressed in the target unit.
      *
-     * @param input_value the value that needs to be converted.
-     * @param output_value the value that holds the result of the conversion.
-     * @param status
+     * @param inputValue the value to be converted.
+     * @return the converted value.
      */
     double convert(double inputValue) const;
 
+    /**
+     * The inverse of convert(): convert a measurement expressed in the target
+     * unit to a measurement expressed in the source unit.
+     *
+     * @param inputValue the value to be converted.
+     * @return the converted value.
+     */
+    double convertInverse(double inputValue) const;
+
   private:
     ConversionRate conversionRate_;
 };
diff --git a/icu4c/source/i18n/units_router.cpp b/icu4c/source/i18n/units_router.cpp
index 759ae2b..9489a8d 100644
--- a/icu4c/source/i18n/units_router.cpp
+++ b/icu4c/source/i18n/units_router.cpp
@@ -10,6 +10,7 @@
 #include "cstring.h"
 #include "measunit_impl.h"
 #include "number_decimalquantity.h"
+#include "number_roundingutils.h"
 #include "resource.h"
 #include "unicode/measure.h"
 #include "units_data.h"
@@ -18,6 +19,29 @@
 U_NAMESPACE_BEGIN
 namespace units {
 
+using number::Precision;
+using number::impl::parseIncrementOption;
+
+Precision UnitsRouter::parseSkeletonToPrecision(icu::UnicodeString precisionSkeleton,
+                                                UErrorCode &status) {
+    if (U_FAILURE(status)) {
+        // As a member of UsagePrefsHandler, which is a friend of Precision, we
+        // get access to the default constructor.
+        return {};
+    }
+    constexpr int32_t kSkelPrefixLen = 20;
+    if (!precisionSkeleton.startsWith(UNICODE_STRING_SIMPLE("precision-increment/"))) {
+        status = U_INVALID_FORMAT_ERROR;
+        return {};
+    }
+    U_ASSERT(precisionSkeleton[kSkelPrefixLen - 1] == u'/');
+    StringSegment segment(precisionSkeleton, false);
+    segment.adjustOffset(kSkelPrefixLen);
+    Precision result;
+    parseIncrementOption(segment, result, status);
+    return result;
+}
+
 UnitsRouter::UnitsRouter(MeasureUnit inputUnit, StringPiece region, StringPiece usage,
                          UErrorCode &status) {
     // TODO: do we want to pass in ConversionRates and UnitPreferences instead
@@ -67,22 +91,32 @@
     }
 }
 
-RouteResult UnitsRouter::route(double quantity, UErrorCode &status) const {
-    for (int i = 0, n = converterPreferences_.length(); i < n; i++) {
-        const auto &converterPreference = *converterPreferences_[i];
-        if (converterPreference.converter.greaterThanOrEqual(quantity * (1 + DBL_EPSILON),
-                                                             converterPreference.limit)) {
-            return RouteResult(converterPreference.converter.convert(quantity, status),
-                               converterPreference.precision,
-                               converterPreference.targetUnit.copy(status));
+RouteResult UnitsRouter::route(double quantity, icu::number::impl::RoundingImpl *rounder, UErrorCode &status) const {
+    // Find the matching preference
+    const ConverterPreference *converterPreference = nullptr;
+    for (int32_t i = 0, n = converterPreferences_.length(); i < n; i++) {
+        converterPreference = converterPreferences_[i];
+        if (converterPreference->converter.greaterThanOrEqual(quantity * (1 + DBL_EPSILON),
+                                                              converterPreference->limit)) {
+            break;
+        }
+    }
+    U_ASSERT(converterPreference != nullptr);
+
+    // Set up the rounder for this preference's precision
+    if (rounder != nullptr && rounder->fPrecision.isBogus()) {
+        if (converterPreference->precision.length() > 0) {
+            rounder->fPrecision = parseSkeletonToPrecision(converterPreference->precision, status);
+        } else {
+            // We use the same rounding mode as COMPACT notation: known to be a
+            // human-friendly rounding mode: integers, but add a decimal digit
+            // as needed to ensure we have at least 2 significant digits.
+            rounder->fPrecision = Precision::integer().withMinDigits(2);
         }
     }
 
-    // In case of the `quantity` does not fit in any converter limit, use the last converter.
-    const auto &lastConverterPreference = (*converterPreferences_[converterPreferences_.length() - 1]);
-    return RouteResult(lastConverterPreference.converter.convert(quantity, status),
-                       lastConverterPreference.precision,
-                       lastConverterPreference.targetUnit.copy(status));
+    return RouteResult(converterPreference->converter.convert(quantity, rounder, status),
+                       converterPreference->targetUnit.copy(status));
 }
 
 const MaybeStackVector<MeasureUnit> *UnitsRouter::getOutputUnits() const {
diff --git a/icu4c/source/i18n/units_router.h b/icu4c/source/i18n/units_router.h
index e6b74aa..bd7a93d 100644
--- a/icu4c/source/i18n/units_router.h
+++ b/icu4c/source/i18n/units_router.h
@@ -21,6 +21,9 @@
 
 // Forward declarations
 class Measure;
+namespace number {
+class Precision;
+}
 
 namespace units {
 
@@ -31,19 +34,13 @@
     // TODO(icu-units/icu#21): figure out the right mixed unit API.
     MaybeStackVector<Measure> measures;
 
-    // A skeleton string starting with a precision-increment.
-    //
-    // TODO(hugovdm): generalise? or narrow down to only a precision-increment?
-    // or document that other skeleton elements are ignored?
-    UnicodeString precision;
-
     // The output unit for this RouteResult. This may be a MIXED unit - for
     // example: "yard-and-foot-and-inch", for which `measures` will have three
     // elements.
     MeasureUnitImpl outputUnit;
 
-    RouteResult(MaybeStackVector<Measure> measures, UnicodeString precision, MeasureUnitImpl outputUnit)
-        : measures(std::move(measures)), precision(std::move(precision)), outputUnit(std::move(outputUnit)) {}
+    RouteResult(MaybeStackVector<Measure> measures, MeasureUnitImpl outputUnit)
+        : measures(std::move(measures)), outputUnit(std::move(outputUnit)) {}
 };
 
 /**
@@ -125,7 +122,16 @@
   public:
     UnitsRouter(MeasureUnit inputUnit, StringPiece locale, StringPiece usage, UErrorCode &status);
 
-    RouteResult route(double quantity, UErrorCode &status) const;
+    /**
+     * Performs locale and usage sensitive unit conversion.
+     * @param quantity The quantity to convert, expressed in terms of inputUnit.
+     * @param rounder If not null, this RoundingImpl will be used to do rounding
+     *     on the converted value. If the rounder lacks an fPrecision, the
+     *     rounder will be modified to use the preferred precision for the usage
+     *     and locale preference, alternatively with the default precision.
+     * @param status Receives status.
+     */
+    RouteResult route(double quantity, icu::number::impl::RoundingImpl *rounder, UErrorCode &status) const;
 
     /**
      * Returns the list of possible output units, i.e. the full set of
@@ -143,6 +149,9 @@
     MaybeStackVector<MeasureUnit> outputUnits_;
 
     MaybeStackVector<ConverterPreference> converterPreferences_;
+
+    static number::Precision parseSkeletonToPrecision(icu::UnicodeString precisionSkeleton,
+                                                      UErrorCode &status);
 };
 
 } // namespace units
diff --git a/icu4c/source/test/depstest/dependencies.txt b/icu4c/source/test/depstest/dependencies.txt
index fda37de..a3d059a 100644
--- a/icu4c/source/test/depstest/dependencies.txt
+++ b/icu4c/source/test/depstest/dependencies.txt
@@ -1110,6 +1110,7 @@
     units_data.o units_converter.o units_complexconverter.o units_router.o
   deps
     resourcebundle units_extra double_conversion number_representation formattable sort
+    number_rounding
 
 group: decnumber
     decContext.o decNumber.o
diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp
index ecc0dca..ca2170d 100644
--- a/icu4c/source/test/intltest/numbertest_api.cpp
+++ b/icu4c/source/test/intltest/numbertest_api.cpp
@@ -753,21 +753,17 @@
             4.28571,
             u"4 metric tons, 285 kilograms, 710 grams");
 
-//     // TODO(icu-units#73): deal with this "1 foot 12 inches" problem.
-//     // At the time of writing, this test would pass, but is commented out
-//     // because it reflects undesired behaviour:
-//     assertFormatSingle(
-//             u"Demonstrating the \"1 foot 12 inches\" problem",
-//             nullptr,
-//             u"unit/foot-and-inch",
-//             NumberFormatter::with()
-//                 .unit(MeasureUnit::forIdentifier("foot-and-inch", status))
-//                 .precision(Precision::maxSignificantDigits(4))
-//                 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
-//             Locale("en-US"),
-//             1.9999,
-//             // This is undesireable but current behaviour:
-//             u"1 foot, 12 inches");
+    assertFormatSingle(
+            u"Testing  \"1 foot 12 inches\"",
+            nullptr,
+            u"unit/foot-and-inch",
+            NumberFormatter::with()
+                .unit(MeasureUnit::forIdentifier("foot-and-inch", status))
+                .precision(Precision::maxSignificantDigits(4))
+                .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
+            Locale("en-US"),
+            1.9999,
+            u"2 feet, 0 inches");
 }
 
 void NumberFormatterApiTest::unitCompoundMeasure() {
@@ -1161,6 +1157,10 @@
             Locale("en-ZA"),
             30500,
             u"350 m");
+
+    // TODO(icu-units#38): improve unit testing coverage. E.g. add vehicle-fuel
+    // triggering inversion conversion code. Test with 0 too, to see
+    // divide-by-zero behaviour.
 }
 
 void NumberFormatterApiTest::unitUsageErrorCodes() {
diff --git a/icu4c/source/test/intltest/units_test.cpp b/icu4c/source/test/intltest/units_test.cpp
index 8053f1d..1132697 100644
--- a/icu4c/source/test/intltest/units_test.cpp
+++ b/icu4c/source/test/intltest/units_test.cpp
@@ -408,6 +408,10 @@
     msg.clear();
     msg.append("Converting 1000 ", status).append(x, status).append(" to ", status).append(y, status);
     unitsTest->assertEqualsNear(msg.data(), expected, got, 0.0001 * expected);
+    double inverted = converter.convertInverse(got);
+    msg.clear();
+    msg.append("Converting back to ", status).append(x, status).append(" from ", status).append(y, status);
+    unitsTest->assertEqualsNear(msg.data(), 1000, inverted, 0.0001);
 }
 
 /**
@@ -439,7 +443,7 @@
 }
 
 void UnitsTest::testComplexUnitsConverter() {
-    IcuTestErrorCode status(*this, "UnitsTest::testComplexUnitConversions");
+    IcuTestErrorCode status(*this, "UnitsTest::testComplexUnitsConverter");
     ConversionRates rates(status);
     MeasureUnit input = MeasureUnit::getFoot();
     MeasureUnit output = MeasureUnit::forIdentifier("foot-and-inch", status);
@@ -449,7 +453,7 @@
     auto converter = ComplexUnitsConverter(inputImpl, outputImpl, rates, status);
 
     // Significantly less than 2.0.
-    MaybeStackVector<Measure> measures = converter.convert(1.9999, status);
+    MaybeStackVector<Measure> measures = converter.convert(1.9999, nullptr, status);
     assertEquals("measures length", 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(),
@@ -458,13 +462,13 @@
     assertEquals("1.9999: measures[1] unit", MeasureUnit::getInch().getIdentifier(),
                  measures[1]->getUnit().getIdentifier());
 
-    // TODO: consider factoring out the set of tests to make this function more
+    // 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: reusing measures results in a leak.
+    // 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), status);
+    MaybeStackVector<Measure> measures2 = converter.convert((2.0 - DBL_EPSILON), nullptr, status);
     assertEquals("measures length", 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(),
@@ -480,14 +484,14 @@
     // 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: reusing tempInput and tempOutput results in a leak.
+    // 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: reusing converter results in a leak.
+    // TODO(icu-units#100): reusing converter results in a leak.
     ComplexUnitsConverter converter3 = ComplexUnitsConverter(inputImpl3, outputImpl3, rates, status);
-    // TODO: reusing measures results in a leak.
-    MaybeStackVector<Measure> measures3 = converter3.convert((2.0 - DBL_EPSILON), 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());
     assertEquals("light-year test: measures[0] value", 2.0, measures3[0]->getNumber().getDouble(status));
     assertEquals("light-year test: measures[0] unit", MeasureUnit::getLightYear().getIdentifier(),
@@ -499,7 +503,7 @@
     // 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), status);
+    MaybeStackVector<Measure> measures4 = converter3.convert((1.0 + 1e-15), nullptr, status);
     assertEquals("measures length", 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(),
@@ -511,7 +515,7 @@
 
     // 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), status);
+    MaybeStackVector<Measure> measures5 = converter3.convert((1.0 + 2e-16), nullptr, status);
     assertEquals("measures length", 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(),
@@ -532,7 +536,7 @@
     ConversionRates conversionRates(status);
 
     ComplexUnitsConverter complexConverter(source, target, conversionRates, status);
-    auto measures = complexConverter.convert(10.0, status);
+    auto measures = complexConverter.convert(10.0, nullptr, status);
 
     U_ASSERT(measures.length() == 2);
     assertEquals("inch-and-foot unit 0", "inch", measures[0]->getUnit().getIdentifier());
@@ -732,7 +736,7 @@
     if (status.errIfFailureAndReset("Failure before router.route")) {
         return;
     }
-    auto routeResult = router.route(inputAmount, status);
+    RouteResult routeResult = router.route(inputAmount, nullptr, status);
     if (status.errIfFailureAndReset("router.route(inputAmount, ...)")) {
         return;
     }
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/UnitConversionHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/UnitConversionHandler.java
index db27059..0612c63 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/UnitConversionHandler.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/UnitConversionHandler.java
@@ -8,7 +8,6 @@
 import com.ibm.icu.util.Measure;
 import com.ibm.icu.util.MeasureUnit;
 
-import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -49,7 +48,7 @@
         MicroProps result = this.fParent.processQuantity(quantity);
 
         quantity.roundToInfinity(); // Enables toDouble
-        List<Measure> measures = this.fComplexUnitConverter.convert(quantity.toBigDecimal());
+        List<Measure> measures = this.fComplexUnitConverter.convert(quantity.toBigDecimal(), result.rounder);
 
         result.outputUnit = this.fOutputUnit;
         UsagePrefsHandler.mixedMeasuresToMicros(measures, quantity, result);
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/UsagePrefsHandler.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/UsagePrefsHandler.java
index 74c13b6..ca20045 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/UsagePrefsHandler.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/UsagePrefsHandler.java
@@ -2,16 +2,13 @@
 // License & terms of use: http://www.unicode.org/copyright.html
 package com.ibm.icu.impl.number;
 
-import com.ibm.icu.impl.IllegalIcuArgumentException;
 import com.ibm.icu.impl.units.MeasureUnitImpl;
 import com.ibm.icu.impl.units.UnitsRouter;
-import com.ibm.icu.number.Precision;
 import com.ibm.icu.util.Measure;
 import com.ibm.icu.util.MeasureUnit;
 import com.ibm.icu.util.ULocale;
 
 import java.math.BigDecimal;
-import java.math.MathContext;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -28,46 +25,29 @@
                 new UnitsRouter(MeasureUnitImpl.forIdentifier(inputUnit.getIdentifier()), locale.getCountry(), usage);
     }
 
-    private static Precision parseSkeletonToPrecision(String precisionSkeleton) {
-        final String kSuffixPrefix = "precision-increment/";
-        if (!precisionSkeleton.startsWith(kSuffixPrefix)) {
-            throw new IllegalIcuArgumentException("precisionSkeleton is only precision-increment");
-        }
-
-        String skeleton = precisionSkeleton.substring(kSuffixPrefix.length());
-        String skeletons[] = skeleton.split("/");
-        BigDecimal num = new BigDecimal(skeletons[0]);
-        BigDecimal den =
-                skeletons.length == 2 ?
-                        new BigDecimal(skeletons[1]) :
-                        new BigDecimal("1");
-
-
-        return Precision.increment(num.divide(den, MathContext.DECIMAL128));
-    }
-
     /**
      * Populates micros.mixedMeasures and modifies quantity, based on the values
      * in measures.
      */
-    protected static void mixedMeasuresToMicros(List<Measure> measures, DecimalQuantity quantity, MicroProps micros) {
-        micros.mixedMeasures = new ArrayList<>();
+    protected static void
+    mixedMeasuresToMicros(List<Measure> measures, DecimalQuantity outQuantity, MicroProps outMicros) {
+        outMicros.mixedMeasures = new ArrayList<>();
         if (measures.size() > 1) {
             // For debugging
-            assert (micros.outputUnit.getComplexity() == MeasureUnit.Complexity.MIXED);
+            assert (outMicros.outputUnit.getComplexity() == MeasureUnit.Complexity.MIXED);
 
             // Check that we received the expected number of measurements:
-            assert measures.size() == micros.outputUnit.splitToSingleUnits().size();
+            assert measures.size() == outMicros.outputUnit.splitToSingleUnits().size();
 
             // Mixed units: except for the last value, we pass all values to the
             // LongNameHandler via micros->mixedMeasures.
             for (int i = 0, n = measures.size() - 1; i < n; i++) {
-                micros.mixedMeasures.add(measures.get(i));
+                outMicros.mixedMeasures.add(measures.get(i));
             }
         }
 
         // The last value (potentially the only value) gets passed on via quantity.
-        quantity.setToBigDecimal((BigDecimal) measures.get(measures.size()- 1).getNumber());
+        outQuantity.setToBigDecimal((BigDecimal) measures.get(measures.size()- 1).getNumber());
     }
 
     /**
@@ -93,29 +73,12 @@
         MicroProps micros = this.fParent.processQuantity(quantity);
 
         quantity.roundToInfinity(); // Enables toDouble
-        final UnitsRouter.RouteResult routed = fUnitsRouter.route(quantity.toBigDecimal());
+        final UnitsRouter.RouteResult routed = fUnitsRouter.route(quantity.toBigDecimal(), micros);
 
         final List<Measure> routedMeasures = routed.measures;
         micros.outputUnit = routed.outputUnit.build();
 
         UsagePrefsHandler.mixedMeasuresToMicros(routedMeasures, quantity, micros);
-
-        String precisionSkeleton = routed.precision;
-
-        assert micros.rounder != null;
-
-        if (micros.rounder instanceof Precision.BogusRounder) {
-            Precision.BogusRounder rounder = (Precision.BogusRounder)micros.rounder;
-            if (precisionSkeleton != null && precisionSkeleton.length() > 0) {
-                micros.rounder = rounder.into(parseSkeletonToPrecision(precisionSkeleton));
-            } else {
-                // We use the same rounding mode as COMPACT notation: known to be a
-                // human-friendly rounding mode: integers, but add a decimal digit
-                // as needed to ensure we have at least 2 significant digits.
-                micros.rounder = rounder.into(Precision.integer().withMinDigits(2));
-            }
-        }
-
         return micros;
     }
 }
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/ComplexUnitsConverter.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/ComplexUnitsConverter.java
index aec0e5f..d864775 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/ComplexUnitsConverter.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/ComplexUnitsConverter.java
@@ -1,11 +1,12 @@
 // © 2020 and later: Unicode, Inc. and others.
 // License & terms of use: http://www.unicode.org/copyright.html
-
-
 package com.ibm.icu.impl.units;
 
-
+import com.ibm.icu.impl.number.DecimalQuantity;
+import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
+import com.ibm.icu.number.Precision;
 import com.ibm.icu.util.Measure;
+import com.ibm.icu.util.MeasureUnit;
 
 import java.math.BigDecimal;
 import java.math.RoundingMode;
@@ -26,7 +27,10 @@
     public static final BigDecimal EPSILON = BigDecimal.valueOf(Math.ulp(1.0));
     public static final BigDecimal EPSILON_MULTIPLIER = BigDecimal.valueOf(1).add(EPSILON);
     private ArrayList<UnitConverter> unitConverters_;
+    // Individual units of mixed units, sorted big to small
     private ArrayList<MeasureUnitImpl> units_;
+    // Individual units of mixed units, sorted in desired output order
+    private ArrayList<MeasureUnit> outputUnits_;
 
     /**
      * Constructor of `ComplexUnitsConverter`.
@@ -40,6 +44,10 @@
     public ComplexUnitsConverter(MeasureUnitImpl inputUnit, MeasureUnitImpl outputUnits,
                                  ConversionRates conversionRates) {
         units_ = outputUnits.extractIndividualUnits();
+        outputUnits_ = new ArrayList<>(units_.size());
+        for (MeasureUnitImpl itr : units_) {
+            outputUnits_.add(itr.build());
+        }
         assert (!units_.isEmpty());
 
         // Sort the units in a descending order.
@@ -95,8 +103,16 @@
      * the smallest element is the only element that could have fractional values. And all
      * other elements are floored to the nearest integer
      */
-    public List<Measure> convert(BigDecimal quantity) {
-        List<Measure> result = new ArrayList<>();
+    public List<Measure> convert(BigDecimal quantity, Precision rounder) {
+        List<Measure> result = new ArrayList<>(unitConverters_.size());
+
+        // For N converters:
+        // - the first converter converts from the input unit to the largest
+        //   unit,
+        // - N-1 converters convert to bigger units for which we want integers,
+        // - the Nth converter (index N-1) converts to the smallest unit, which
+        //   isn't (necessarily) an integer.
+        List<BigDecimal> intValues = new ArrayList<>(unitConverters_.size() - 1);
 
         for (int i = 0, n = unitConverters_.size(); i < n; ++i) {
             quantity = (unitConverters_.get(i)).convert(quantity);
@@ -108,21 +124,85 @@
                 // decision is made. However after the thresholding, we use the
                 // original values to ensure unbiased accuracy (to the extent of
                 // double's capabilities).
-                BigDecimal newQuantity = quantity.multiply(EPSILON_MULTIPLIER).setScale(0, RoundingMode.FLOOR);
-
-                result.add(new Measure(newQuantity, units_.get(i).build()));
+                BigDecimal roundedQuantity =
+                    quantity.multiply(EPSILON_MULTIPLIER).setScale(0, RoundingMode.FLOOR);
+                intValues.add(roundedQuantity);
 
                 // Keep the residual of the quantity.
                 //   For example: `3.6 feet`, keep only `0.6 feet`
-                quantity = quantity.subtract(newQuantity);
+                quantity = quantity.subtract(roundedQuantity);
                 if (quantity.compareTo(BigDecimal.ZERO) == -1) {
                     quantity = BigDecimal.ZERO;
                 }
             } else { // LAST ELEMENT
+                if (rounder == null) {
+                    // Nothing to do for the last element.
+                    break;
+                }
+
+                // Round the last value
+                // TODO(ICU-21288): get smarter about precision for mixed units.
+                DecimalQuantity quant = new DecimalQuantity_DualStorageBCD(quantity);
+                rounder.apply(quant);
+                quantity = quant.toBigDecimal();
+                if (i == 0) {
+                    // Last element is also the first element, so we're done
+                    break;
+                }
+
+                // Check if there's a carry, and bubble it back up the resulting intValues.
+                BigDecimal carry = unitConverters_.get(i)
+                                       .convertInverse(quantity)
+                                       .multiply(EPSILON_MULTIPLIER)
+                                       .setScale(0, RoundingMode.FLOOR);
+                if (carry.compareTo(BigDecimal.ZERO) <= 0) { // carry is not greater than zero
+                    break;
+                }
+                quantity = quantity.subtract(unitConverters_.get(i).convert(carry));
+                intValues.set(i - 1, intValues.get(i - 1).add(carry));
+
+                // We don't use the first converter: that one is for the input unit
+                for (int j = i - 1; j > 0; j--) {
+                    carry = unitConverters_.get(j)
+                                .convertInverse(intValues.get(j))
+                                .multiply(EPSILON_MULTIPLIER)
+                                .setScale(0, RoundingMode.FLOOR);
+                    if (carry.compareTo(BigDecimal.ZERO) <= 0) { // carry is not greater than zero
+                        break;
+                    }
+                    intValues.set(j, intValues.get(j).subtract(unitConverters_.get(j).convert(carry)));
+                    intValues.set(j - 1, intValues.get(j - 1).add(carry));
+                }
+            }
+        }
+
+        // Package values into Measure instances in result:
+        for (int i = 0, n = unitConverters_.size(); i < n; ++i) {
+            if (i < n - 1) {
+                result.add(new Measure(intValues.get(i), units_.get(i).build()));
+            } else {
                 result.add(new Measure(quantity, units_.get(i).build()));
             }
         }
 
+        for (int i = 0; i < result.size(); i++) {
+            for (int j = i; j < result.size(); j++) {
+                // Find the next expected unit, and swap it into place.
+                if (result.get(j).getUnit().equals(outputUnits_.get(i))) {
+                    if (j != i) {
+                        Measure tmp = result.get(j);
+                        result.set(j, result.get(i));
+                        result.set(i, tmp);
+                    }
+                }
+            }
+        }
+    
         return result;
     }
+
+    @Override
+    public String toString() {
+        return "ComplexUnitsConverter [unitConverters_=" + unitConverters_ + ", units_=" + units_ + "]";
+    }
 }
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 ac8b1ed..d46487d 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
@@ -1,7 +1,5 @@
 // © 2020 and later: Unicode, Inc. and others.
 // License & terms of use: http://www.unicode.org/copyright.html
-
-
 package com.ibm.icu.impl.units;
 
 import com.ibm.icu.util.BytesTrie;
@@ -765,4 +763,9 @@
             return o1.compareTo(o2);
         }
     }
-}
\ No newline at end of file
+
+    @Override
+    public String toString() {
+        return "MeasureUnitImpl [" + build().getIdentifier() + "]";
+    }
+}
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitConverter.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitConverter.java
index 9ac7e1c..7a38852 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitConverter.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitConverter.java
@@ -1,7 +1,5 @@
 // © 2020 and later: Unicode, Inc. and others.
 // License & terms of use: http://www.unicode.org/copyright.html
-
-
 package com.ibm.icu.impl.units;
 
 import com.ibm.icu.util.MeasureUnit;
@@ -88,6 +86,10 @@
         return inputValue.multiply(this.conversionRate).add(offset);
     }
 
+    public BigDecimal convertInverse(BigDecimal inputValue) {
+        return inputValue.subtract(offset).divide(this.conversionRate, DECIMAL128);
+    }
+
     public enum Convertibility {
         CONVERTIBLE,
         RECIPROCAL,
@@ -308,4 +310,9 @@
             }
         }
     }
+
+    @Override
+    public String toString() {
+        return "UnitConverter [conversionRate=" + conversionRate + ", offset=" + offset + "]";
+    }
 }
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitPreferences.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitPreferences.java
index ac5d9a7..5dedeeb 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitPreferences.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitPreferences.java
@@ -1,7 +1,5 @@
 // © 2020 and later: Unicode, Inc. and others.
 // License & terms of use: http://www.unicode.org/copyright.html
-
-
 package com.ibm.icu.impl.units;
 
 import com.ibm.icu.impl.ICUData;
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitsRouter.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitsRouter.java
index 4f8fbb6..f2ebfda 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitsRouter.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/units/UnitsRouter.java
@@ -1,9 +1,10 @@
 // © 2020 and later: Unicode, Inc. and others.
 // License & terms of use: http://www.unicode.org/copyright.html
-
-
 package com.ibm.icu.impl.units;
 
+import com.ibm.icu.impl.IllegalIcuArgumentException;
+import com.ibm.icu.impl.number.MicroProps;
+import com.ibm.icu.number.Precision;
 import com.ibm.icu.util.Measure;
 import com.ibm.icu.util.MeasureUnit;
 
@@ -79,27 +80,55 @@
         }
     }
 
-    public RouteResult route(BigDecimal quantity) {
-        for (ConverterPreference converterPreference :
-                converterPreferences_) {
+    /** If micros.rounder is a BogusRounder, this function replaces it with a valid one. */
+    public RouteResult route(BigDecimal quantity, MicroProps micros) {
+        Precision rounder = micros == null ? null : micros.rounder;
+        ConverterPreference converterPreference = null;
+        for (ConverterPreference itr : converterPreferences_) {
+            converterPreference = itr;
             if (converterPreference.converter.greaterThanOrEqual(quantity, converterPreference.limit)) {
-                return new RouteResult(
-                        converterPreference.converter.convert(quantity),
-                        converterPreference.precision,
-                        converterPreference.targetUnit
-                );
+                break;
+            }
+        }
+        assert converterPreference != null;
+        assert converterPreference.precision != null;
+
+        // Set up the rounder for this preference's precision
+        if (rounder != null && rounder instanceof Precision.BogusRounder) {
+            Precision.BogusRounder bogus = (Precision.BogusRounder)rounder;
+            if (converterPreference.precision.length() > 0) {
+                rounder = bogus.into(parseSkeletonToPrecision(converterPreference.precision));
+            } else {
+                // We use the same rounding mode as COMPACT notation: known to be a
+                // human-friendly rounding mode: integers, but add a decimal digit
+                // as needed to ensure we have at least 2 significant digits.
+                rounder = bogus.into(Precision.integer().withMinDigits(2));
             }
         }
 
-        // In case of the `quantity` does not fit in any converter limit, use the last converter.
-        ConverterPreference lastConverterPreference = converterPreferences_.get(converterPreferences_.size() - 1);
+        if (micros != null) {
+            micros.rounder = rounder;
+        }
         return new RouteResult(
-                lastConverterPreference.converter.convert(quantity),
-                lastConverterPreference.precision,
-                lastConverterPreference.targetUnit
+                converterPreference.converter.convert(quantity, rounder),
+                converterPreference.targetUnit
         );
     }
 
+    private static Precision parseSkeletonToPrecision(String precisionSkeleton) {
+        final String kSkeletonPrefix = "precision-increment/";
+        if (!precisionSkeleton.startsWith(kSkeletonPrefix)) {
+            throw new IllegalIcuArgumentException("precisionSkeleton is only precision-increment");
+        }
+
+        // TODO(icu-units#104): the C++ code uses a more sophisticated
+        // parseIncrementOption which supports "withMinFraction" - e.g.
+        // "precision-increment/0.5". Test with a unit preference that uses
+        // this, and fix Java.
+        String incrementValue = precisionSkeleton.substring(kSkeletonPrefix.length());
+        return Precision.increment(new BigDecimal(incrementValue));
+    }
+
     /**
      * Returns the list of possible output units, i.e. the full set of
      * preferences, for the localized, usage-specific unit preferences.
@@ -152,20 +181,13 @@
         // TODO(icu-units/icu#21): figure out the right mixed unit API.
         public final List<Measure> measures;
 
-        // A skeleton string starting with a precision-increment.
-        //
-        // TODO(hugovdm): generalise? or narrow down to only a precision-increment?
-        // or document that other skeleton elements are ignored?
-        public final String precision;
-
         // The output unit for this RouteResult. This may be a MIXED unit - for
         // example: "yard-and-foot-and-inch", for which `measures` will have three
         // elements.
         public final MeasureUnitImpl outputUnit;
 
-        RouteResult(List<Measure> measures, String precision, MeasureUnitImpl outputUnit) {
+        RouteResult(List<Measure> measures, MeasureUnitImpl outputUnit) {
             this.measures = measures;
-            this.precision = precision;
             this.outputUnit = outputUnit;
         }
     }
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 3ace927..69df74d 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
@@ -1,7 +1,5 @@
 // © 2020 and later: Unicode, Inc. and others.
 // License & terms of use: http://www.unicode.org/copyright.html
-
-
 package com.ibm.icu.dev.test.impl;
 
 import com.ibm.icu.dev.test.TestUtil;
@@ -47,7 +45,7 @@
         ComplexUnitsConverter converter = new ComplexUnitsConverter(inputImpl, outputImpl, rates);
 
         // Significantly less than 2.0.
-        List<Measure> measures = converter.convert(BigDecimal.valueOf(1.9999));
+        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(),
@@ -58,13 +56,14 @@
         assertEquals("1.9999: measures[1] unit", MeasureUnit.INCH.getIdentifier(),
                 measures.get(1).getUnit().getIdentifier());
 
-        // TODO: 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): 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.
 
-        // TODO: reusing measures results in a leak.
         // A minimal nudge under 2.0.
-        List<Measure> measures2 = converter.convert(BigDecimal.valueOf(2.0).subtract(ComplexUnitsConverter.EPSILON));
+        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(),
@@ -83,11 +82,10 @@
         final MeasureUnitImpl inputImpl3 = MeasureUnitImpl.forIdentifier(input.getIdentifier());
         final MeasureUnitImpl outputImpl3 = MeasureUnitImpl.forIdentifier(output.getIdentifier());
 
-        // TODO: reusing converter results in a leak.
         ComplexUnitsConverter converter3 = new ComplexUnitsConverter(inputImpl3, outputImpl3, rates);
 
-        // TODO: reusing measures results in a leak.
-        List<Measure> measures3 = converter3.convert(BigDecimal.valueOf(2.0).subtract(ComplexUnitsConverter.EPSILON));
+        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(),
@@ -99,7 +97,7 @@
         // 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));
+        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(),
@@ -112,7 +110,7 @@
 
         // 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));
+        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(),
@@ -134,14 +132,14 @@
         ConversionRates conversionRates = new ConversionRates();
 
         ComplexUnitsConverter complexConverter = new ComplexUnitsConverter(source, target, conversionRates);
-        List<Measure> measures = complexConverter.convert(BigDecimal.valueOf(10.0));
+        List<Measure> measures = complexConverter.convert(BigDecimal.valueOf(10.0), null);
 
         assertEquals(measures.size(), 2);
-        assertEquals("inch-and-foot unit 0", "foot", measures.get(0).getUnit().getIdentifier());
-        assertEquals("inch-and-foot unit 1", "inch", measures.get(1).getUnit().getIdentifier());
+        assertEquals("inch-and-foot unit 0", "inch", measures.get(0).getUnit().getIdentifier());
+        assertEquals("inch-and-foot unit 1", "foot", measures.get(1).getUnit().getIdentifier());
 
-        assertTrue("inch-and-foot value 0", compareTwoBigDecimal(BigDecimal.valueOf(32), BigDecimal.valueOf(measures.get(0).getNumber().doubleValue()), BigDecimal.valueOf(0.0001)));
-        assertTrue("inch-and-foot value 1", compareTwoBigDecimal(BigDecimal.valueOf(9.7008), BigDecimal.valueOf(measures.get(1).getNumber().doubleValue()), BigDecimal.valueOf(0.0001)));
+        assertEquals("inch-and-foot value 0", 9.7008, measures.get(0).getNumber().doubleValue(), 0.0001);
+        assertEquals("inch-and-foot value 1", 32, measures.get(1).getNumber().doubleValue(), 0.0001);
     }
 
 
@@ -254,21 +252,36 @@
         for (TestCase testCase :
                 tests) {
             UnitConverter converter = new UnitConverter(testCase.source, testCase.target, conversionRates);
-            if (compareTwoBigDecimal(testCase.expected, converter.convert(testCase.input), BigDecimal.valueOf(0.000001))) {
+            BigDecimal got = converter.convert(testCase.input);
+            if (compareTwoBigDecimal(testCase.expected, got, BigDecimal.valueOf(0.000001))) {
                 continue;
             } else {
                 fail(new StringBuilder()
                         .append(testCase.category)
-                        .append(" ")
+                        .append(": Converting 1000 ")
                         .append(testCase.sourceString)
-                        .append(" ")
+                        .append(" to ")
                         .append(testCase.targetString)
-                        .append(" ")
-                        .append(converter.convert(testCase.input).toString())
-                        .append(" expected  ")
+                        .append(", got ")
+                        .append(got)
+                        .append(", expected ")
                         .append(testCase.expected.toString())
                         .toString());
             }
+            BigDecimal inverted = converter.convertInverse(testCase.input);
+            if (compareTwoBigDecimal(BigDecimal.valueOf(1000), inverted, BigDecimal.valueOf(0.000001))) {
+                continue;
+            } else {
+                fail(new StringBuilder()
+                .append("Converting back to ")
+                .append(testCase.sourceString)
+                .append(" from ")
+                .append(testCase.targetString)
+                .append(": got ")
+                .append(inverted)
+                .append(", expected 1000")
+                .toString());
+            }
         }
     }
 
@@ -345,7 +358,7 @@
         for (TestCase testCase :
                 tests) {
             UnitsRouter router = new UnitsRouter(testCase.inputUnit.second, testCase.region, testCase.usage);
-            List<Measure> measures = router.route(testCase.input).measures;
+            List<Measure> measures = router.route(testCase.input, null).measures;
 
             assertEquals("Measures size must be the same as expected units",
                     measures.size(), testCase.expectedInOrder.size());
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 3bed7ba..22bd114 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
@@ -715,21 +715,17 @@
                 4.28571,
                 "4 metric tons, 285 kilograms, 710 grams");
 
-//     // TODO(icu-units#73): deal with this "1 foot 12 inches" problem.
-//     // At the time of writing, this test would pass, but is commented out
-//     // because it reflects undesired behaviour:
-//     assertFormatSingle(
-//             u"Demonstrating the \"1 foot 12 inches\" problem",
-//             nullptr,
-//             u"unit/foot-and-inch",
-//             NumberFormatter::with()
-//                 .unit(MeasureUnit::forIdentifier("foot-and-inch"))
-//                 .precision(Precision::maxSignificantDigits(4))
-//                 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
-//             Locale("en-US"),
-//             1.9999,
-//             // This is undesireable but current behaviour:
-//             u"1 foot, 12 inches");
+        assertFormatSingle(
+                "Testing \"1 foot 12 inches\"",
+                null,
+                "unit/foot-and-inch",
+                NumberFormatter.with()
+                        .unit(MeasureUnit.forIdentifier("foot-and-inch"))
+                        .precision(Precision.maxSignificantDigits(4))
+                        .unitWidth(UnitWidth.FULL_NAME),
+                new ULocale("en-US"),
+                1.9999,
+                "2 feet, 0 inches");
     }
 
     @Test
@@ -1118,7 +1114,11 @@
                 new ULocale("en-ZA"),
                 30500,
                 "350 m");
-}
+
+        // TODO(icu-units#38): improve unit testing coverage. E.g. add
+        // vehicle-fuel triggering inversion conversion code. Test with 0 too,
+        // to see divide-by-zero behaviour.
+    }
 
 
     @Test