ICU-20418 Implementing concise number skeletons in ICU4J.
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/StringSegment.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/StringSegment.java
index ce3f667..da5da54 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/StringSegment.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/StringSegment.java
@@ -221,8 +221,14 @@
         return Utility.charSequenceHashCode(this);
     }
 
+    /** Returns a string representation useful for debugging. */
     @Override
     public String toString() {
         return str.substring(0, start) + "[" + str.substring(start, end) + "]" + str.substring(end);
     }
+
+    /** Returns a String that is equivalent to the CharSequence representation. */
+    public String asString() {
+        return str.substring(start, end);
+    }
 }
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 a6878a1..492f6b5 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
@@ -53,6 +53,7 @@
         STATE_INCREMENT_PRECISION,
         STATE_MEASURE_UNIT,
         STATE_PER_MEASURE_UNIT,
+        STATE_IDENTIFIER_UNIT,
         STATE_CURRENCY_UNIT,
         STATE_INTEGER_WIDTH,
         STATE_NUMBERING_SYSTEM,
@@ -76,6 +77,7 @@
         STEM_BASE_UNIT,
         STEM_PERCENT,
         STEM_PERMILLE,
+        STEM_PERCENT_100, // concise-only
         STEM_PRECISION_INTEGER,
         STEM_PRECISION_UNLIMITED,
         STEM_PRECISION_CURRENCY_STANDARD,
@@ -113,6 +115,7 @@
         STEM_PRECISION_INCREMENT,
         STEM_MEASURE_UNIT,
         STEM_PER_MEASURE_UNIT,
+        STEM_UNIT,
         STEM_CURRENCY,
         STEM_INTEGER_WIDTH,
         STEM_NUMBERING_SYSTEM,
@@ -174,11 +177,27 @@
         b.add("precision-increment", StemEnum.STEM_PRECISION_INCREMENT.ordinal());
         b.add("measure-unit", StemEnum.STEM_MEASURE_UNIT.ordinal());
         b.add("per-measure-unit", StemEnum.STEM_PER_MEASURE_UNIT.ordinal());
+        b.add("unit", StemEnum.STEM_UNIT.ordinal());
         b.add("currency", StemEnum.STEM_CURRENCY.ordinal());
         b.add("integer-width", StemEnum.STEM_INTEGER_WIDTH.ordinal());
         b.add("numbering-system", StemEnum.STEM_NUMBERING_SYSTEM.ordinal());
         b.add("scale", StemEnum.STEM_SCALE.ordinal());
 
+        // Section 3 (concise tokens):
+        b.add("K", StemEnum.STEM_COMPACT_SHORT.ordinal());
+        b.add("KK", StemEnum.STEM_COMPACT_LONG.ordinal());
+        b.add("%", StemEnum.STEM_PERCENT.ordinal());
+        b.add("%x100", StemEnum.STEM_PERCENT_100.ordinal());
+        b.add(",_", StemEnum.STEM_GROUP_OFF.ordinal());
+        b.add(",?", StemEnum.STEM_GROUP_MIN2.ordinal());
+        b.add(",!", StemEnum.STEM_GROUP_ON_ALIGNED.ordinal());
+        b.add("+!", StemEnum.STEM_SIGN_ALWAYS.ordinal());
+        b.add("+_", StemEnum.STEM_SIGN_NEVER.ordinal());
+        b.add("()", StemEnum.STEM_SIGN_ACCOUNTING.ordinal());
+        b.add("()!", StemEnum.STEM_SIGN_ACCOUNTING_ALWAYS.ordinal());
+        b.add("+?", StemEnum.STEM_SIGN_EXCEPT_ZERO.ordinal());
+        b.add("()?", StemEnum.STEM_SIGN_ACCOUNTING_EXCEPT_ZERO.ordinal());
+
         // Build the CharsTrie
         // TODO: Use SLOW or FAST here?
         return b.buildCharSequence(StringTrieBuilder.Option.FAST).toString();
@@ -604,6 +623,14 @@
             checkNull(macros.precision, segment);
             BlueprintHelpers.parseDigitsStem(segment, macros);
             return ParseState.STATE_NULL;
+        case 'E':
+            checkNull(macros.notation, segment);
+            BlueprintHelpers.parseScientificStem(segment, macros);
+            return ParseState.STATE_NULL;
+        case '0':
+            checkNull(macros.notation, segment);
+            BlueprintHelpers.parseIntegerStem(segment, macros);
+            return ParseState.STATE_NULL;
         }
 
         // Now look at the stemsTrie, which is already be pointing at our stem.
@@ -641,6 +668,13 @@
             macros.unit = StemToObject.unit(stem);
             return ParseState.STATE_NULL;
 
+        case STEM_PERCENT_100:
+            checkNull(macros.scale, segment);
+            checkNull(macros.unit, segment);
+            macros.scale = Scale.powerOfTen(2);
+            macros.unit = NoUnit.PERCENT;
+            return ParseState.STATE_NULL;
+
         case STEM_PRECISION_INTEGER:
         case STEM_PRECISION_UNLIMITED:
         case STEM_PRECISION_CURRENCY_STANDARD:
@@ -720,6 +754,11 @@
             checkNull(macros.perUnit, segment);
             return ParseState.STATE_PER_MEASURE_UNIT;
 
+        case STEM_UNIT:
+            checkNull(macros.unit, segment);
+            checkNull(macros.perUnit, segment);
+            return ParseState.STATE_IDENTIFIER_UNIT;
+
         case STEM_CURRENCY:
             checkNull(macros.unit, segment);
             return ParseState.STATE_CURRENCY_UNIT;
@@ -761,6 +800,9 @@
         case STATE_PER_MEASURE_UNIT:
             BlueprintHelpers.parseMeasurePerUnitOption(segment, macros);
             return ParseState.STATE_NULL;
+        case STATE_IDENTIFIER_UNIT:
+            BlueprintHelpers.parseIdentifierUnitOption(segment, macros);
+            return ParseState.STATE_NULL;
         case STATE_INCREMENT_PRECISION:
             BlueprintHelpers.parseIncrementOption(segment, macros);
             return ParseState.STATE_NULL;
@@ -970,7 +1012,7 @@
         }
 
         private static void parseMeasurePerUnitOption(StringSegment segment, MacroProps macros) {
-            // A little bit of a hack: safe the current unit (numerator), call the main measure unit
+            // A little bit of a hack: save the current unit (numerator), call the main measure unit
             // parsing code, put back the numerator unit, and put the new unit into per-unit.
             MeasureUnit numerator = macros.unit;
             parseMeasureUnitOption(segment, macros);
@@ -978,6 +1020,17 @@
             macros.unit = numerator;
         }
 
+        private static void parseIdentifierUnitOption(StringSegment segment, MacroProps macros) {
+            MeasureUnit[] units = MeasureUnit.parseCoreUnitIdentifier(segment.asString());
+            if (units == null) {
+                throw new SkeletonSyntaxException("Invalid core unit identifier", segment);
+            }
+            macros.unit = units[0];
+            if (units.length == 2) {
+                macros.perUnit = units[1];
+            }
+        }
+
         private static void parseFractionStem(StringSegment segment, MacroProps macros) {
             assert segment.charAt(0) == '.';
             int offset = 1;
@@ -1012,7 +1065,11 @@
             }
             // Use the public APIs to enforce bounds checking
             if (maxFrac == -1) {
-                macros.precision = Precision.minFraction(minFrac);
+                if (minFrac == 0) {
+                    macros.precision = Precision.unlimited();
+                } else {
+                    macros.precision = Precision.minFraction(minFrac);
+                }
             } else {
                 macros.precision = Precision.minMaxFraction(minFrac, maxFrac);
             }
@@ -1081,6 +1138,71 @@
             }
         }
 
+        private static void parseScientificStem(StringSegment segment, MacroProps macros) {
+            assert(segment.charAt(0) == 'E');
+            block:
+            {
+                int offset = 1;
+                if (segment.length() == offset) {
+                    break block;
+                }
+                boolean isEngineering = false;
+                if (segment.charAt(offset) == 'E') {
+                    isEngineering = true;
+                    offset++;
+                    if (segment.length() == offset) {
+                        break block;
+                    }
+                }
+                SignDisplay signDisplay = SignDisplay.AUTO;
+                if (segment.charAt(offset) == '+') {
+                    offset++;
+                    if (segment.length() == offset) {
+                        break block;
+                    }
+                    if (segment.charAt(offset) == '!') {
+                        signDisplay = SignDisplay.ALWAYS;
+                    } else if (segment.charAt(offset) == '?') {
+                        signDisplay = SignDisplay.EXCEPT_ZERO;
+                    } else {
+                        break block;
+                    }
+                    offset++;
+                    if (segment.length() == offset) {
+                        break block;
+                    }
+                }
+                int minDigits = 0;
+                for (; offset < segment.length(); offset++) {
+                    if (segment.charAt(offset) != '0') {
+                        break block;
+                    }
+                    minDigits++;
+                }
+                macros.notation = (isEngineering ? Notation.engineering() : Notation.scientific())
+                    .withExponentSignDisplay(signDisplay)
+                    .withMinExponentDigits(minDigits);
+                return;
+            }
+            throw new SkeletonSyntaxException("Invalid scientific stem", segment);
+        }
+
+        private static void parseIntegerStem(StringSegment segment, MacroProps macros) {
+            assert(segment.charAt(0) == '0');
+            int offset = 1;
+            for (; offset < segment.length(); offset++) {
+                if (segment.charAt(offset) != '0') {
+                    offset--;
+                    break;
+                }
+            }
+            if (offset < segment.length()) {
+                 throw new SkeletonSyntaxException("Invalid integer stem", segment);
+            }
+            macros.integerWidth = IntegerWidth.zeroFillTo(offset);
+            return;
+        }
+
         /** @return Whether we successfully found and parsed a frac-sig option. */
         private static boolean parseFracSigOption(StringSegment segment, MacroProps macros) {
             if (segment.charAt(0) != '@') {
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 eaf01d5..357fe96 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
@@ -205,6 +205,47 @@
         return MeasureUnit.addUnit(type, subType, factory);
     }
 
+    private static MeasureUnit findBySubType(String subType) {
+        populateCache();
+        for (Map<String, MeasureUnit> unitsForType : cache.values()) {
+            if (unitsForType.containsKey(subType)) {
+                return unitsForType.get(subType);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * For ICU use only.
+     * @internal
+     * @deprecated This API is ICU internal only.
+     */
+    @Deprecated
+    public static MeasureUnit[] parseCoreUnitIdentifier(String coreUnitIdentifier) {
+        // First search for the whole code unit identifier as a subType
+        MeasureUnit whole = findBySubType(coreUnitIdentifier);
+        if (whole != null) {
+            return new MeasureUnit[] { whole }; // found a numerator but not denominator
+        }
+
+        // If not found, try breaking apart numerator and denominator
+        int perIdx = coreUnitIdentifier.indexOf("-per-");
+        if (perIdx == -1) {
+            // String does not contain "-per-"
+            return null;
+        }
+        String numeratorStr = coreUnitIdentifier.substring(0, perIdx);
+        String denominatorStr = coreUnitIdentifier.substring(perIdx + 5);
+        MeasureUnit numerator = findBySubType(numeratorStr);
+        MeasureUnit denominator = findBySubType(denominatorStr);
+        if (numerator != null && denominator != null) {
+            return new MeasureUnit[] { numerator, denominator }; // found both a numerator and denominator
+        }
+
+        // The numerator or denominator were invalid
+        return null;
+    }
+
     /**
      * For ICU use only.
      * @internal
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 a12468f..4ba49dc 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
@@ -72,6 +72,7 @@
         assertFormatDescending(
                 "Basic",
                 "",
+                "",
                 NumberFormatter.with(),
                 ULocale.ENGLISH,
                 "87,650",
@@ -87,6 +88,7 @@
         assertFormatDescendingBig(
                 "Big Simple",
                 "notation-simple",
+                "",
                 NumberFormatter.with().notation(Notation.simple()),
                 ULocale.ENGLISH,
                 "87,650,000",
@@ -102,6 +104,7 @@
         assertFormatSingle(
                 "Basic with Negative Sign",
                 "",
+                "",
                 NumberFormatter.with(),
                 ULocale.ENGLISH,
                 -9876543.21,
@@ -113,6 +116,7 @@
         assertFormatDescending(
                 "Scientific",
                 "scientific",
+                "E0",
                 NumberFormatter.with().notation(Notation.scientific()),
                 ULocale.ENGLISH,
                 "8.765E4",
@@ -128,6 +132,7 @@
         assertFormatDescending(
                 "Engineering",
                 "engineering",
+                "EE0",
                 NumberFormatter.with().notation(Notation.engineering()),
                 ULocale.ENGLISH,
                 "87.65E3",
@@ -143,6 +148,7 @@
         assertFormatDescending(
                 "Scientific sign always shown",
                 "scientific/sign-always",
+                "E+!0",
                 NumberFormatter.with().notation(Notation.scientific().withExponentSignDisplay(SignDisplay.ALWAYS)),
                 ULocale.ENGLISH,
                 "8.765E+4",
@@ -158,6 +164,7 @@
         assertFormatDescending(
                 "Scientific min exponent digits",
                 "scientific/+ee",
+                "E00",
                 NumberFormatter.with().notation(Notation.scientific().withMinExponentDigits(2)),
                 ULocale.ENGLISH,
                 "8.765E04",
@@ -173,6 +180,7 @@
         assertFormatSingle(
                 "Scientific Negative",
                 "scientific",
+                "E0",
                 NumberFormatter.with().notation(Notation.scientific()),
                 ULocale.ENGLISH,
                 -1000000,
@@ -181,6 +189,7 @@
         assertFormatSingle(
                 "Scientific Infinity",
                 "scientific",
+                "E0",
                 NumberFormatter.with().notation(Notation.scientific()),
                 ULocale.ENGLISH,
                 Double.NEGATIVE_INFINITY,
@@ -189,6 +198,7 @@
         assertFormatSingle(
                 "Scientific NaN",
                 "scientific",
+                "E0",
                 NumberFormatter.with().notation(Notation.scientific()),
                 ULocale.ENGLISH,
                 Double.NaN,
@@ -200,6 +210,7 @@
         assertFormatDescendingBig(
                 "Compact Short",
                 "compact-short",
+                "K",
                 NumberFormatter.with().notation(Notation.compactShort()),
                 ULocale.ENGLISH,
                 "88M",
@@ -215,6 +226,7 @@
         assertFormatDescendingBig(
                 "Compact Long",
                 "compact-long",
+                "KK",
                 NumberFormatter.with().notation(Notation.compactLong()),
                 ULocale.ENGLISH,
                 "88 million",
@@ -230,6 +242,7 @@
         assertFormatDescending(
                 "Compact Short Currency",
                 "compact-short currency/USD",
+                "K currency/USD",
                 NumberFormatter.with().notation(Notation.compactShort()).unit(USD),
                 ULocale.ENGLISH,
                 "$88K",
@@ -245,6 +258,7 @@
         assertFormatDescending(
                 "Compact Short with ISO Currency",
                 "compact-short currency/USD unit-width-iso-code",
+                "K currency/USD unit-width-iso-code",
                 NumberFormatter.with().notation(Notation.compactShort()).unit(USD).unitWidth(UnitWidth.ISO_CODE),
                 ULocale.ENGLISH,
                 "USD 88K",
@@ -260,6 +274,7 @@
         assertFormatDescending(
                 "Compact Short with Long Name Currency",
                 "compact-short currency/USD unit-width-full-name",
+                "K currency/USD unit-width-full-name",
                 NumberFormatter.with().notation(Notation.compactShort()).unit(USD).unitWidth(UnitWidth.FULL_NAME),
                 ULocale.ENGLISH,
                 "88K US dollars",
@@ -277,6 +292,7 @@
         assertFormatDescending(
                 "Compact Long Currency",
                 "compact-long currency/USD",
+                "KK currency/USD",
                 NumberFormatter.with().notation(Notation.compactLong()).unit(USD),
                 ULocale.ENGLISH,
                 "$88K", // should be something like "$88 thousand"
@@ -294,6 +310,7 @@
         assertFormatDescending(
                 "Compact Long with ISO Currency",
                 "compact-long currency/USD unit-width-iso-code",
+                "KK currency/USD unit-width-iso-code",
                 NumberFormatter.with().notation(Notation.compactLong()).unit(USD).unitWidth(UnitWidth.ISO_CODE),
                 ULocale.ENGLISH,
                 "USD 88K", // should be something like "USD 88 thousand"
@@ -310,6 +327,7 @@
         assertFormatDescending(
                 "Compact Long with Long Name Currency",
                 "compact-long currency/USD unit-width-full-name",
+                "KK currency/USD unit-width-full-name",
                 NumberFormatter.with().notation(Notation.compactLong()).unit(USD).unitWidth(UnitWidth.FULL_NAME),
                 ULocale.ENGLISH,
                 "88 thousand US dollars",
@@ -325,6 +343,7 @@
         assertFormatSingle(
                 "Compact Plural One",
                 "compact-long",
+                "KK",
                 NumberFormatter.with().notation(Notation.compactLong()),
                 ULocale.forLanguageTag("es"),
                 1000000,
@@ -333,6 +352,7 @@
         assertFormatSingle(
                 "Compact Plural Other",
                 "compact-long",
+                "KK",
                 NumberFormatter.with().notation(Notation.compactLong()),
                 ULocale.forLanguageTag("es"),
                 2000000,
@@ -341,6 +361,7 @@
         assertFormatSingle(
                 "Compact with Negative Sign",
                 "compact-short",
+                "K",
                 NumberFormatter.with().notation(Notation.compactShort()),
                 ULocale.ENGLISH,
                 -9876543.21,
@@ -349,6 +370,7 @@
         assertFormatSingle(
                 "Compact Rounding",
                 "compact-short",
+                "K",
                 NumberFormatter.with().notation(Notation.compactShort()),
                 ULocale.ENGLISH,
                 990000,
@@ -357,6 +379,7 @@
         assertFormatSingle(
                 "Compact Rounding",
                 "compact-short",
+                "K",
                 NumberFormatter.with().notation(Notation.compactShort()),
                 ULocale.ENGLISH,
                 999000,
@@ -365,6 +388,7 @@
         assertFormatSingle(
                 "Compact Rounding",
                 "compact-short",
+                "K",
                 NumberFormatter.with().notation(Notation.compactShort()),
                 ULocale.ENGLISH,
                 999900,
@@ -373,6 +397,7 @@
         assertFormatSingle(
                 "Compact Rounding",
                 "compact-short",
+                "K",
                 NumberFormatter.with().notation(Notation.compactShort()),
                 ULocale.ENGLISH,
                 9900000,
@@ -381,6 +406,7 @@
         assertFormatSingle(
                 "Compact Rounding",
                 "compact-short",
+                "K",
                 NumberFormatter.with().notation(Notation.compactShort()),
                 ULocale.ENGLISH,
                 9990000,
@@ -389,6 +415,7 @@
         assertFormatSingle(
                 "Compact in zh-Hant-HK",
                 "compact-short",
+                "K",
                 NumberFormatter.with().notation(Notation.compactShort()),
                 new ULocale("zh-Hant-HK"),
                 1e7,
@@ -397,6 +424,7 @@
         assertFormatSingle(
                 "Compact in zh-Hant",
                 "compact-short",
+                "K",
                 NumberFormatter.with().notation(Notation.compactShort()),
                 new ULocale("zh-Hant"),
                 1e7,
@@ -405,6 +433,7 @@
         assertFormatSingle(
                 "Compact Infinity",
                 "compact-short",
+                "K",
                 NumberFormatter.with().notation(Notation.compactShort()),
                 ULocale.ENGLISH,
                 Double.NEGATIVE_INFINITY,
@@ -413,6 +442,7 @@
         assertFormatSingle(
                 "Compact NaN",
                 "compact-short",
+                "K",
                 NumberFormatter.with().notation(Notation.compactShort()),
                 ULocale.ENGLISH,
                 Double.NaN,
@@ -426,6 +456,7 @@
         assertFormatSingle(
                 "Compact Somali No Figure",
                 null, // feature not supported in skeleton
+                null,
                 NumberFormatter.with().notation(CompactNotation.forCustomData(compactCustomData)),
                 ULocale.ENGLISH,
                 1000,
@@ -437,6 +468,7 @@
         assertFormatDescending(
                 "Meters Short",
                 "measure-unit/length-meter",
+                "unit/meter",
                 NumberFormatter.with().unit(MeasureUnit.METER),
                 ULocale.ENGLISH,
                 "87,650 m",
@@ -452,6 +484,7 @@
         assertFormatDescending(
                 "Meters Long",
                 "measure-unit/length-meter unit-width-full-name",
+                "unit/meter unit-width-full-name",
                 NumberFormatter.with().unit(MeasureUnit.METER).unitWidth(UnitWidth.FULL_NAME),
                 ULocale.ENGLISH,
                 "87,650 meters",
@@ -467,6 +500,7 @@
         assertFormatDescending(
                 "Compact Meters Long",
                 "compact-long measure-unit/length-meter unit-width-full-name",
+                "KK unit/meter unit-width-full-name",
                 NumberFormatter.with().notation(Notation.compactLong()).unit(MeasureUnit.METER)
                         .unitWidth(UnitWidth.FULL_NAME),
                 ULocale.ENGLISH,
@@ -483,6 +517,7 @@
         assertFormatSingleMeasure(
                 "Meters with Measure Input",
                 "unit-width-full-name",
+                "unit-width-full-name",
                 NumberFormatter.with().unitWidth(UnitWidth.FULL_NAME),
                 ULocale.ENGLISH,
                 new Measure(5.43, MeasureUnit.METER),
@@ -491,6 +526,7 @@
         assertFormatSingleMeasure(
                 "Measure format method takes precedence over fluent chain",
                 "measure-unit/length-meter",
+                "unit/meter",
                 NumberFormatter.with().unit(MeasureUnit.METER),
                 ULocale.ENGLISH,
                 new Measure(5.43, USD),
@@ -499,6 +535,7 @@
         assertFormatSingle(
                 "Meters with Negative Sign",
                 "measure-unit/length-meter",
+                "unit/meter",
                 NumberFormatter.with().unit(MeasureUnit.METER),
                 ULocale.ENGLISH,
                 -9876543.21,
@@ -508,6 +545,7 @@
         assertFormatSingle(
                 "Interesting Data Fallback 1",
                 "measure-unit/duration-day unit-width-full-name",
+                "unit/day unit-width-full-name",
                 NumberFormatter.with().unit(MeasureUnit.DAY).unitWidth(UnitWidth.FULL_NAME),
                 ULocale.forLanguageTag("brx"),
                 5.43,
@@ -517,6 +555,7 @@
         assertFormatSingle(
                 "Interesting Data Fallback 2",
                 "measure-unit/duration-day unit-width-narrow",
+                "unit/day unit-width-narrow",
                 NumberFormatter.with().unit(MeasureUnit.DAY).unitWidth(UnitWidth.NARROW),
                 ULocale.forLanguageTag("brx"),
                 5.43,
@@ -527,6 +566,7 @@
         assertFormatSingle(
                 "Interesting Data Fallback 3",
                 "measure-unit/area-square-meter unit-width-narrow",
+                "unit/square-meter unit-width-narrow",
                 NumberFormatter.with().unit(MeasureUnit.SQUARE_METER).unitWidth(UnitWidth.NARROW),
                 ULocale.forLanguageTag("en-GB"),
                 5.43,
@@ -536,6 +576,7 @@
         assertFormatSingle(
                 "Interesting Data Fallback 4",
                 "measure-unit/area-square-meter unit-width-narrow",
+                "unit/square-meter unit-width-narrow",
                 NumberFormatter.with().unit(MeasureUnit.SQUARE_METER).unitWidth(UnitWidth.NARROW),
                 ULocale.forLanguageTag("root"),
                 5.43,
@@ -546,6 +587,7 @@
         assertFormatSingle(
                 "MeasureUnit Difference between Narrow and Short (Narrow Version)",
                 "measure-unit/temperature-fahrenheit unit-width-narrow",
+                "unit/fahrenheit unit-width-narrow",
                 NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).unitWidth(UnitWidth.NARROW),
                 ULocale.forLanguageTag("es-US"),
                 5.43,
@@ -554,6 +596,7 @@
         assertFormatSingle(
                 "MeasureUnit Difference between Narrow and Short (Short Version)",
                 "measure-unit/temperature-fahrenheit unit-width-short",
+                "unit/fahrenheit unit-width-short",
                 NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).unitWidth(UnitWidth.SHORT),
                 ULocale.forLanguageTag("es-US"),
                 5.43,
@@ -562,6 +605,7 @@
         assertFormatSingle(
                 "MeasureUnit form without {0} in CLDR pattern",
                 "measure-unit/temperature-kelvin unit-width-full-name",
+                "unit/kelvin unit-width-full-name",
                 NumberFormatter.with().unit(MeasureUnit.KELVIN).unitWidth(UnitWidth.FULL_NAME),
                 ULocale.forLanguageTag("es-MX"),
                 1,
@@ -570,6 +614,7 @@
         assertFormatSingle(
                 "MeasureUnit form without {0} in CLDR pattern and wide base form",
                 "measure-unit/temperature-kelvin .00000000000000000000 unit-width-full-name",
+                "unit/kelvin .00000000000000000000 unit-width-full-name",
                 NumberFormatter.with()
                     .precision(Precision.fixedFraction(20))
                     .unit(MeasureUnit.KELVIN)
@@ -584,6 +629,7 @@
         assertFormatDescending(
                 "Meters Per Second Short (unit that simplifies) and perUnit method",
                 "measure-unit/length-meter per-measure-unit/duration-second",
+                "~unit/meter-per-second", // does not round-trip to the full skeleton above
                 NumberFormatter.with().unit(MeasureUnit.METER).perUnit(MeasureUnit.SECOND),
                 ULocale.ENGLISH,
                 "87,650 m/s",
@@ -599,6 +645,7 @@
         assertFormatDescending(
                 "Pounds Per Square Mile Short (secondary unit has per-format)",
                 "measure-unit/mass-pound per-measure-unit/area-square-mile",
+                "unit/pound-per-square-mile",
                 NumberFormatter.with().unit(MeasureUnit.POUND).perUnit(MeasureUnit.SQUARE_MILE),
                 ULocale.ENGLISH,
                 "87,650 lb/mi²",
@@ -614,6 +661,7 @@
         assertFormatDescending(
                 "Joules Per Furlong Short (unit with no simplifications or special patterns)",
                 "measure-unit/energy-joule per-measure-unit/length-furlong",
+                "unit/joule-per-furlong",
                 NumberFormatter.with().unit(MeasureUnit.JOULE).perUnit(MeasureUnit.FURLONG),
                 ULocale.ENGLISH,
                 "87,650 J/fur",
@@ -632,6 +680,7 @@
         assertFormatDescending(
                 "Currency",
                 "currency/GBP",
+                "currency/GBP",
                 NumberFormatter.with().unit(GBP),
                 ULocale.ENGLISH,
                 "£87,650.00",
@@ -647,6 +696,7 @@
         assertFormatDescending(
                 "Currency ISO",
                 "currency/GBP unit-width-iso-code",
+                "currency/GBP unit-width-iso-code",
                 NumberFormatter.with().unit(GBP).unitWidth(UnitWidth.ISO_CODE),
                 ULocale.ENGLISH,
                 "GBP 87,650.00",
@@ -662,6 +712,7 @@
         assertFormatDescending(
                 "Currency Long Name",
                 "currency/GBP unit-width-full-name",
+                "currency/GBP unit-width-full-name",
                 NumberFormatter.with().unit(GBP).unitWidth(UnitWidth.FULL_NAME),
                 ULocale.ENGLISH,
                 "87,650.00 British pounds",
@@ -677,6 +728,7 @@
         assertFormatDescending(
                 "Currency Hidden",
                 "currency/GBP unit-width-hidden",
+                "currency/GBP unit-width-hidden",
                 NumberFormatter.with().unit(GBP).unitWidth(UnitWidth.HIDDEN),
                 ULocale.ENGLISH,
                 "87,650.00",
@@ -692,6 +744,7 @@
         assertFormatSingleMeasure(
                 "Currency with CurrencyAmount Input",
                 "",
+                "",
                 NumberFormatter.with(),
                 ULocale.ENGLISH,
                 new CurrencyAmount(5.43, GBP),
@@ -700,6 +753,7 @@
         assertFormatSingle(
                 "Currency Long Name from Pattern Syntax",
                 null,
+                null,
                 NumberFormatter.fromDecimalFormat(
                         PatternStringParser.parseToProperties("0 ¤¤¤"),
                         DecimalFormatSymbols.getInstance(ULocale.ENGLISH),
@@ -711,6 +765,7 @@
         assertFormatSingle(
                 "Currency with Negative Sign",
                 "currency/GBP",
+                "currency/GBP",
                 NumberFormatter.with().unit(GBP),
                 ULocale.ENGLISH,
                 -9876543.21,
@@ -721,6 +776,7 @@
         assertFormatSingle(
                 "Currency Difference between Narrow and Short (Narrow Version)",
                 "currency/USD unit-width-narrow",
+                "currency/USD unit-width-narrow",
                 NumberFormatter.with().unit(USD).unitWidth(UnitWidth.NARROW),
                 ULocale.forLanguageTag("en-CA"),
                 5.43,
@@ -729,6 +785,7 @@
         assertFormatSingle(
                 "Currency Difference between Narrow and Short (Short Version)",
                 "currency/USD unit-width-short",
+                "currency/USD unit-width-short",
                 NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT),
                 ULocale.forLanguageTag("en-CA"),
                 5.43,
@@ -737,6 +794,7 @@
         assertFormatSingle(
                 "Currency-dependent format (Control)",
                 "currency/USD unit-width-short",
+                "currency/USD unit-width-short",
                 NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT),
                 ULocale.forLanguageTag("ca"),
                 444444.55,
@@ -745,6 +803,7 @@
         assertFormatSingle(
                 "Currency-dependent format (Test)",
                 "currency/ESP unit-width-short",
+                "currency/ESP unit-width-short",
                 NumberFormatter.with().unit(ESP).unitWidth(UnitWidth.SHORT),
                 ULocale.forLanguageTag("ca"),
                 444444.55,
@@ -753,6 +812,7 @@
         assertFormatSingle(
                 "Currency-dependent symbols (Control)",
                 "currency/USD unit-width-short",
+                "currency/USD unit-width-short",
                 NumberFormatter.with().unit(USD).unitWidth(UnitWidth.SHORT),
                 ULocale.forLanguageTag("pt-PT"),
                 444444.55,
@@ -763,6 +823,7 @@
         assertFormatSingle(
                 "Currency-dependent symbols (Test Short)",
                 "currency/PTE unit-width-short",
+                "currency/PTE unit-width-short",
                 NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.SHORT),
                 ULocale.forLanguageTag("pt-PT"),
                 444444.55,
@@ -771,6 +832,7 @@
         assertFormatSingle(
                 "Currency-dependent symbols (Test Narrow)",
                 "currency/PTE unit-width-narrow",
+                "currency/PTE unit-width-narrow",
                 NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.NARROW),
                 ULocale.forLanguageTag("pt-PT"),
                 444444.55,
@@ -779,6 +841,7 @@
         assertFormatSingle(
                 "Currency-dependent symbols (Test ISO Code)",
                 "currency/PTE unit-width-iso-code",
+                "currency/PTE unit-width-iso-code",
                 NumberFormatter.with().unit(PTE).unitWidth(UnitWidth.ISO_CODE),
                 ULocale.forLanguageTag("pt-PT"),
                 444444.55,
@@ -787,6 +850,7 @@
         assertFormatSingle(
                 "Plural form depending on visible digits (ICU-20499)",
                 "currency/RON unit-width-full-name",
+                "currency/RON unit-width-full-name",
                 NumberFormatter.with().unit(RON).unitWidth(UnitWidth.FULL_NAME),
                 ULocale.forLanguageTag("ro-RO"),
                 24,
@@ -798,6 +862,7 @@
         assertFormatDescending(
                 "Percent",
                 "percent",
+                "%",
                 NumberFormatter.with().unit(NoUnit.PERCENT),
                 ULocale.ENGLISH,
                 "87,650%",
@@ -813,6 +878,7 @@
         assertFormatDescending(
                 "Permille",
                 "permille",
+                "permille",
                 NumberFormatter.with().unit(NoUnit.PERMILLE),
                 ULocale.ENGLISH,
                 "87,650‰",
@@ -828,6 +894,7 @@
         assertFormatSingle(
                 "NoUnit Base",
                 "base-unit",
+                "",
                 NumberFormatter.with().unit(NoUnit.BASE),
                 ULocale.ENGLISH,
                 51423,
@@ -836,6 +903,7 @@
         assertFormatSingle(
                 "Percent with Negative Sign",
                 "percent",
+                "%",
                 NumberFormatter.with().unit(NoUnit.PERCENT),
                 ULocale.ENGLISH,
                 -98.7654321,
@@ -847,6 +915,7 @@
         assertFormatDescending(
                 "Integer",
                 "precision-integer",
+                ".",
                 NumberFormatter.with().precision(Precision.integer()),
                 ULocale.ENGLISH,
                 "87,650",
@@ -862,6 +931,7 @@
         assertFormatDescending(
                 "Fixed Fraction",
                 ".000",
+                ".000",
                 NumberFormatter.with().precision(Precision.fixedFraction(3)),
                 ULocale.ENGLISH,
                 "87,650.000",
@@ -877,6 +947,7 @@
         assertFormatDescending(
                 "Min Fraction",
                 ".0+",
+                ".0+",
                 NumberFormatter.with().precision(Precision.minFraction(1)),
                 ULocale.ENGLISH,
                 "87,650.0",
@@ -892,6 +963,7 @@
         assertFormatDescending(
                 "Max Fraction",
                 ".#",
+                ".#",
                 NumberFormatter.with().precision(Precision.maxFraction(1)),
                 ULocale.ENGLISH,
                 "87,650",
@@ -907,6 +979,7 @@
         assertFormatDescending(
                 "Min/Max Fraction",
                 ".0##",
+                ".0##",
                 NumberFormatter.with().precision(Precision.minMaxFraction(1, 3)),
                 ULocale.ENGLISH,
                 "87,650.0",
@@ -925,6 +998,7 @@
         assertFormatSingle(
                 "Fixed Significant",
                 "@@@",
+                "@@@",
                 NumberFormatter.with().precision(Precision.fixedSignificantDigits(3)),
                 ULocale.ENGLISH,
                 -98,
@@ -933,6 +1007,7 @@
         assertFormatSingle(
                 "Fixed Significant Rounding",
                 "@@@",
+                "@@@",
                 NumberFormatter.with().precision(Precision.fixedSignificantDigits(3)),
                 ULocale.ENGLISH,
                 -98.7654321,
@@ -941,6 +1016,7 @@
         assertFormatSingle(
                 "Fixed Significant Zero",
                 "@@@",
+                "@@@",
                 NumberFormatter.with().precision(Precision.fixedSignificantDigits(3)),
                 ULocale.ENGLISH,
                 0,
@@ -949,6 +1025,7 @@
         assertFormatSingle(
                 "Min Significant",
                 "@@+",
+                "@@+",
                 NumberFormatter.with().precision(Precision.minSignificantDigits(2)),
                 ULocale.ENGLISH,
                 -9,
@@ -957,6 +1034,7 @@
         assertFormatSingle(
                 "Max Significant",
                 "@###",
+                "@###",
                 NumberFormatter.with().precision(Precision.maxSignificantDigits(4)),
                 ULocale.ENGLISH,
                 98.7654321,
@@ -965,6 +1043,7 @@
         assertFormatSingle(
                 "Min/Max Significant",
                 "@@@#",
+                "@@@#",
                 NumberFormatter.with().precision(Precision.minMaxSignificantDigits(3, 4)),
                 ULocale.ENGLISH,
                 9.99999,
@@ -973,6 +1052,7 @@
         assertFormatSingle(
                 "Fixed Significant on zero with zero integer width",
                 "@ integer-width/+",
+                "@ integer-width/+",
                 NumberFormatter.with().precision(Precision.fixedSignificantDigits(1)).integerWidth(IntegerWidth.zeroFillTo(0)),
                 ULocale.ENGLISH,
                 0,
@@ -981,6 +1061,7 @@
         assertFormatSingle(
                 "Fixed Significant on zero with lots of integer width",
                 "@ integer-width/+000",
+                "@ 000",
                 NumberFormatter.with().precision(Precision.fixedSignificantDigits(1)).integerWidth(IntegerWidth.zeroFillTo(3)),
                 ULocale.ENGLISH,
                 0,
@@ -992,6 +1073,7 @@
         assertFormatDescending(
                 "Basic Significant", // for comparison
                 "@#",
+                "@#",
                 NumberFormatter.with().precision(Precision.maxSignificantDigits(2)),
                 ULocale.ENGLISH,
                 "88,000",
@@ -1007,6 +1089,7 @@
         assertFormatDescending(
                 "FracSig minMaxFrac minSig",
                 ".0#/@@@+",
+                ".0#/@@@+",
                 NumberFormatter.with().precision(Precision.minMaxFraction(1, 2).withMinDigits(3)),
                 ULocale.ENGLISH,
                 "87,650.0",
@@ -1022,6 +1105,7 @@
         assertFormatDescending(
                 "FracSig minMaxFrac maxSig A",
                 ".0##/@#",
+                ".0##/@#",
                 NumberFormatter.with().precision(Precision.minMaxFraction(1, 3).withMaxDigits(2)),
                 ULocale.ENGLISH,
                 "88,000.0", // maxSig beats maxFrac
@@ -1037,6 +1121,7 @@
         assertFormatDescending(
                 "FracSig minMaxFrac maxSig B",
                 ".00/@#",
+                ".00/@#",
                 NumberFormatter.with().precision(Precision.fixedFraction(2).withMaxDigits(2)),
                 ULocale.ENGLISH,
                 "88,000.00", // maxSig beats maxFrac
@@ -1052,6 +1137,7 @@
         assertFormatDescending(
                 "FracSig minFrac maxSig",
                 ".0+/@#",
+                ".0+/@#",
                 NumberFormatter.with().precision(Precision.minFraction(1).withMaxDigits(2)),
                 ULocale.ENGLISH,
                 "88,000.0",
@@ -1067,6 +1153,7 @@
         assertFormatSingle(
                 "FracSig with trailing zeros A",
                 ".00/@@@+",
+                ".00/@@@+",
                 NumberFormatter.with().precision(Precision.fixedFraction(2).withMinDigits(3)),
                 ULocale.ENGLISH,
                 0.1,
@@ -1075,6 +1162,7 @@
         assertFormatSingle(
                 "FracSig with trailing zeros B",
                 ".00/@@@+",
+                ".00/@@@+",
                 NumberFormatter.with().precision(Precision.fixedFraction(2).withMinDigits(3)),
                 ULocale.ENGLISH,
                 0.0999999,
@@ -1086,6 +1174,7 @@
         assertFormatDescending(
                 "Rounding None",
                 "precision-unlimited",
+                ".+",
                 NumberFormatter.with().precision(Precision.unlimited()),
                 ULocale.ENGLISH,
                 "87,650",
@@ -1101,6 +1190,7 @@
         assertFormatDescending(
                 "Increment",
                 "precision-increment/0.5",
+                "precision-increment/0.5",
                 NumberFormatter.with().precision(Precision.increment(BigDecimal.valueOf(0.5))),
                 ULocale.ENGLISH,
                 "87,650.0",
@@ -1116,6 +1206,7 @@
         assertFormatDescending(
                 "Increment with Min Fraction",
                 "precision-increment/0.50",
+                "precision-increment/0.50",
                 NumberFormatter.with().precision(Precision.increment(new BigDecimal("0.50"))),
                 ULocale.ENGLISH,
                 "87,650.00",
@@ -1131,6 +1222,7 @@
         assertFormatDescending(
                 "Strange Increment",
                 "precision-increment/3.140",
+                "precision-increment/3.140",
                 NumberFormatter.with().precision(Precision.increment(new BigDecimal("3.140"))),
                 ULocale.ENGLISH,
                 "87,649.960",
@@ -1146,6 +1238,7 @@
         assertFormatDescending(
                 "Increment Resolving to Power of 10",
                 "precision-increment/0.010",
+                "precision-increment/0.010",
                 NumberFormatter.with().precision(Precision.increment(new BigDecimal("0.010"))),
                 ULocale.ENGLISH,
                 "87,650.000",
@@ -1161,6 +1254,7 @@
         assertFormatDescending(
                 "Currency Standard",
                 "currency/CZK precision-currency-standard",
+                "currency/CZK precision-currency-standard",
                 NumberFormatter.with().precision(Precision.currency(CurrencyUsage.STANDARD)).unit(CZK),
                 ULocale.ENGLISH,
                 "CZK 87,650.00",
@@ -1176,6 +1270,7 @@
         assertFormatDescending(
                 "Currency Cash",
                 "currency/CZK precision-currency-cash",
+                "currency/CZK precision-currency-cash",
                 NumberFormatter.with().precision(Precision.currency(CurrencyUsage.CASH)).unit(CZK),
                 ULocale.ENGLISH,
                 "CZK 87,650",
@@ -1191,6 +1286,7 @@
         assertFormatDescending(
                 "Currency Cash with Nickel Rounding",
                 "currency/CAD precision-currency-cash",
+                "currency/CAD precision-currency-cash",
                 NumberFormatter.with().precision(Precision.currency(CurrencyUsage.CASH)).unit(CAD),
                 ULocale.ENGLISH,
                 "CA$87,650.00",
@@ -1206,6 +1302,7 @@
         assertFormatDescending(
                 "Currency not in top-level fluent chain",
                 "precision-integer", // calling .withCurrency() applies currency rounding rules immediately
+                ".",
                 NumberFormatter.with().precision(Precision.currency(CurrencyUsage.CASH).withCurrency(CZK)),
                 ULocale.ENGLISH,
                 "87,650",
@@ -1222,6 +1319,7 @@
         assertFormatDescending(
                 "Rounding Mode CEILING",
                 "precision-integer rounding-mode-ceiling",
+                ". rounding-mode-ceiling",
                 NumberFormatter.with().precision(Precision.integer()).roundingMode(RoundingMode.CEILING),
                 ULocale.ENGLISH,
                 "87,650",
@@ -1240,6 +1338,7 @@
         assertFormatDescendingBig(
                 "Western Grouping",
                 "group-auto",
+                "",
                 NumberFormatter.with().grouping(GroupingStrategy.AUTO),
                 ULocale.ENGLISH,
                 "87,650,000",
@@ -1255,6 +1354,7 @@
         assertFormatDescendingBig(
                 "Indic Grouping",
                 "group-auto",
+                "",
                 NumberFormatter.with().grouping(GroupingStrategy.AUTO),
                 new ULocale("en-IN"),
                 "8,76,50,000",
@@ -1270,6 +1370,7 @@
         assertFormatDescendingBig(
                 "Western Grouping, Min 2",
                 "group-min2",
+                ",?",
                 NumberFormatter.with().grouping(GroupingStrategy.MIN2),
                 ULocale.ENGLISH,
                 "87,650,000",
@@ -1285,6 +1386,7 @@
         assertFormatDescendingBig(
                 "Indic Grouping, Min 2",
                 "group-min2",
+                ",?",
                 NumberFormatter.with().grouping(GroupingStrategy.MIN2),
                 new ULocale("en-IN"),
                 "8,76,50,000",
@@ -1300,6 +1402,7 @@
         assertFormatDescendingBig(
                 "No Grouping",
                 "group-off",
+                ",_",
                 NumberFormatter.with().grouping(GroupingStrategy.OFF),
                 new ULocale("en-IN"),
                 "87650000",
@@ -1315,6 +1418,7 @@
         assertFormatDescendingBig(
                 "Indic locale with THOUSANDS grouping",
                 "group-thousands",
+                "group-thousands",
                 NumberFormatter.with().grouping(GroupingStrategy.THOUSANDS),
                 new ULocale("en-IN"),
                 "87,650,000",
@@ -1333,6 +1437,7 @@
         assertFormatDescendingBig(
                 "Polish Grouping",
                 "group-auto",
+                "",
                 NumberFormatter.with().grouping(GroupingStrategy.AUTO),
                 new ULocale("pl"),
                 "87 650 000",
@@ -1348,6 +1453,7 @@
         assertFormatDescendingBig(
                 "Polish Grouping, Min 2",
                 "group-min2",
+                ",?",
                 NumberFormatter.with().grouping(GroupingStrategy.MIN2),
                 new ULocale("pl"),
                 "87 650 000",
@@ -1363,6 +1469,7 @@
         assertFormatDescendingBig(
                 "Polish Grouping, Always",
                 "group-on-aligned",
+                ",!",
                 NumberFormatter.with().grouping(GroupingStrategy.ON_ALIGNED),
                 new ULocale("pl"),
                 "87 650 000",
@@ -1380,6 +1487,7 @@
         assertFormatDescendingBig(
                 "Bulgarian Currency Grouping",
                 "currency/USD group-auto",
+                "currency/USD",
                 NumberFormatter.with().grouping(GroupingStrategy.AUTO).unit(USD),
                 new ULocale("bg"),
                 "87650000,00 щ.д.",
@@ -1395,6 +1503,7 @@
         assertFormatDescendingBig(
                 "Bulgarian Currency Grouping, Always",
                 "currency/USD group-on-aligned",
+                "currency/USD ,!",
                 NumberFormatter.with().grouping(GroupingStrategy.ON_ALIGNED).unit(USD),
                 new ULocale("bg"),
                 "87 650 000,00 щ.д.",
@@ -1412,6 +1521,7 @@
         assertFormatDescendingBig(
                 "Custom Grouping via Internal API",
                 null,
+                null,
                 NumberFormatter.with().macros(macros),
                 ULocale.ENGLISH,
                 "8,7,6,5,0000",
@@ -1430,6 +1540,7 @@
         assertFormatDescending(
                 "Padding",
                 null,
+                null,
                 NumberFormatter.with().padding(Padder.none()),
                 ULocale.ENGLISH,
                 "87,650",
@@ -1445,6 +1556,7 @@
         assertFormatDescending(
                 "Padding",
                 null,
+                null,
                 NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX)),
                 ULocale.ENGLISH,
                 "**87,650",
@@ -1460,6 +1572,7 @@
         assertFormatDescending(
                 "Padding with code points",
                 null,
+                null,
                 NumberFormatter.with().padding(Padder.codePoints(0x101E4, 8, PadPosition.AFTER_PREFIX)),
                 ULocale.ENGLISH,
                 "𐇤𐇤87,650",
@@ -1475,6 +1588,7 @@
         assertFormatDescending(
                 "Padding with wide digits",
                 null,
+                null,
                 NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX))
                         .symbols(NumberingSystem.getInstanceByName("mathsanb")),
                 ULocale.ENGLISH,
@@ -1491,6 +1605,7 @@
         assertFormatDescending(
                 "Padding with currency spacing",
                 null,
+                null,
                 NumberFormatter.with().padding(Padder.codePoints('*', 10, PadPosition.AFTER_PREFIX)).unit(GBP)
                         .unitWidth(UnitWidth.ISO_CODE),
                 ULocale.ENGLISH,
@@ -1507,6 +1622,7 @@
         assertFormatSingle(
                 "Pad Before Prefix",
                 null,
+                null,
                 NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.BEFORE_PREFIX)),
                 ULocale.ENGLISH,
                 -88.88,
@@ -1515,6 +1631,7 @@
         assertFormatSingle(
                 "Pad After Prefix",
                 null,
+                null,
                 NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_PREFIX)),
                 ULocale.ENGLISH,
                 -88.88,
@@ -1523,6 +1640,7 @@
         assertFormatSingle(
                 "Pad Before Suffix",
                 null,
+                null,
                 NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.BEFORE_SUFFIX))
                         .unit(NoUnit.PERCENT),
                 ULocale.ENGLISH,
@@ -1532,6 +1650,7 @@
         assertFormatSingle(
                 "Pad After Suffix",
                 null,
+                null,
                 NumberFormatter.with().padding(Padder.codePoints('*', 8, PadPosition.AFTER_SUFFIX))
                         .unit(NoUnit.PERCENT),
                 ULocale.ENGLISH,
@@ -1541,6 +1660,7 @@
         assertFormatSingle(
                 "Currency Spacing with Zero Digit Padding Broken",
                 null,
+                null,
                 NumberFormatter.with().padding(Padder.codePoints('0', 12, PadPosition.AFTER_PREFIX)).unit(GBP)
                         .unitWidth(UnitWidth.ISO_CODE),
                 ULocale.ENGLISH,
@@ -1553,6 +1673,7 @@
         assertFormatDescending(
                 "Integer Width Default",
                 "integer-width/+0",
+                "0",
                 NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(1)),
                 ULocale.ENGLISH,
                 "87,650",
@@ -1568,6 +1689,7 @@
         assertFormatDescending(
                 "Integer Width Zero Fill 0",
                 "integer-width/+",
+                "integer-width/+",
                 NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(0)),
                 ULocale.ENGLISH,
                 "87,650",
@@ -1583,6 +1705,7 @@
         assertFormatDescending(
                 "Integer Width Zero Fill 3",
                 "integer-width/+000",
+                "000",
                 NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(3)),
                 ULocale.ENGLISH,
                 "87,650",
@@ -1598,6 +1721,7 @@
         assertFormatDescending(
                 "Integer Width Max 3",
                 "integer-width/##0",
+                "integer-width/##0",
                 NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(1).truncateAt(3)),
                 ULocale.ENGLISH,
                 "650",
@@ -1613,6 +1737,7 @@
         assertFormatDescending(
                 "Integer Width Fixed 2",
                 "integer-width/00",
+                "integer-width/00",
                 NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(2).truncateAt(2)),
                 ULocale.ENGLISH,
                 "50",
@@ -1628,6 +1753,7 @@
         assertFormatDescending(
                 "Integer Width Compact",
                 "compact-short integer-width/000",
+                "K integer-width/000",
                 NumberFormatter.with()
                     .notation(Notation.compactShort())
                     .integerWidth(IntegerWidth.zeroFillTo(3).truncateAt(3)),
@@ -1645,6 +1771,7 @@
         assertFormatDescending(
                 "Integer Width Scientific",
                 "scientific integer-width/000",
+                "E0 integer-width/000",
                 NumberFormatter.with()
                     .notation(Notation.scientific())
                     .integerWidth(IntegerWidth.zeroFillTo(3).truncateAt(3)),
@@ -1662,6 +1789,7 @@
         assertFormatDescending(
                 "Integer Width Engineering",
                 "engineering integer-width/000",
+                "EE0 integer-width/000",
                 NumberFormatter.with()
                     .notation(Notation.engineering())
                     .integerWidth(IntegerWidth.zeroFillTo(3).truncateAt(3)),
@@ -1679,6 +1807,7 @@
         assertFormatSingle(
                 "Integer Width Remove All A",
                 "integer-width/00",
+                "integer-width/00",
                 NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(2).truncateAt(2)),
                 ULocale.ENGLISH,
                 2500,
@@ -1687,6 +1816,7 @@
         assertFormatSingle(
                 "Integer Width Remove All B",
                 "integer-width/00",
+                "integer-width/00",
                 NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(2).truncateAt(2)),
                 ULocale.ENGLISH,
                 25000,
@@ -1695,6 +1825,7 @@
         assertFormatSingle(
                 "Integer Width Remove All B, Bytes Mode",
                 "integer-width/00",
+                "integer-width/00",
                 NumberFormatter.with().integerWidth(IntegerWidth.zeroFillTo(2).truncateAt(2)),
                 ULocale.ENGLISH,
                 // Note: this double produces all 17 significant digits
@@ -1707,6 +1838,7 @@
         assertFormatDescending(
                 "French Symbols with Japanese Data 1",
                 null,
+                null,
                 NumberFormatter.with().symbols(DecimalFormatSymbols.getInstance(ULocale.FRENCH)),
                 ULocale.JAPAN,
                 "87\u202F650",
@@ -1722,6 +1854,7 @@
         assertFormatSingle(
                 "French Symbols with Japanese Data 2",
                 null,
+                null,
                 NumberFormatter.with().notation(Notation.compactShort())
                         .symbols(DecimalFormatSymbols.getInstance(ULocale.FRENCH)),
                 ULocale.JAPAN,
@@ -1731,6 +1864,7 @@
         assertFormatDescending(
                 "Latin Numbering System with Arabic Data",
                 "currency/USD latin",
+                "currency/USD latin",
                 NumberFormatter.with().symbols(NumberingSystem.LATIN).unit(USD),
                 new ULocale("ar"),
                 "US$ 87,650.00",
@@ -1746,6 +1880,7 @@
         assertFormatDescending(
                 "Math Numbering System with French Data",
                 "numbering-system/mathsanb",
+                "numbering-system/mathsanb",
                 NumberFormatter.with().symbols(NumberingSystem.getInstanceByName("mathsanb")),
                 ULocale.FRENCH,
                 "𝟴𝟳\u202f𝟲𝟱𝟬",
@@ -1761,6 +1896,7 @@
         assertFormatSingle(
                 "Swiss Symbols (used in documentation)",
                 null,
+                null,
                 NumberFormatter.with().symbols(DecimalFormatSymbols.getInstance(new ULocale("de-CH"))),
                 ULocale.ENGLISH,
                 12345.67,
@@ -1769,6 +1905,7 @@
         assertFormatSingle(
                 "Myanmar Symbols (used in documentation)",
                 null,
+                null,
                 NumberFormatter.with().symbols(DecimalFormatSymbols.getInstance(new ULocale("my_MY"))),
                 ULocale.ENGLISH,
                 12345.67,
@@ -1779,6 +1916,7 @@
         assertFormatSingle(
                 "Currency symbol should precede number in ar with NS latn",
                 "currency/USD latin",
+                "currency/USD latin",
                 NumberFormatter.with().symbols(NumberingSystem.LATIN).unit(USD),
                 new ULocale("ar"),
                 12345.67,
@@ -1787,6 +1925,7 @@
         assertFormatSingle(
                 "Currency symbol should precede number in ar@numbers=latn",
                 "currency/USD",
+                "currency/USD",
                 NumberFormatter.with().unit(USD),
                 new ULocale("ar@numbers=latn"),
                 12345.67,
@@ -1795,6 +1934,7 @@
         assertFormatSingle(
                 "Currency symbol should follow number in ar-EG with NS arab",
                 "currency/USD",
+                "currency/USD",
                 NumberFormatter.with().unit(USD),
                 new ULocale("ar-EG"),
                 12345.67,
@@ -1803,6 +1943,7 @@
         assertFormatSingle(
                 "Currency symbol should follow number in ar@numbers=arab",
                 "currency/USD",
+                "currency/USD",
                 NumberFormatter.with().unit(USD),
                 new ULocale("ar@numbers=arab"),
                 12345.67,
@@ -1811,6 +1952,7 @@
         assertFormatSingle(
                 "NumberingSystem in API should win over @numbers keyword",
                 "currency/USD latin",
+                "currency/USD latin",
                 NumberFormatter.with().symbols(NumberingSystem.LATIN).unit(USD),
                 new ULocale("ar@numbers=arab"),
                 12345.67,
@@ -1830,6 +1972,7 @@
         assertFormatSingle(
                 "Symbols object should be copied",
                 null,
+                null,
                 f,
                 ULocale.ENGLISH,
                 12345.67,
@@ -1838,6 +1981,7 @@
         assertFormatSingle(
                 "The last symbols setter wins",
                 "latin",
+                "latin",
                 NumberFormatter.with().symbols(symbols).symbols(NumberingSystem.LATIN),
                 ULocale.ENGLISH,
                 12345.67,
@@ -1846,6 +1990,7 @@
         assertFormatSingle(
                 "The last symbols setter wins",
                 null,
+                null,
                 NumberFormatter.with().symbols(NumberingSystem.LATIN).symbols(symbols),
                 ULocale.ENGLISH,
                 12345.67,
@@ -1861,6 +2006,7 @@
         assertFormatSingle(
                 "Custom Short Currency Symbol",
                 "$XXX",
+                "$XXX",
                 NumberFormatter.with().unit(Currency.getInstance("XXX")).symbols(dfs),
                 ULocale.ENGLISH,
                 12.3,
@@ -1872,6 +2018,7 @@
         assertFormatSingle(
                 "Sign Auto Positive",
                 "sign-auto",
+                "",
                 NumberFormatter.with().sign(SignDisplay.AUTO),
                 ULocale.ENGLISH,
                 444444,
@@ -1880,6 +2027,7 @@
         assertFormatSingle(
                 "Sign Auto Negative",
                 "sign-auto",
+                "",
                 NumberFormatter.with().sign(SignDisplay.AUTO),
                 ULocale.ENGLISH,
                 -444444,
@@ -1888,6 +2036,7 @@
         assertFormatSingle(
                 "Sign Auto Zero",
                 "sign-auto",
+                "",
                 NumberFormatter.with().sign(SignDisplay.AUTO),
                 ULocale.ENGLISH,
                 0,
@@ -1896,6 +2045,7 @@
         assertFormatSingle(
                 "Sign Always Positive",
                 "sign-always",
+                "+!",
                 NumberFormatter.with().sign(SignDisplay.ALWAYS),
                 ULocale.ENGLISH,
                 444444,
@@ -1904,6 +2054,7 @@
         assertFormatSingle(
                 "Sign Always Negative",
                 "sign-always",
+                "+!",
                 NumberFormatter.with().sign(SignDisplay.ALWAYS),
                 ULocale.ENGLISH,
                 -444444,
@@ -1912,6 +2063,7 @@
         assertFormatSingle(
                 "Sign Always Zero",
                 "sign-always",
+                "+!",
                 NumberFormatter.with().sign(SignDisplay.ALWAYS),
                 ULocale.ENGLISH,
                 0,
@@ -1920,6 +2072,7 @@
         assertFormatSingle(
                 "Sign Never Positive",
                 "sign-never",
+                "+_",
                 NumberFormatter.with().sign(SignDisplay.NEVER),
                 ULocale.ENGLISH,
                 444444,
@@ -1928,6 +2081,7 @@
         assertFormatSingle(
                 "Sign Never Negative",
                 "sign-never",
+                "+_",
                 NumberFormatter.with().sign(SignDisplay.NEVER),
                 ULocale.ENGLISH,
                 -444444,
@@ -1936,6 +2090,7 @@
         assertFormatSingle(
                 "Sign Never Zero",
                 "sign-never",
+                "+_",
                 NumberFormatter.with().sign(SignDisplay.NEVER),
                 ULocale.ENGLISH,
                 0,
@@ -1944,6 +2099,7 @@
         assertFormatSingle(
                 "Sign Accounting Positive",
                 "currency/USD sign-accounting",
+                "currency/USD ()",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD),
                 ULocale.ENGLISH,
                 444444,
@@ -1952,6 +2108,7 @@
         assertFormatSingle(
                 "Sign Accounting Negative",
                 "currency/USD sign-accounting",
+                "currency/USD ()",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD),
                 ULocale.ENGLISH,
                 -444444,
@@ -1960,6 +2117,7 @@
         assertFormatSingle(
                 "Sign Accounting Zero",
                 "currency/USD sign-accounting",
+                "currency/USD ()",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD),
                 ULocale.ENGLISH,
                 0,
@@ -1968,6 +2126,7 @@
         assertFormatSingle(
                 "Sign Accounting-Always Positive",
                 "currency/USD sign-accounting-always",
+                "currency/USD ()!",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING_ALWAYS).unit(USD),
                 ULocale.ENGLISH,
                 444444,
@@ -1976,6 +2135,7 @@
         assertFormatSingle(
                 "Sign Accounting-Always Negative",
                 "currency/USD sign-accounting-always",
+                "currency/USD ()!",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING_ALWAYS).unit(USD),
                 ULocale.ENGLISH,
                 -444444,
@@ -1984,6 +2144,7 @@
         assertFormatSingle(
                 "Sign Accounting-Always Zero",
                 "currency/USD sign-accounting-always",
+                "currency/USD ()!",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING_ALWAYS).unit(USD),
                 ULocale.ENGLISH,
                 0,
@@ -1992,6 +2153,7 @@
         assertFormatSingle(
                 "Sign Except-Zero Positive",
                 "sign-except-zero",
+                "+?",
                 NumberFormatter.with().sign(SignDisplay.EXCEPT_ZERO),
                 ULocale.ENGLISH,
                 444444,
@@ -2000,6 +2162,7 @@
         assertFormatSingle(
                 "Sign Except-Zero Negative",
                 "sign-except-zero",
+                "+?",
                 NumberFormatter.with().sign(SignDisplay.EXCEPT_ZERO),
                 ULocale.ENGLISH,
                 -444444,
@@ -2008,6 +2171,7 @@
         assertFormatSingle(
                 "Sign Except-Zero Zero",
                 "sign-except-zero",
+                "+?",
                 NumberFormatter.with().sign(SignDisplay.EXCEPT_ZERO),
                 ULocale.ENGLISH,
                 0,
@@ -2016,6 +2180,7 @@
         assertFormatSingle(
                 "Sign Accounting-Except-Zero Positive",
                 "currency/USD sign-accounting-except-zero",
+                "currency/USD ()?",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING_EXCEPT_ZERO).unit(USD),
                 ULocale.ENGLISH,
                 444444,
@@ -2024,6 +2189,7 @@
         assertFormatSingle(
                 "Sign Accounting-Except-Zero Negative",
                 "currency/USD sign-accounting-except-zero",
+                "currency/USD ()?",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING_EXCEPT_ZERO).unit(USD),
                 ULocale.ENGLISH,
                 -444444,
@@ -2032,6 +2198,7 @@
         assertFormatSingle(
                 "Sign Accounting-Except-Zero Zero",
                 "currency/USD sign-accounting-except-zero",
+                "currency/USD ()?",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING_EXCEPT_ZERO).unit(USD),
                 ULocale.ENGLISH,
                 0,
@@ -2040,6 +2207,7 @@
         assertFormatSingle(
                 "Sign Accounting Negative Hidden",
                 "currency/USD unit-width-hidden sign-accounting",
+                "currency/USD unit-width-hidden ()",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD).unitWidth(UnitWidth.HIDDEN),
                 ULocale.ENGLISH,
                 -444444,
@@ -2048,6 +2216,7 @@
         assertFormatSingle(
                 "Sign Accounting Negative Narrow",
                 "currency/USD unit-width-narrow sign-accounting",
+                "currency/USD unit-width-narrow ()",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD).unitWidth(UnitWidth.NARROW),
                 ULocale.CANADA,
                 -444444,
@@ -2056,6 +2225,7 @@
         assertFormatSingle(
                 "Sign Accounting Negative Short",
                 "currency/USD sign-accounting",
+                "currency/USD ()",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD).unitWidth(UnitWidth.SHORT),
                 ULocale.CANADA,
                 -444444,
@@ -2064,6 +2234,7 @@
         assertFormatSingle(
                 "Sign Accounting Negative Iso Code",
                 "currency/USD unit-width-iso-code sign-accounting",
+                "currency/USD unit-width-iso-code ()",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD).unitWidth(UnitWidth.ISO_CODE),
                 ULocale.CANADA,
                 -444444,
@@ -2074,6 +2245,7 @@
         assertFormatSingle(
                 "Sign Accounting Negative Full Name",
                 "currency/USD unit-width-full-name sign-accounting",
+                "currency/USD unit-width-full-name ()",
                 NumberFormatter.with().sign(SignDisplay.ACCOUNTING).unit(USD).unitWidth(UnitWidth.FULL_NAME),
                 ULocale.CANADA,
                 -444444,
@@ -2154,6 +2326,7 @@
         assertFormatDescending(
                 "Decimal Default",
                 "decimal-auto",
+                "",
                 NumberFormatter.with().decimal(DecimalSeparatorDisplay.AUTO),
                 ULocale.ENGLISH,
                 "87,650",
@@ -2169,6 +2342,7 @@
         assertFormatDescending(
                 "Decimal Always Shown",
                 "decimal-always",
+                "decimal-always",
                 NumberFormatter.with().decimal(DecimalSeparatorDisplay.ALWAYS),
                 ULocale.ENGLISH,
                 "87,650.",
@@ -2187,6 +2361,7 @@
         assertFormatDescending(
                 "Multiplier None",
                 "scale/1",
+                "",
                 NumberFormatter.with().scale(Scale.none()),
                 ULocale.ENGLISH,
                 "87,650",
@@ -2202,6 +2377,7 @@
         assertFormatDescending(
                 "Multiplier Power of Ten",
                 "scale/1000000",
+                "scale/1000000",
                 NumberFormatter.with().scale(Scale.powerOfTen(6)),
                 ULocale.ENGLISH,
                 "87,650,000,000",
@@ -2217,6 +2393,7 @@
         assertFormatDescending(
                 "Multiplier Arbitrary Double",
                 "scale/5.2",
+                "scale/5.2",
                 NumberFormatter.with().scale(Scale.byDouble(5.2)),
                 ULocale.ENGLISH,
                 "455,780",
@@ -2232,6 +2409,7 @@
         assertFormatDescending(
                 "Multiplier Arbitrary BigDecimal",
                 "scale/5.2",
+                "scale/5.2",
                 NumberFormatter.with().scale(Scale.byBigDecimal(new BigDecimal("5.2"))),
                 ULocale.ENGLISH,
                 "455,780",
@@ -2247,6 +2425,7 @@
         assertFormatDescending(
                 "Multiplier Arbitrary Double And Power Of Ten",
                 "scale/5200",
+                "scale/5200",
                 NumberFormatter.with().scale(Scale.byDoubleAndPowerOfTen(5.2, 3)),
                 ULocale.ENGLISH,
                 "455,780,000",
@@ -2262,6 +2441,7 @@
         assertFormatDescending(
                 "Multiplier Zero",
                 "scale/0",
+                "scale/0",
                 NumberFormatter.with().scale(Scale.byDouble(0)),
                 ULocale.ENGLISH,
                 "0",
@@ -2277,6 +2457,7 @@
         assertFormatSingle(
                 "Multiplier Skeleton Scientific Notation and Percent",
                 "percent scale/1E2",
+                "%x100",
                 NumberFormatter.with().unit(NoUnit.PERCENT).scale(Scale.powerOfTen(2)),
                 ULocale.ENGLISH,
                 0.5,
@@ -2285,6 +2466,7 @@
         assertFormatSingle(
                 "Negative Multiplier",
                 "scale/-5.2",
+                "scale/-5.2",
                 NumberFormatter.with().scale(Scale.byDouble(-5.2)),
                 ULocale.ENGLISH,
                 2,
@@ -2293,6 +2475,7 @@
         assertFormatSingle(
                 "Negative One Multiplier",
                 "scale/-1",
+                "scale/-1",
                 NumberFormatter.with().scale(Scale.byDouble(-1)),
                 ULocale.ENGLISH,
                 444444,
@@ -2301,6 +2484,7 @@
         assertFormatSingle(
                 "Two-Type Multiplier with Overlap",
                 "scale/10000",
+                "scale/10000",
                 NumberFormatter.with().scale(Scale.byDoubleAndPowerOfTen(100, 2)),
                 ULocale.ENGLISH,
                 2,
@@ -2346,6 +2530,7 @@
         FormattedNumber fmtd = assertFormatSingle(
                 message,
                 "",
+                "",
                 NumberFormatter.with(),
                 ULocale.ENGLISH,
                 -9876543210.12,
@@ -2399,6 +2584,7 @@
             FormattedNumber result = assertFormatSingle(
                     message,
                     "measure-unit/temperature-fahrenheit",
+                    "unit/fahrenheit",
                     NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT),
                     ULocale.ENGLISH,
                     68,
@@ -2418,6 +2604,7 @@
             FormattedNumber result = assertFormatSingle(
                     message,
                     "measure-unit/temperature-fahrenheit per-measure-unit/duration-day",
+                    "unit/fahrenheit-per-day",
                     NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).perUnit(MeasureUnit.DAY),
                     ULocale.ENGLISH,
                     68,
@@ -2437,6 +2624,7 @@
             FormattedNumber result = assertFormatSingle(
                     message,
                     "measure-unit/length-meter unit-width-full-name",
+                    "unit/meter unit-width-full-name",
                     NumberFormatter.with().unit(MeasureUnit.METER).unitWidth(UnitWidth.FULL_NAME),
                     ULocale.ENGLISH,
                     68,
@@ -2457,6 +2645,7 @@
             FormattedNumber result = assertFormatSingle(
                     message,
                     "measure-unit/length-meter per-measure-unit/duration-second unit-width-full-name",
+                    "~unit/meter-per-second unit-width-full-name", // does not round-trip to the full skeleton above
                     NumberFormatter.with().unit(MeasureUnit.METER).perUnit(MeasureUnit.SECOND).unitWidth(UnitWidth.FULL_NAME),
                     new ULocale("ky"), // locale with the interesting data
                     68,
@@ -2477,6 +2666,7 @@
             FormattedNumber result = assertFormatSingle(
                     message,
                     "measure-unit/temperature-fahrenheit unit-width-full-name",
+                    "unit/fahrenheit unit-width-full-name",
                     NumberFormatter.with().unit(MeasureUnit.FAHRENHEIT).unitWidth(UnitWidth.FULL_NAME),
                     new ULocale("vi"), // locale with the interesting data
                     68,
@@ -2500,6 +2690,7 @@
             FormattedNumber result = assertFormatSingle(
                     message,
                     "measure-unit/temperature-kelvin",
+                    "unit/kelvin",
                     NumberFormatter.with().unit(MeasureUnit.KELVIN),
                     new ULocale("fa"), // locale with the interesting data
                     68,
@@ -2519,6 +2710,7 @@
             FormattedNumber result = assertFormatSingle(
                     message,
                     "compact-short",
+                    "K",
                     NumberFormatter.with().notation(Notation.compactShort()),
                     ULocale.US,
                     65000,
@@ -2538,6 +2730,7 @@
             FormattedNumber result = assertFormatSingle(
                     message,
                     "compact-long",
+                    "KK",
                     NumberFormatter.with().notation(Notation.compactLong()),
                     ULocale.US,
                     65000,
@@ -2557,6 +2750,7 @@
             FormattedNumber result = assertFormatSingle(
                     message,
                     "compact-long",
+                    "KK",
                     NumberFormatter.with().notation(Notation.compactLong()),
                     new ULocale("fil"),  // locale with interesting data
                     6000,
@@ -2576,6 +2770,7 @@
             FormattedNumber result = assertFormatSingle(
                     message,
                     "compact-long",
+                    "KK",
                     NumberFormatter.with().notation(Notation.compactLong()),
                     new ULocale("he"),  // locale with interesting data
                     6000,
@@ -2595,6 +2790,7 @@
             FormattedNumber result = assertFormatSingle(
                     message,
                     "compact-short currency/USD",
+                    "K currency/USD",
                     NumberFormatter.with().notation(Notation.compactShort()).unit(USD),
                     new ULocale("sr_Latn"),  // locale with interesting data
                     65000,
@@ -2615,6 +2811,7 @@
             FormattedNumber result = assertFormatSingle(
                     message,
                     "currency/USD unit-width-full-name",
+                    "currency/USD unit-width-full-name",
                     NumberFormatter.with().unit(USD)
                         .unitWidth(UnitWidth.FULL_NAME),
                     ULocale.ENGLISH,
@@ -2638,6 +2835,7 @@
             FormattedNumber result = assertFormatSingle(
                     message,
                     "compact-long measure-unit/length-meter unit-width-full-name",
+                    "KK unit/meter unit-width-full-name",
                     NumberFormatter.with().notation(Notation.compactLong())
                         .unit(MeasureUnit.METER)
                         .unitWidth(UnitWidth.FULL_NAME),
@@ -2704,6 +2902,7 @@
         assertFormatSingle(
                 "Plural 1",
                 "currency/USD precision-integer unit-width-full-name",
+                "currency/USD . unit-width-full-name",
                 NumberFormatter.with().unit(USD).unitWidth(UnitWidth.FULL_NAME).precision(Precision.fixedFraction(0)),
                 ULocale.ENGLISH,
                 1,
@@ -2712,6 +2911,7 @@
         assertFormatSingle(
                 "Plural 1.00",
                 "currency/USD .00 unit-width-full-name",
+                "currency/USD .00 unit-width-full-name",
                 NumberFormatter.with().unit(USD).unitWidth(UnitWidth.FULL_NAME).precision(Precision.fixedFraction(2)),
                 ULocale.ENGLISH,
                 1,
@@ -2835,26 +3035,29 @@
     static void assertFormatDescending(
             String message,
             String skeleton,
+            String conciseSkeleton,
             UnlocalizedNumberFormatter f,
             ULocale locale,
             String... expected) {
         final double[] inputs = new double[] { 87650, 8765, 876.5, 87.65, 8.765, 0.8765, 0.08765, 0.008765, 0 };
-        assertFormatDescending(message, skeleton, f, locale, inputs, expected);
+        assertFormatDescending(message, skeleton, conciseSkeleton, f, locale, inputs, expected);
     }
 
     static void assertFormatDescendingBig(
             String message,
             String skeleton,
+            String conciseSkeleton,
             UnlocalizedNumberFormatter f,
             ULocale locale,
             String... expected) {
         final double[] inputs = new double[] { 87650000, 8765000, 876500, 87650, 8765, 876.5, 87.65, 8.765, 0 };
-        assertFormatDescending(message, skeleton, f, locale, inputs, expected);
+        assertFormatDescending(message, skeleton, conciseSkeleton, f, locale, inputs, expected);
     }
 
     static void assertFormatDescending(
             String message,
             String skeleton,
+            String conciseSkeleton,
             UnlocalizedNumberFormatter f,
             ULocale locale,
             double[] inputs,
@@ -2880,6 +3083,22 @@
                 String actual3 = l3.format(d).toString();
                 assertEquals(message + ": Skeleton Path: " + d, expected[i], actual3);
             }
+            // Concise skeletons should have same output, and usually round-trip to the normalized skeleton.
+            // If the concise skeleton starts with '~', disable the round-trip check.
+            boolean shouldRoundTrip = true;
+            if (conciseSkeleton.length() > 0 && conciseSkeleton.charAt(0) == '~') {
+                conciseSkeleton = conciseSkeleton.substring(1);
+                shouldRoundTrip = false;
+            }
+            LocalizedNumberFormatter l4 = NumberFormatter.forSkeleton(conciseSkeleton).locale(locale);
+            if (shouldRoundTrip) {
+                assertEquals(message + ": Concise Skeleton:", normalized, l4.toSkeleton());
+            }
+            for (int i = 0; i < 9; i++) {
+                double d = inputs[i];
+                String actual4 = l4.format(d).toString();
+                assertEquals(message + ": Concise Skeleton Path: '" + normalized + "': " + d, expected[i], actual4);
+            }
         } else {
             assertUndefinedSkeleton(f);
         }
@@ -2888,6 +3107,7 @@
     static FormattedNumber assertFormatSingle(
             String message,
             String skeleton,
+            String conciseSkeleton,
             UnlocalizedNumberFormatter f,
             ULocale locale,
             Number input,
@@ -2907,6 +3127,19 @@
             LocalizedNumberFormatter l3 = NumberFormatter.forSkeleton(normalized).locale(locale);
             String actual3 = l3.format(input).toString();
             assertEquals(message + ": Skeleton Path: " + input, expected, actual3);
+            // Concise skeletons should have same output, and usually round-trip to the normalized skeleton.
+            // If the concise skeleton starts with '~', disable the round-trip check.
+            boolean shouldRoundTrip = true;
+            if (conciseSkeleton.length() > 0 && conciseSkeleton.charAt(0) == '~') {
+                conciseSkeleton = conciseSkeleton.substring(1);
+                shouldRoundTrip = false;
+            }
+            LocalizedNumberFormatter l4 = NumberFormatter.forSkeleton(conciseSkeleton).locale(locale);
+            if (shouldRoundTrip) {
+                assertEquals(message + ": Concise Skeleton:", normalized, l4.toSkeleton());
+            }
+            String actual4 = l4.format(input).toString();
+            assertEquals(message + ": Concise Skeleton Path: '" + normalized + "': " + input, expected, actual4);
         } else {
             assertUndefinedSkeleton(f);
         }
@@ -2916,6 +3149,7 @@
     static void assertFormatSingleMeasure(
             String message,
             String skeleton,
+            String conciseSkeleton,
             UnlocalizedNumberFormatter f,
             ULocale locale,
             Measure input,
@@ -2934,6 +3168,19 @@
             LocalizedNumberFormatter l3 = NumberFormatter.forSkeleton(normalized).locale(locale);
             String actual3 = l3.format(input).toString();
             assertEquals(message + ": Skeleton Path: " + input, expected, actual3);
+            // Concise skeletons should have same output, and usually round-trip to the normalized skeleton.
+            // If the concise skeleton starts with '~', disable the round-trip check.
+            boolean shouldRoundTrip = true;
+            if (conciseSkeleton.length() > 0 && conciseSkeleton.charAt(0) == '~') {
+                conciseSkeleton = conciseSkeleton.substring(1);
+                shouldRoundTrip = false;
+            }
+            LocalizedNumberFormatter l4 = NumberFormatter.forSkeleton(conciseSkeleton).locale(locale);
+            if (shouldRoundTrip) {
+                assertEquals(message + ": Concise Skeleton:", normalized, l4.toSkeleton());
+            }
+            String actual4 = l4.format(input).toString();
+            assertEquals(message + ": Concise Skeleton Path: '" + normalized + "': " + input, expected, actual4);
         } else {
             assertUndefinedSkeleton(f);
         }
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java
index 8d5f83b..a2c9553 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java
@@ -57,6 +57,7 @@
                 "measure-unit/length-meter",
                 "measure-unit/area-square-meter",
                 "measure-unit/energy-joule per-measure-unit/length-meter",
+                "unit/square-meter-per-square-meter",
                 "currency/XXX",
                 "currency/ZZZ",
                 "currency/usd",
@@ -92,7 +93,20 @@
                 "numbering-system/latn",
                 "precision-integer/@##",
                 "precision-integer rounding-mode-ceiling",
-                "precision-currency-cash rounding-mode-ceiling" };
+                "precision-currency-cash rounding-mode-ceiling",
+                "0",
+                "00",
+                "000",
+                "E0",
+                "E00",
+                "E000",
+                "EE0",
+                "EE00",
+                "EE+?0",
+                "EE+?00",
+                "EE+!0",
+                "EE+!00",
+        };
 
         for (String cas : cases) {
             try {
@@ -137,7 +151,20 @@
                 "integer-width/+0#",
                 "integer-width/+#",
                 "integer-width/+#0",
-                "scientific/foo" };
+                "scientific/foo",
+                "E",
+                "E1",
+                "E+",
+                "E+?",
+                "E+!",
+                "E+0",
+                "EE",
+                "EE+",
+                "EEE",
+                "EEE0",
+                "001",
+                "00+",
+        };
 
         for (String cas : cases) {
             try {