ICU-20886 Implement trailingZeroDisplay

See #1583
diff --git a/docs/userguide/format_parse/numbers/skeletons.md b/docs/userguide/format_parse/numbers/skeletons.md
index fd5e2e9..fa30ba1 100644
--- a/docs/userguide/format_parse/numbers/skeletons.md
+++ b/docs/userguide/format_parse/numbers/skeletons.md
@@ -257,6 +257,16 @@
 digits, or zero or more `#` symbols, which implies the minimum significant
 digits when added to the `@` symbols.
 
+#### Trailing Zero Display
+
+***Starting with ICU 69***, a new option called `trailingZeroDisplay` was added.
+To enable this in an ICU number skeleton, append `/w` to any precision token:
+
+| Skeleton | Explanation | Equivalent C++ Code |
+|---|---|---|
+| `.00/w` | Exactly 2 fraction digits, but hide <br/> them if they are all 0 | `Precision::fixedFraction(2)` <br/> `.trailingZeroDisplay(` <br/> `UNUM_TRAILING_ZERO_HIDE_IF_WHOLE)` |
+| `precision-curren` <br/> `cy-standard/w` | Currency rounding, but hide <br/> fraction digits if they are all 0 | `Precision::currency(UCURR_USAGE_STANDARD)` <br/> `.trailingZeroDisplay(` <br/> `UNUM_TRAILING_ZERO_HIDE_IF_WHOLE)` |
+
 #### Wildcard Character
 
 ***Prior to ICU 67***, the symbol `+` was used for unlimited precision, instead
diff --git a/icu4c/source/i18n/number_rounding.cpp b/icu4c/source/i18n/number_rounding.cpp
index 0932d2f..40392ee 100644
--- a/icu4c/source/i18n/number_rounding.cpp
+++ b/icu4c/source/i18n/number_rounding.cpp
@@ -193,6 +193,12 @@
     }
 }
 
+Precision Precision::trailingZeroDisplay(UNumberTrailingZeroDisplay trailingZeroDisplay) const {
+    Precision result(*this); // copy constructor
+    result.fTrailingZeroDisplay = trailingZeroDisplay;
+    return result;
+}
+
 IncrementPrecision Precision::increment(double roundingIncrement) {
     if (roundingIncrement > 0.0) {
         return constructIncrement(roundingIncrement, 0);
@@ -256,11 +262,11 @@
     double increment = ucurr_getRoundingIncrementForUsage(isoCode, fUnion.currencyUsage, &status);
     int32_t minMaxFrac = ucurr_getDefaultFractionDigitsForUsage(
             isoCode, fUnion.currencyUsage, &status);
-    if (increment != 0.0) {
-        return constructIncrement(increment, minMaxFrac);
-    } else {
-        return constructFraction(minMaxFrac, minMaxFrac);
-    }
+    Precision retval = (increment != 0.0)
+        ? static_cast<Precision>(constructIncrement(increment, minMaxFrac))
+        : static_cast<Precision>(constructFraction(minMaxFrac, minMaxFrac));
+    retval.fTrailingZeroDisplay = fTrailingZeroDisplay;
+    return retval;
 }
 
 // Public method on CurrencyPrecision subclass
@@ -413,6 +419,7 @@
     if (fPassThrough) {
         return;
     }
+    int32_t resolvedMinFraction = 0;
     switch (fPrecision.fType) {
         case Precision::RND_BOGUS:
         case Precision::RND_ERROR:
@@ -429,8 +436,8 @@
                     getRoundingMagnitudeFraction(fPrecision.fUnion.fracSig.fMaxFrac),
                     fRoundingMode,
                     status);
-            value.setMinFraction(
-                    uprv_max(0, -getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac)));
+            resolvedMinFraction =
+                    uprv_max(0, -getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac));
             break;
 
         case Precision::RND_SIGNIFICANT:
@@ -438,8 +445,8 @@
                     getRoundingMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMaxSig),
                     fRoundingMode,
                     status);
-            value.setMinFraction(
-                    uprv_max(0, -getDisplayMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMinSig)));
+            resolvedMinFraction =
+                    uprv_max(0, -getDisplayMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMinSig));
             // Make sure that digits are displayed on zero.
             if (value.isZeroish() && fPrecision.fUnion.fracSig.fMinSig > 0) {
                 value.setMinInteger(1);
@@ -460,7 +467,7 @@
             int32_t displayMag1 = getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac);
             int32_t displayMag2 = getDisplayMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMinSig);
             int32_t displayMag = uprv_min(displayMag1, displayMag2);
-            value.setMinFraction(uprv_max(0, -displayMag));
+            resolvedMinFraction = uprv_max(0, -displayMag);
 
             break;
         }
@@ -470,7 +477,7 @@
                     fPrecision.fUnion.increment.fIncrement,
                     fRoundingMode,
                     status);
-            value.setMinFraction(fPrecision.fUnion.increment.fMinFrac);
+            resolvedMinFraction = fPrecision.fUnion.increment.fMinFrac;
             break;
 
         case Precision::RND_INCREMENT_ONE:
@@ -478,7 +485,7 @@
                     -fPrecision.fUnion.increment.fMaxFrac,
                     fRoundingMode,
                     status);
-            value.setMinFraction(fPrecision.fUnion.increment.fMinFrac);
+            resolvedMinFraction = fPrecision.fUnion.increment.fMinFrac;
             break;
 
         case Precision::RND_INCREMENT_FIVE:
@@ -486,7 +493,7 @@
                     -fPrecision.fUnion.increment.fMaxFrac,
                     fRoundingMode,
                     status);
-            value.setMinFraction(fPrecision.fUnion.increment.fMinFrac);
+            resolvedMinFraction = fPrecision.fUnion.increment.fMinFrac;
             break;
 
         case Precision::RND_CURRENCY:
@@ -496,10 +503,17 @@
         default:
             UPRV_UNREACHABLE;
     }
+
+    if (fPrecision.fTrailingZeroDisplay == UNUM_TRAILING_ZERO_AUTO ||
+            // PLURAL_OPERAND_T returns fraction digits as an integer
+            value.getPluralOperand(PLURAL_OPERAND_T) != 0) {
+        value.setMinFraction(resolvedMinFraction);
+    }
 }
 
 void RoundingImpl::apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode /*status*/) {
     // This method is intended for the one specific purpose of helping print "00.000E0".
+    // Question: Is it useful to look at trailingZeroDisplay here?
     U_ASSERT(isSignificantDigits());
     U_ASSERT(value.isZeroish());
     value.setMinFraction(fPrecision.fUnion.fracSig.fMinSig - minInt);
diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp
index 0110e95e..97d7430 100644
--- a/icu4c/source/i18n/number_skeletons.cpp
+++ b/icu4c/source/i18n/number_skeletons.cpp
@@ -616,7 +616,7 @@
         case u'@':
             CHECK_NULL(seen, precision, status);
             blueprint_helpers::parseDigitsStem(segment, macros, status);
-            return STATE_NULL;
+            return STATE_PRECISION;
         case u'E':
             CHECK_NULL(seen, notation, status);
             blueprint_helpers::parseScientificStem(segment, macros, status);
@@ -682,7 +682,7 @@
                 case STEM_PRECISION_INTEGER:
                     return STATE_FRACTION_PRECISION; // allows for "precision-integer/@##"
                 default:
-                    return STATE_NULL;
+                    return STATE_PRECISION;
             }
 
         case STEM_ROUNDING_MODE_CEILING:
@@ -813,7 +813,7 @@
             return STATE_NULL;
         case STATE_INCREMENT_PRECISION:
             blueprint_helpers::parseIncrementOption(segment, macros, status);
-            return STATE_NULL;
+            return STATE_PRECISION;
         case STATE_INTEGER_WIDTH:
             blueprint_helpers::parseIntegerWidthOption(segment, macros, status);
             return STATE_NULL;
@@ -853,6 +853,22 @@
     switch (stem) {
         case STATE_FRACTION_PRECISION:
             if (blueprint_helpers::parseFracSigOption(segment, macros, status)) {
+                return STATE_PRECISION;
+            }
+            if (U_FAILURE(status)) {
+                return {};
+            }
+            // If the fracSig option was not found, try normal precision options.
+            stem = STATE_PRECISION;
+            break;
+        default:
+            break;
+    }
+
+    // Trailing zeros option
+    switch (stem) {
+        case STATE_PRECISION:
+            if (blueprint_helpers::parseTrailingZeroOption(segment, macros, status)) {
                 return STATE_NULL;
             }
             if (U_FAILURE(status)) {
@@ -1362,6 +1378,14 @@
     return true;
 }
 
+bool blueprint_helpers::parseTrailingZeroOption(const StringSegment& segment, MacroProps& macros, UErrorCode&) {
+    if (segment == u"w") {
+        macros.precision = macros.precision.trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE);
+        return true;
+    }
+    return false;
+}
+
 void blueprint_helpers::parseIncrementOption(const StringSegment &segment, MacroProps &macros,
                                              UErrorCode &status) {
     number::impl::parseIncrementOption(segment, macros.precision, status);
@@ -1617,6 +1641,10 @@
         return false;
     }
 
+    if (macros.precision.fTrailingZeroDisplay == UNUM_TRAILING_ZERO_HIDE_IF_WHOLE) {
+        sb.append(u"/w", -1);
+    }
+
     // NOTE: Always return true for rounding because the default value depends on other options.
     return true;
 }
diff --git a/icu4c/source/i18n/number_skeletons.h b/icu4c/source/i18n/number_skeletons.h
index cb3cdf3..af63650 100644
--- a/icu4c/source/i18n/number_skeletons.h
+++ b/icu4c/source/i18n/number_skeletons.h
@@ -42,6 +42,7 @@
 
     STATE_SCIENTIFIC,
     STATE_FRACTION_PRECISION,
+    STATE_PRECISION,
 
     // Section 2: An option is required:
 
@@ -278,6 +279,9 @@
 /** @return Whether we successfully found and parsed a frac-sig option. */
 bool parseFracSigOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
 
+/** @return Whether we successfully found and parsed a trailing zero option. */
+bool parseTrailingZeroOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
+
 void parseIncrementOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
 
 void
diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h
index 10e4830..8d867c0 100644
--- a/icu4c/source/i18n/unicode/numberformatter.h
+++ b/icu4c/source/i18n/unicode/numberformatter.h
@@ -659,6 +659,17 @@
      */
     static CurrencyPrecision currency(UCurrencyUsage currencyUsage);
 
+#ifndef U_HIDE_DRAFT_API
+    /**
+     * Configure how trailing zeros are displayed on numbers. For example, to hide trailing zeros
+     * when the number is an integer, use UNUM_TRAILING_ZERO_HIDE_IF_WHOLE.
+     *
+     * @param trailingZeroDisplay Option to configure the display of trailing zeros.
+     * @draft ICU 69
+     */
+    Precision trailingZeroDisplay(UNumberTrailingZeroDisplay trailingZeroDisplay) const;
+#endif // U_HIDE_DRAFT_API
+
   private:
     enum PrecisionType {
         RND_BOGUS,
@@ -711,6 +722,8 @@
         UErrorCode errorCode; // For RND_ERROR
     } fUnion;
 
+    UNumberTrailingZeroDisplay fTrailingZeroDisplay = UNUM_TRAILING_ZERO_AUTO;
+
     typedef PrecisionUnion::FractionSignificantSettings FractionSignificantSettings;
     typedef PrecisionUnion::IncrementSettings IncrementSettings;
 
diff --git a/icu4c/source/i18n/unicode/unumberformatter.h b/icu4c/source/i18n/unicode/unumberformatter.h
index 1430f65..f92b780 100644
--- a/icu4c/source/i18n/unicode/unumberformatter.h
+++ b/icu4c/source/i18n/unicode/unumberformatter.h
@@ -494,6 +494,32 @@
             UNUM_DECIMAL_SEPARATOR_COUNT
 } UNumberDecimalSeparatorDisplay;
 
+#ifndef U_FORCE_HIDE_DRAFT_API
+/**
+ * An enum declaring how to render trailing zeros.
+ * 
+ * - UNUM_TRAILING_ZERO_AUTO: 0.90, 1.00, 1.10
+ * - UNUM_TRAILING_ZERO_HIDE_IF_WHOLE: 0.90, 1, 1.10
+ * 
+ * @draft ICU 69
+ */
+typedef enum UNumberTrailingZeroDisplay {
+    /**
+     * Display trailing zeros according to the settings for minimum fraction and significant digits.
+     *
+     * @draft ICU 69
+     */
+    UNUM_TRAILING_ZERO_AUTO,
+
+    /**
+     * Same as AUTO, but hide trailing zeros after the decimal separator if they are all zero.
+     *
+     * @draft ICU 69
+     */
+    UNUM_TRAILING_ZERO_HIDE_IF_WHOLE,
+} UNumberTrailingZeroDisplay;
+#endif // U_FORCE_HIDE_DRAFT_API
+
 struct UNumberFormatter;
 /**
  * C-compatible version of icu::number::LocalizedNumberFormatter.
diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp
index 20d0374..40124c2 100644
--- a/icu4c/source/test/intltest/numbertest_api.cpp
+++ b/icu4c/source/test/intltest/numbertest_api.cpp
@@ -2545,6 +2545,26 @@
             u"0.088",
             u"0.009",
             u"0.0");
+
+    assertFormatSingle(
+            u"Hide If Whole A",
+            u".00/w",
+            u".00/w",
+            NumberFormatter::with().precision(Precision::fixedFraction(2)
+                .trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE)),
+            Locale::getEnglish(),
+            1.2,
+            "1.20");
+
+    assertFormatSingle(
+            u"Hide If Whole B",
+            u".00/w",
+            u".00/w",
+            NumberFormatter::with().precision(Precision::fixedFraction(2)
+                .trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE)),
+            Locale::getEnglish(),
+            1,
+            "1");
 }
 
 void NumberFormatterApiTest::roundingFigures() {
@@ -2770,6 +2790,16 @@
             Locale::getEnglish(),
             9.99,
             u"10.0");
+
+    assertFormatSingle(
+            u"FracSig with Trailing Zero Display",
+            u".00/@@@*/w",
+            u".00/@@@+/w",
+            NumberFormatter::with().precision(Precision::fixedFraction(2).withMinDigits(3)
+                .trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE)),
+            Locale::getEnglish(),
+            1,
+            u"1");
 }
 
 void NumberFormatterApiTest::roundingOther() {
@@ -2888,6 +2918,25 @@
             u"CZK 0");
 
     assertFormatDescending(
+            u"Currency Standard with Trailing Zero Display",
+            u"currency/CZK precision-currency-standard/w",
+            u"currency/CZK precision-currency-standard/w",
+            NumberFormatter::with().precision(
+                        Precision::currency(UCurrencyUsage::UCURR_USAGE_STANDARD)
+                        .trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE))
+                    .unit(CZK),
+            Locale::getEnglish(),
+            u"CZK 87,650",
+            u"CZK 8,765",
+            u"CZK 876.50",
+            u"CZK 87.65",
+            u"CZK 8.76",
+            u"CZK 0.88",
+            u"CZK 0.09",
+            u"CZK 0.01",
+            u"CZK 0");
+
+    assertFormatDescending(
             u"Currency Cash with Nickel Rounding",
             u"currency/CAD precision-currency-cash",
             u"currency/CAD precision-currency-cash",
diff --git a/icu4c/source/test/intltest/numbertest_skeletons.cpp b/icu4c/source/test/intltest/numbertest_skeletons.cpp
index 67c755d..7be57f0 100644
--- a/icu4c/source/test/intltest/numbertest_skeletons.cpp
+++ b/icu4c/source/test/intltest/numbertest_skeletons.cpp
@@ -46,22 +46,28 @@
             u"@@@##",
             u"@@*",
             u"@@+",
+            u"@@+/w",
             u".000##",
             u".00*",
             u".00+",
             u".",
+            u"./w",
             u".*",
             u".+",
+            u".+/w",
             u".######",
             u".00/@@*",
             u".00/@@+",
             u".00/@##",
+            u".00/@##/w",
             u".00/@",
             u".00/@r",
             u".00/@@s",
             u".00/@@#r",
             u"precision-increment/3.14",
+            u"precision-increment/3.14/w",
             u"precision-currency-standard",
+            u"precision-currency-standard/w",
             u"precision-integer rounding-mode-half-up",
             u".00# rounding-mode-ceiling",
             u".00/@@* rounding-mode-floor",
@@ -152,6 +158,9 @@
 void NumberSkeletonTest::invalidTokens() {
     static const char16_t* cases[] = {
             u".00x",
+            u".00i",
+            u".00/x",
+            u".00/ww",
             u".00##0",
             u".##*",
             u".00##*",
@@ -226,6 +235,7 @@
 
 void NumberSkeletonTest::unexpectedTokens() {
     static const char16_t* cases[] = {
+            u".00/w/w",
             u"group-thousands/foo",
             u"precision-integer//@## group-off",
             u"precision-integer//@##  group-off",
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/CurrencyPrecision.java b/icu4j/main/classes/core/src/com/ibm/icu/number/CurrencyPrecision.java
index cdd3d99..6bb587a 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/CurrencyPrecision.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/CurrencyPrecision.java
@@ -40,7 +40,9 @@
      */
     public Precision withCurrency(Currency currency) {
         if (currency != null) {
-            return constructFromCurrency(this, currency);
+            Precision retval = constructFromCurrency(this, currency);
+            retval.trailingZeroDisplay = trailingZeroDisplay;
+            return retval;
         } else {
             throw new IllegalArgumentException("Currency must not be null");
         }
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java
index 091bccb..99e9ae3 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java
@@ -481,6 +481,33 @@
     }
 
     /**
+     * An enum declaring how to render trailing zeros.
+     *
+     * <ul>
+     * <li>AUTO: 0.90, 1.00, 1.10
+     * <li>HIDE_IF_WHOLE: 0.90, 1, 1.10
+     * </ul>
+     * 
+     * @draft ICU 69
+     * @provisional This API might change or be removed in a future release.
+     */
+    public static enum TrailingZeroDisplay {
+        /**
+         * Display trailing zeros according to the settings for minimum fraction and significant digits.
+         *
+         * @draft ICU 69
+         */
+        AUTO,
+    
+        /**
+         * Same as AUTO, but hide trailing zeros after the decimal separator if they are all zero.
+         *
+         * @draft ICU 69
+         */
+        HIDE_IF_WHOLE,
+    }
+
+    /**
      * Use a default threshold of 3. This means that the third time .format() is called, the data
      * structures get built using the "safe" code path. The first two calls to .format() will trigger the
      * unsafe code path.
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 3da00bf..f36a98a 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
@@ -16,6 +16,7 @@
 import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
 import com.ibm.icu.number.NumberFormatter.RoundingPriority;
 import com.ibm.icu.number.NumberFormatter.SignDisplay;
+import com.ibm.icu.number.NumberFormatter.TrailingZeroDisplay;
 import com.ibm.icu.number.NumberFormatter.UnitWidth;
 import com.ibm.icu.text.DecimalFormatSymbols;
 import com.ibm.icu.text.NumberingSystem;
@@ -51,6 +52,7 @@
         // Section 1: We might accept an option, but it is not required:
         STATE_SCIENTIFIC,
         STATE_FRACTION_PRECISION,
+        STATE_PRECISION,
 
         // Section 2: An option is required:
         STATE_INCREMENT_PRECISION,
@@ -671,7 +673,7 @@
         case '@':
             checkNull(macros.precision, segment);
             BlueprintHelpers.parseDigitsStem(segment, macros);
-            return ParseState.STATE_NULL;
+            return ParseState.STATE_PRECISION;
         case 'E':
             checkNull(macros.notation, segment);
             BlueprintHelpers.parseScientificStem(segment, macros);
@@ -734,7 +736,7 @@
             case STEM_PRECISION_INTEGER:
                 return ParseState.STATE_FRACTION_PRECISION; // allows for "precision-integer/@##"
             default:
-                return ParseState.STATE_NULL;
+                return ParseState.STATE_PRECISION;
             }
 
         case STEM_ROUNDING_MODE_CEILING:
@@ -871,7 +873,7 @@
             return ParseState.STATE_NULL;
         case STATE_INCREMENT_PRECISION:
             BlueprintHelpers.parseIncrementOption(segment, macros);
-            return ParseState.STATE_NULL;
+            return ParseState.STATE_PRECISION;
         case STATE_INTEGER_WIDTH:
             BlueprintHelpers.parseIntegerWidthOption(segment, macros);
             return ParseState.STATE_NULL;
@@ -905,6 +907,19 @@
         switch (stem) {
         case STATE_FRACTION_PRECISION:
             if (BlueprintHelpers.parseFracSigOption(segment, macros)) {
+                return ParseState.STATE_PRECISION;
+            }
+            // If the fracSig option was not found, try normal precision options.
+            stem = ParseState.STATE_PRECISION;
+            break;
+        default:
+            break;
+        }
+
+        // Trailing zeros option
+        switch (stem) {
+        case STATE_PRECISION:
+            if (BlueprintHelpers.parseTrailingZeroOption(segment, macros)) {
                 return ParseState.STATE_NULL;
             }
             break;
@@ -1351,6 +1366,15 @@
             return true;
         }
 
+        /** @return Whether we successfully found and parsed a trailing zero option. */
+        private static boolean parseTrailingZeroOption(StringSegment segment, MacroProps macros) {
+            if (segment.equals("w")) {
+                macros.precision = macros.precision.trailingZeroDisplay(TrailingZeroDisplay.HIDE_IF_WHOLE);
+                return true;
+            }
+            return false;
+        }
+
         private static void parseIncrementOption(StringSegment segment, MacroProps macros) {
             // Call segment.subSequence() because segment.toString() doesn't create a clean string.
             String str = segment.subSequence(0, segment.length()).toString();
@@ -1566,6 +1590,10 @@
                 }
             }
 
+            if (macros.precision.trailingZeroDisplay == TrailingZeroDisplay.HIDE_IF_WHOLE) {
+                sb.append("/w");
+            }
+
             // NOTE: Always return true for rounding because the default value depends on other options.
             return true;
         }
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/Precision.java b/icu4j/main/classes/core/src/com/ibm/icu/number/Precision.java
index 2d0a28a..31aded5 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/Precision.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/Precision.java
@@ -10,6 +10,8 @@
 import com.ibm.icu.impl.number.MultiplierProducer;
 import com.ibm.icu.impl.number.RoundingUtils;
 import com.ibm.icu.number.NumberFormatter.RoundingPriority;
+import com.ibm.icu.number.NumberFormatter.TrailingZeroDisplay;
+import com.ibm.icu.text.PluralRules.Operand;
 import com.ibm.icu.util.Currency;
 import com.ibm.icu.util.Currency.CurrencyUsage;
 
@@ -25,6 +27,7 @@
 public abstract class Precision {
 
     /* package-private final */ MathContext mathContext;
+    /* package-private final */ TrailingZeroDisplay trailingZeroDisplay;
 
     /* package-private */ Precision() {
         mathContext = RoundingUtils.DEFAULT_MATH_CONTEXT_UNLIMITED;
@@ -336,6 +339,12 @@
         }
     }
 
+    public Precision trailingZeroDisplay(TrailingZeroDisplay trailingZeroDisplay) {
+        Precision result = this.createCopy();
+        result.trailingZeroDisplay = trailingZeroDisplay;
+        return result;
+    }
+
     /**
      * Sets a MathContext to use on this Precision.
      *
@@ -611,7 +620,7 @@
         @Override
         public void apply(DecimalQuantity value) {
             value.roundToInfinity();
-            value.setMinFraction(0);
+            setResolvedMinFraction(value, 0);
         }
 
         @Override
@@ -634,7 +643,7 @@
         @Override
         public void apply(DecimalQuantity value) {
             value.roundToMagnitude(getRoundingMagnitudeFraction(maxFrac), mathContext);
-            value.setMinFraction(Math.max(0, -getDisplayMagnitudeFraction(minFrac)));
+            setResolvedMinFraction(value, Math.max(0, -getDisplayMagnitudeFraction(minFrac)));
         }
 
         @Override
@@ -657,7 +666,7 @@
         @Override
         public void apply(DecimalQuantity value) {
             value.roundToMagnitude(getRoundingMagnitudeSignificant(value, maxSig), mathContext);
-            value.setMinFraction(Math.max(0, -getDisplayMagnitudeSignificant(value, minSig)));
+            setResolvedMinFraction(value, Math.max(0, -getDisplayMagnitudeSignificant(value, minSig)));
             // Make sure that digits are displayed on zero.
             if (value.isZeroish() && minSig > 0) {
                 value.setMinInteger(1);
@@ -670,7 +679,7 @@
          */
         public void apply(DecimalQuantity quantity, int minInt) {
             assert quantity.isZeroish();
-            quantity.setMinFraction(minSig - minInt);
+            setResolvedMinFraction(quantity, minSig - minInt);
         }
 
         @Override
@@ -711,7 +720,7 @@
             int displayMag1 = getDisplayMagnitudeFraction(minFrac);
             int displayMag2 = getDisplayMagnitudeSignificant(value, minSig);
             int displayMag = Math.min(displayMag1, displayMag2);
-            value.setMinFraction(Math.max(0, -displayMag));
+            setResolvedMinFraction(value, Math.max(0, -displayMag));
         }
 
         @Override
@@ -735,7 +744,7 @@
         @Override
         public void apply(DecimalQuantity value) {
             value.roundToIncrement(increment, mathContext);
-            value.setMinFraction(increment.scale());
+            setResolvedMinFraction(value, increment.scale());
         }
 
         @Override
@@ -764,7 +773,7 @@
         @Override
         public void apply(DecimalQuantity value) {
             value.roundToMagnitude(-maxFrac, mathContext);
-            value.setMinFraction(minFrac);
+            setResolvedMinFraction(value, minFrac);
         }
 
         @Override
@@ -791,7 +800,7 @@
         @Override
         public void apply(DecimalQuantity value) {
             value.roundToNickel(-maxFrac, mathContext);
-            value.setMinFraction(minFrac);
+            setResolvedMinFraction(value, minFrac);
         }
 
         @Override
@@ -845,7 +854,17 @@
         return -minFrac;
     }
 
+    void setResolvedMinFraction(DecimalQuantity value, int resolvedMinFraction) {
+        if (trailingZeroDisplay == null ||
+                trailingZeroDisplay == TrailingZeroDisplay.AUTO ||
+                // PLURAL_OPERAND_T returns fraction digits as an integer
+                value.getPluralOperand(Operand.t) != 0) {
+            value.setMinFraction(resolvedMinFraction);
+        }
+    }
+
     private static int getDisplayMagnitudeSignificant(DecimalQuantity value, int minSig) {
+        // Question: Is it useful to look at trailingZeroDisplay here?
         int magnitude = value.isZeroish() ? 0 : value.getMagnitude();
         return magnitude - minSig + 1;
     }
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 2eb887c..cd56e27 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
@@ -39,6 +39,7 @@
 import com.ibm.icu.number.NumberFormatter.GroupingStrategy;
 import com.ibm.icu.number.NumberFormatter.RoundingPriority;
 import com.ibm.icu.number.NumberFormatter.SignDisplay;
+import com.ibm.icu.number.NumberFormatter.TrailingZeroDisplay;
 import com.ibm.icu.number.NumberFormatter.UnitWidth;
 import com.ibm.icu.number.Precision;
 import com.ibm.icu.number.Scale;
@@ -2508,6 +2509,26 @@
                 "0.088",
                 "0.009",
                 "0.0");
+
+        assertFormatSingle(
+                "Hide If Whole A",
+                ".00/w",
+                ".00/w",
+                NumberFormatter.with().precision(Precision.fixedFraction(2)
+                        .trailingZeroDisplay(TrailingZeroDisplay.HIDE_IF_WHOLE)),
+                ULocale.ENGLISH,
+                1.2,
+                "1.20");
+        
+        assertFormatSingle(
+                "Hide If Whole B",
+                ".00/w",
+                ".00/w",
+                NumberFormatter.with().precision(Precision.fixedFraction(2)
+                        .trailingZeroDisplay(TrailingZeroDisplay.HIDE_IF_WHOLE)),
+                ULocale.ENGLISH,
+                1,
+                "1");
     }
 
     @Test
@@ -2749,6 +2770,16 @@
                 ULocale.ENGLISH,
                 9.99,
                 "10.0");
+
+        assertFormatSingle(
+                "FracSig with Trailing Zero Display",
+                ".00/@@@*/w",
+                ".00/@@@+/w",
+                NumberFormatter.with().precision(Precision.fixedFraction(2).withMinDigits(3)
+                        .trailingZeroDisplay(TrailingZeroDisplay.HIDE_IF_WHOLE)),
+                ULocale.ENGLISH,
+                1,
+                "1");
     }
 
     @Test
@@ -2866,6 +2897,25 @@
                 "CZK 0");
 
         assertFormatDescending(
+                "Currency Standard with Trailing Zero Display",
+                "currency/CZK precision-currency-standard/w",
+                "currency/CZK precision-currency-standard/w",
+                NumberFormatter.with().precision(
+                                Precision.currency(CurrencyUsage.STANDARD)
+                                .trailingZeroDisplay(TrailingZeroDisplay.HIDE_IF_WHOLE))
+                        .unit(CZK),
+                ULocale.ENGLISH,
+                "CZK 87,650",
+                "CZK 8,765",
+                "CZK 876.50",
+                "CZK 87.65",
+                "CZK 8.76",
+                "CZK 0.88",
+                "CZK 0.09",
+                "CZK 0.01",
+                "CZK 0");
+
+        assertFormatDescending(
                 "Currency Cash with Nickel Rounding",
                 "currency/CAD precision-currency-cash",
                 "currency/CAD precision-currency-cash",
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 7f9645f..e563a9f 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
@@ -30,22 +30,28 @@
                 "@@@##",
                 "@@*",
                 "@@+",
+                "@@+/w",
                 ".000##",
                 ".00*",
                 ".00+",
                 ".",
+                "./w",
                 ".*",
                 ".+",
+                ".+/w",
                 ".######",
                 ".00/@@*",
                 ".00/@@+",
                 ".00/@##",
+                ".00/@##/w",
                 ".00/@",
                 ".00/@r",
                 ".00/@@s",
                 ".00/@@#r",
                 "precision-increment/3.14",
+                "precision-increment/3.14/w",
                 "precision-currency-standard",
+                "precision-currency-standard/w",
                 "precision-integer rounding-mode-half-up",
                 ".00# rounding-mode-ceiling",
                 ".00/@@* rounding-mode-floor",
@@ -135,6 +141,9 @@
     public void invalidTokens() {
         String[] cases = {
                 ".00x",
+                ".00i",
+                ".00/x",
+                ".00/ww",
                 ".00##0",
                 ".##*",
                 ".00##*",
@@ -225,6 +234,7 @@
     @Test
     public void unexpectedTokens() {
         String[] cases = {
+                ".00/w/w",
                 "group-thousands/foo",
                 "precision-integer//@## group-off",
                 "precision-integer//@##  group-off",