ICU-13836 Represent suppressed exponent for better plural support
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java
index 7d30c58..9d6fc34 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java
@@ -123,6 +123,26 @@
     public int getMagnitude() throws ArithmeticException;
 
     /**
+     * @return The value of the (suppressed) exponent after the number has been
+     * put into a notation with exponents (ex: compact, scientific).  Ex: given
+     * the number 1000 as "1K" / "1E3", the return value will be 3 (positive).
+     */
+    public int getExponent();
+
+    /**
+     * Adjusts the value for the (suppressed) exponent stored when using
+     * notation with exponents (ex: compact, scientific).
+     *
+     * <p>Adjusting the exponent is decoupled from {@link #adjustMagnitude} in
+     * order to allow flexibility for {@link StandardPlural} to be selected in
+     * formatting (ex: for compact notation) either with or without the exponent
+     * applied in the value of the number.
+     * @param delta
+     *             The value to adjust the exponent by.
+     */
+    public void adjustExponent(int delta);
+
+    /**
      * @return Whether the value represented by this {@link DecimalQuantity} is
      * zero, infinity, or NaN.
      */
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java
index 09c8893..32635e5 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java
@@ -86,6 +86,12 @@
     protected int lReqPos = 0;
     protected int rReqPos = 0;
 
+    /**
+     * The value of the (suppressed) exponent after the number has been put into
+     * a notation with exponents (ex: compact, scientific).
+     */
+    protected int exponent = 0;
+
     @Override
     public void copyFrom(DecimalQuantity _other) {
         copyBcdFrom(_other);
@@ -98,13 +104,14 @@
         origDouble = other.origDouble;
         origDelta = other.origDelta;
         isApproximate = other.isApproximate;
+        exponent = other.exponent;
     }
 
     public DecimalQuantity_AbstractBCD clear() {
         lReqPos = 0;
         rReqPos = 0;
         flags = 0;
-        setBcdToZero(); // sets scale, precision, hasDouble, origDouble, origDelta, and BCD data
+        setBcdToZero(); // sets scale, precision, hasDouble, origDouble, origDelta, exponent, and BCD data
         return this;
     }
 
@@ -217,6 +224,16 @@
     }
 
     @Override
+    public int getExponent() {
+        return exponent;
+    }
+
+    @Override
+    public void adjustExponent(int delta) {
+        exponent = exponent + delta;
+    }
+
+    @Override
     public StandardPlural getStandardPlural(PluralRules rules) {
         if (rules == null) {
             // Fail gracefully if the user didn't provide a PluralRules
@@ -246,6 +263,8 @@
             return fractionCount();
         case w:
             return fractionCountWithoutTrailingZeros();
+        case e:
+            return getExponent();
         default:
             return Math.abs(toDouble());
         }
@@ -291,11 +310,11 @@
     }
 
     private int fractionCount() {
-        return -getLowerDisplayMagnitude();
+        return Math.max(0, -getLowerDisplayMagnitude() - exponent);
     }
 
     private int fractionCountWithoutTrailingZeros() {
-        return Math.max(-scale, 0);
+        return Math.max(-scale - exponent, 0);
     }
 
     @Override
@@ -577,7 +596,9 @@
 
     /**
      * Returns a long approximating the internal BCD. A long can only represent the integral part of the
-     * number.
+     * number.  Note: this method incorporates the value of {@code exponent}
+     * (for cases such as compact notation) to return the proper long value
+     * represented by the result.
      *
      * @param truncateIfOverflow if false and the number does NOT fit, fails with an assertion error.
      * @return A 64-bit integer representation of the internal BCD.
@@ -588,12 +609,12 @@
         // Fallback behavior upon truncateIfOverflow is to truncate at 17 digits.
         assert(truncateIfOverflow || fitsInLong());
         long result = 0L;
-        int upperMagnitude = scale + precision - 1;
+        int upperMagnitude = exponent + scale + precision - 1;
         if (truncateIfOverflow) {
             upperMagnitude = Math.min(upperMagnitude, 17);
         }
         for (int magnitude = upperMagnitude; magnitude >= 0; magnitude--) {
-            result = result * 10 + getDigitPos(magnitude - scale);
+            result = result * 10 + getDigitPos(magnitude - scale - exponent);
         }
         if (isNegative()) {
             result = -result;
@@ -605,10 +626,13 @@
      * This returns a long representing the fraction digits of the number, as required by PluralRules.
      * For example, if we represent the number "1.20" (including optional and required digits), then this
      * function returns "20" if includeTrailingZeros is true or "2" if false.
+     * Note: this method incorporates the value of {@code exponent}
+     * (for cases such as compact notation) to return the proper long value
+     * represented by the result.
      */
     public long toFractionLong(boolean includeTrailingZeros) {
         long result = 0L;
-        int magnitude = -1;
+        int magnitude = -1 - exponent;
         int lowerMagnitude = scale;
         if (includeTrailingZeros) {
             lowerMagnitude = Math.min(lowerMagnitude, rReqPos);
@@ -638,7 +662,7 @@
         if (isZeroish()) {
             return true;
         }
-        if (scale < 0) {
+        if (exponent + scale < 0) {
             return false;
         }
         int magnitude = getMagnitude();
@@ -991,22 +1015,40 @@
 
     @Override
     public String toPlainString() {
-        // NOTE: This logic is duplicated between here and DecimalQuantity_SimpleStorage.
         StringBuilder sb = new StringBuilder();
-        if (isNegative()) {
-            sb.append('-');
-        }
-        if (precision == 0 || getMagnitude() < 0) {
-            sb.append('0');
-        }
-        for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) {
-            sb.append((char) ('0' + getDigit(m)));
-            if (m == 0)
-                sb.append('.');
-        }
+        toPlainString(sb);
         return sb.toString();
     }
 
+    public void toPlainString(StringBuilder result) {
+        assert(!isApproximate);
+        if (isNegative()) {
+            result.append('-');
+        }
+        if (precision == 0) {
+            result.append('0');
+            return;
+        }
+
+        int upper = scale + precision + exponent - 1;
+        int lower = scale + exponent;
+        if (upper < lReqPos - 1) {
+            upper = lReqPos - 1;
+        }
+        if (lower > rReqPos) {
+            lower = rReqPos;
+        }
+
+        int p = upper;
+        for (; p >= 0; p--) {
+            result.append((char) ('0' + getDigitPos(p - scale - exponent)));
+        }
+        result.append('.');
+        for(; p >= lower; p--) {
+            result.append((char) ('0' + getDigitPos(p - scale - exponent)));
+        }
+    }
+
     public String toScientificString() {
         StringBuilder sb = new StringBuilder();
         toScientificString(sb);
@@ -1035,7 +1077,7 @@
             }
         }
         result.append('E');
-        int _scale = upperPos + scale;
+        int _scale = upperPos + scale + exponent;
         if (_scale == Integer.MIN_VALUE) {
             result.append("-2147483648");
             return;
@@ -1146,7 +1188,7 @@
 
     /**
      * Sets the internal representation to zero. Clears any values stored in scale, precision, hasDouble,
-     * origDouble, origDelta, and BCD data.
+     * origDouble, origDelta, exponent, and BCD data.
      */
     protected abstract void setBcdToZero();
 
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java
index 47f0d97..05612af 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java
@@ -180,6 +180,7 @@
         isApproximate = false;
         origDouble = 0;
         origDelta = 0;
+        exponent = 0;
     }
 
     @Override
@@ -254,11 +255,11 @@
             }
             BigDecimal result = BigDecimal.valueOf(tempLong);
             // Test that the new scale fits inside the BigDecimal
-            long newScale = result.scale() + scale;
+            long newScale = result.scale() + scale + exponent;
             if (newScale <= Integer.MIN_VALUE) {
                 result = BigDecimal.ZERO;
             } else {
-                result = result.scaleByPowerOfTen(scale);
+                result = result.scaleByPowerOfTen(scale + exponent);
             }
             if (isNegative()) {
                 result = result.negate();
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java b/icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java
index a4dad2d..f2bffe7 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java
@@ -128,11 +128,12 @@
 
             // Treat zero, NaN, and infinity as if they had magnitude 0
             int magnitude;
+            int multiplier = 0;
             if (quantity.isZeroish()) {
                 magnitude = 0;
                 micros.rounder.apply(quantity);
             } else {
-                int multiplier = micros.rounder.chooseMultiplierAndApply(quantity, data);
+                multiplier = micros.rounder.chooseMultiplierAndApply(quantity, data);
                 magnitude = quantity.isZeroish() ? 0 : quantity.getMagnitude();
                 magnitude -= multiplier;
             }
@@ -156,6 +157,11 @@
                 micros.modMiddle = unsafePatternModifier;
             }
 
+            // Change the exponent only after we select appropriate plural form
+            // for formatting purposes so that we preserve expected formatted
+            // string behavior.
+            quantity.adjustExponent(-1 * multiplier);
+
             // We already performed rounding. Do not perform it again.
             micros.rounder = null;
 
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java b/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java
index e751688..cf1ac78 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java
@@ -195,6 +195,11 @@
                 micros.modInner = this;
             }
 
+            // Change the exponent only after we select appropriate plural form
+            // for formatting purposes so that we preserve expected formatted
+            // string behavior.
+            quantity.adjustExponent(exponent);
+
             // We already performed rounding. Do not perform it again.
             micros.rounder = null;
 
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java b/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java
index a9bfa46..ec6944f 100644
--- a/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java
+++ b/icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java
@@ -465,6 +465,16 @@
         w,
 
         /**
+         * Suppressed exponent for compact notation (exponent needed in
+         * scientific notation with compact notation to approximate i).
+         *
+         * @internal
+         * @deprecated This API is ICU internal only.
+         */
+        @Deprecated
+        e,
+
+        /**
          * THIS OPERAND IS DEPRECATED AND HAS BEEN REMOVED FROM THE SPEC.
          *
          * <p>Returns the integer value, but will fail if the number has fraction digits.
@@ -539,6 +549,8 @@
 
         private final int baseFactor;
 
+        final int suppressedExponent;
+
         /**
          * @internal CLDR
          * @deprecated This API is ICU internal only.
@@ -620,6 +632,15 @@
             return baseFactor;
         }
 
+        /**
+         * @internal
+         * @deprecated This API is ICU internal only.
+         */
+        @Deprecated
+        public int getSuppressedExponent() {
+            return suppressedExponent;
+        }
+
         static final long MAX = (long)1E18;
 
         /**
@@ -640,6 +661,7 @@
                     ? MAX
                             : (long)n;
             hasIntegerValue = source == integerValue;
+            suppressedExponent = 0;
             // check values. TODO make into unit test.
             //
             //            long visiblePower = (int) Math.pow(10, v);
@@ -797,6 +819,7 @@
             case t: return decimalDigitsWithoutTrailingZeros;
             case v: return visibleDecimalDigitCount;
             case w: return visibleDecimalDigitCountWithoutTrailingZeros;
+            case e: return suppressedExponent;
             default: return source;
             }
         }
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_64BitBCD.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_64BitBCD.java
index ea84162..22f6e20 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_64BitBCD.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_64BitBCD.java
@@ -95,6 +95,7 @@
     isApproximate = false;
     origDouble = 0;
     origDelta = 0;
+    exponent = 0;
   }
 
   @Override
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_ByteArrayBCD.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_ByteArrayBCD.java
index de87080..92b40b0 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_ByteArrayBCD.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_ByteArrayBCD.java
@@ -112,6 +112,7 @@
     isApproximate = false;
     origDouble = 0;
     origDelta = 0;
+    exponent = 0;
   }
 
   @Override
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java
index cb1bc57..24d0434 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java
@@ -95,6 +95,8 @@
     1000000000000000000L
   };
 
+  private int origPrimaryScale;
+
   @Override
   public int maxRepresentableDigits() {
     return Integer.MAX_VALUE;
@@ -110,6 +112,7 @@
     primaryScale = 0;
     primaryPrecision = computePrecision(primary);
     fallback = null;
+    origPrimaryScale = primaryScale;
   }
 
   /**
@@ -189,6 +192,8 @@
       primary = -1;
       fallback = new BigDecimal(temp);
     }
+
+    origPrimaryScale = primaryScale;
   }
 
   static final double LOG_2_OF_TEN = 3.32192809489;
@@ -279,6 +284,7 @@
     primaryPrecision = _other.primaryPrecision;
     fallback = _other.fallback;
     flags = _other.flags;
+    origPrimaryScale = _other.origPrimaryScale;
   }
 
   @Override
@@ -916,4 +922,14 @@
           .setFractionDigits((int) getPluralOperand(Operand.v), (long) getPluralOperand(Operand.f));
     }
   }
+
+  @Override
+  public int getExponent() {
+    return origPrimaryScale;
+  }
+
+  @Override
+  public void adjustExponent(int delta) {
+      origPrimaryScale = origPrimaryScale + delta;
+  }
 }
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java
index 2379470..b67000e 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/PluralRulesTest.java
@@ -42,6 +42,7 @@
 import com.ibm.icu.impl.Relation;
 import com.ibm.icu.impl.Utility;
 import com.ibm.icu.number.FormattedNumber;
+import com.ibm.icu.number.LocalizedNumberFormatter;
 import com.ibm.icu.number.NumberFormatter;
 import com.ibm.icu.number.Precision;
 import com.ibm.icu.number.UnlocalizedNumberFormatter;
@@ -929,6 +930,66 @@
         }
     }
 
+
+
+    @Test
+    public void testCompactDecimalPluralKeyword() {
+        PluralRules rules = PluralRules.createRules("one: i = 0,1 @integer 0, 1 @decimal 0.0~1.5;  many: e = 0 and i % 1000000 = 0 and v = 0 or " +
+                "e != 0 .. 5;  other:  @integer 2~17, 100, 1000, 10000, 100000, 1000000, @decimal 2.0~3.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …");
+        ULocale locale = new ULocale("fr-FR");
+
+        Object[][] casesData = {
+                // unlocalized formatter skeleton, input, string output, plural rule keyword
+                {"",             0, "0", "one"},
+                {"compact-long", 0, "0", "one"},
+
+                {"",             1, "1", "one"},
+                {"compact-long", 1, "1", "one"},
+
+                {"",             2, "2", "other"},
+                {"compact-long", 2, "2", "other"},
+
+                {"",             1000000, "1 000 000", "many"},
+                {"compact-long", 1000000, "1 million", "many"},
+
+                {"",             1000001, "1 000 001", "other"},
+                {"compact-long", 1000001, "1 million", "many"},
+
+                {"",             120000, "1 200 000", "other"},
+                {"compact-long", 1200000, "1,2 millions", "many"},
+
+                {"",             1200001, "1 200 001", "other"},
+                {"compact-long", 1200001, "1,2 millions", "many"},
+
+                {"",             2000000, "2 000 000", "many"},
+                {"compact-long", 2000000, "2 millions", "many"},
+        };
+
+        for (Object[] caseDatum : casesData) {
+            String skeleton = (String) caseDatum[0];
+            int input = (int) caseDatum[1];
+            String expectedString = (String) caseDatum[2];
+            String expectPluralRuleKeyword = (String) caseDatum[3];
+
+            String actualPluralRuleKeyword =
+                    getPluralKeyword(rules, locale, input, skeleton);
+
+            assertEquals(
+                    String.format("PluralRules select %s: %d", skeleton, input),
+                    expectPluralRuleKeyword,
+                    actualPluralRuleKeyword);
+        }
+    }
+
+    private String getPluralKeyword(PluralRules rules, ULocale locale, double number, String skeleton) {
+        LocalizedNumberFormatter formatter =
+                NumberFormatter.forSkeleton(skeleton)
+                    .locale(locale);
+        FormattedNumber fn = formatter.format(number);
+        String pluralKeyword = rules.select(fn);
+        return pluralKeyword;
+    }
+
     enum StandardPluralCategories {
         zero, one, two, few, many, other;
         /**
diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java
index 471682a..82a0b4f 100644
--- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java
+++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java
@@ -24,10 +24,15 @@
 import com.ibm.icu.impl.number.DecimalQuantity;
 import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
 import com.ibm.icu.impl.number.RoundingUtils;
+import com.ibm.icu.number.FormattedNumber;
 import com.ibm.icu.number.LocalizedNumberFormatter;
+import com.ibm.icu.number.Notation;
 import com.ibm.icu.number.NumberFormatter;
+import com.ibm.icu.number.Precision;
+import com.ibm.icu.number.Scale;
 import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
 import com.ibm.icu.text.DecimalFormatSymbols;
+import com.ibm.icu.text.PluralRules.Operand;
 import com.ibm.icu.util.ULocale;
 
 @RunWith(JUnit4.class)
@@ -603,8 +608,272 @@
         }
     }
 
+    @Test
+    public void testCompactDecimalSuppressedExponent() {
+        ULocale locale = new ULocale("fr-FR");
+
+        Object[][] casesData = {
+                // unlocalized formatter skeleton, input, string output, long output, double output, BigDecimal output, plain string, suppressed exponent
+                {"",              123456789, "123 456 789",  123456789L, 123456789.0, new BigDecimal("123456789"), "123456789.", 0},
+                {"compact-long",  123456789, "123 millions", 123000000L, 123000000.0, new BigDecimal("123000000"), "123000000.", 6},
+                {"compact-short", 123456789, "123 M",        123000000L, 123000000.0, new BigDecimal("123000000"), "123000000.", 6},
+                {"scientific",    123456789, "1,234568E8",   123456800L, 123456800.0, new BigDecimal("123456800"), "123456800.", 8},
+
+                {"",              1234567, "1 234 567",   1234567L, 1234567.0, new BigDecimal("1234567"), "1234567.", 0},
+                {"compact-long",  1234567, "1,2 million", 1200000L, 1200000.0, new BigDecimal("1200000"), "1200000.", 6},
+                {"compact-short", 1234567, "1,2 M",       1200000L, 1200000.0, new BigDecimal("1200000"), "1200000.", 6},
+                {"scientific",    1234567, "1,234567E6",  1234567L, 1234567.0, new BigDecimal("1234567"), "1234567.", 6},
+
+                {"",              123456, "123 456",   123456L, 123456.0, new BigDecimal("123456"), "123456.", 0},
+                {"compact-long",  123456, "123 mille", 123000L, 123000.0, new BigDecimal("123000"), "123000.", 3},
+                {"compact-short", 123456, "123 k",     123000L, 123000.0, new BigDecimal("123000"), "123000.", 3},
+                {"scientific",    123456, "1,23456E5", 123456L, 123456.0, new BigDecimal("123456"), "123456.", 5},
+
+                {"",              123, "123",    123L, 123.0, new BigDecimal("123"), "123.", 0},
+                {"compact-long",  123, "123",    123L, 123.0, new BigDecimal("123"), "123.", 0},
+                {"compact-short", 123, "123",    123L, 123.0, new BigDecimal("123"), "123.", 0},
+                {"scientific",    123, "1,23E2", 123L, 123.0, new BigDecimal("123"), "123.", 2},
+
+                {"",              1.2, "1,2",   1L, 1.2, new BigDecimal("1.2"), "1.2", 0},
+                {"compact-long",  1.2, "1,2",   1L, 1.2, new BigDecimal("1.2"), "1.2", 0},
+                {"compact-short", 1.2, "1,2",   1L, 1.2, new BigDecimal("1.2"), "1.2", 0},
+                {"scientific",    1.2, "1,2E0", 1L, 1.2, new BigDecimal("1.2"), "1.2", 0},
+
+                {"",              0.12, "0,12",   0L, 0.12, new BigDecimal("0.12"), "0.12", 0},
+                {"compact-long",  0.12, "0,12",   0L, 0.12, new BigDecimal("0.12"), "0.12", 0},
+                {"compact-short", 0.12, "0,12",   0L, 0.12, new BigDecimal("0.12"), "0.12", 0},
+                {"scientific",    0.12, "1,2E-1", 0L, 0.12, new BigDecimal("0.12"), "0.12", -1},
+
+                {"",              0.012, "0,012",   0L, 0.012, new BigDecimal("0.012"), "0.012", 0},
+                {"compact-long",  0.012, "0,012",   0L, 0.012, new BigDecimal("0.012"), "0.012", 0},
+                {"compact-short", 0.012, "0,012",   0L, 0.012, new BigDecimal("0.012"), "0.012", 0},
+                {"scientific",    0.012, "1,2E-2",  0L, 0.012, new BigDecimal("0.012"), "0.012", -2},
+
+                {"",              999.9, "999,9",     999L,  999.9,  new BigDecimal("999.9"), "999.9", 0},
+                {"compact-long",  999.9, "1 millier", 1000L, 1000.0, new BigDecimal("1000"),  "1000.", 3},
+                {"compact-short", 999.9, "1 k",       1000L, 1000.0, new BigDecimal("1000"),  "1000.", 3},
+                {"scientific",    999.9, "9,999E2",   999L,  999.9,  new BigDecimal("999.9"), "999.9", 2},
+
+                {"",              1000.0, "1 000",     1000L, 1000.0, new BigDecimal("1000"), "1000.", 0},
+                {"compact-long",  1000.0, "1 millier", 1000L, 1000.0, new BigDecimal("1000"), "1000.", 3},
+                {"compact-short", 1000.0, "1 k",       1000L, 1000.0, new BigDecimal("1000"), "1000.", 3},
+                {"scientific",    1000.0, "1E3",       1000L, 1000.0, new BigDecimal("1000"), "1000.", 3},
+        };
+
+        for (Object[] caseDatum : casesData) {
+            // test the helper methods used to compute plural operand values
+
+            String skeleton = (String) caseDatum[0];
+            LocalizedNumberFormatter formatter =
+                    NumberFormatter.forSkeleton(skeleton)
+                        .locale(locale);
+            double input = ((Number) caseDatum[1]).doubleValue();
+            String expectedString = (String) caseDatum[2];
+            long expectedLong = (long) caseDatum[3];
+            double expectedDouble = (double) caseDatum[4];
+            BigDecimal expectedBigDecimal = (BigDecimal) caseDatum[5];
+            String expectedPlainString = (String) caseDatum[6];
+            int expectedSuppressedExponent = (int) caseDatum[7];
+
+            FormattedNumber fn = formatter.format(input);
+            DecimalQuantity_DualStorageBCD dq = (DecimalQuantity_DualStorageBCD)
+                    fn.getFixedDecimal();
+            String actualString = fn.toString();
+            long actualLong = dq.toLong(true);
+            double actualDouble = dq.toDouble();
+            BigDecimal actualBigDecimal = dq.toBigDecimal();
+            String actualPlainString = dq.toPlainString();
+            int actualSuppressedExponent = dq.getExponent();
+
+            assertEquals(
+                    String.format("formatted number %s toString: %f", skeleton, input),
+                    expectedString,
+                    actualString);
+            assertEquals(
+                    String.format("compact decimal %s toLong: %f", skeleton, input),
+                    expectedLong,
+                    actualLong);
+            assertDoubleEquals(
+                    String.format("compact decimal %s toDouble: %f", skeleton, input),
+                    expectedDouble,
+                    actualDouble);
+            assertBigDecimalEquals(
+                    String.format("compact decimal %s toBigDecimal: %f", skeleton, input),
+                    expectedBigDecimal,
+                    actualBigDecimal);
+            assertEquals(
+                    String.format("formatted number %s toPlainString: %f", skeleton, input),
+                    expectedPlainString,
+                    actualPlainString);
+            assertEquals(
+                    String.format("compact decimal %s suppressed exponent: %f", skeleton, input),
+                    expectedSuppressedExponent,
+                    actualSuppressedExponent);
+
+            // test the actual computed values of the plural operands
+
+            double expectedNOperand = expectedDouble;
+            double expectedIOperand = expectedLong;
+            double expectedEOperand = expectedSuppressedExponent;
+            double actualNOperand = dq.getPluralOperand(Operand.n);
+            double actualIOperand = dq.getPluralOperand(Operand.i);
+            double actualEOperand = dq.getPluralOperand(Operand.e);
+
+            assertEquals(
+                    String.format("formatted number %s toString: %s", skeleton, input),
+                    expectedString,
+                    actualString);
+            assertDoubleEquals(
+                    String.format("compact decimal %s n operand: %f", skeleton, input),
+                    expectedNOperand,
+                    actualNOperand);
+            assertDoubleEquals(
+                    String.format("compact decimal %s i operand: %f", skeleton, input),
+                    expectedIOperand,
+                    actualIOperand);
+            assertDoubleEquals(
+                    String.format("compact decimal %s e operand: %f", skeleton, input),
+                    expectedEOperand,
+                    actualEOperand);
+        }
+    }
+
+
+    @Test
+    public void testCompactNotationFractionPluralOperands() {
+        ULocale locale = new ULocale("fr-FR");
+        LocalizedNumberFormatter formatter =
+                NumberFormatter.withLocale(locale)
+                    .notation(Notation.compactLong())
+                    .precision(Precision.fixedFraction(5))
+                    .scale(Scale.powerOfTen(-1));
+        double formatterInput = 12345;
+        double inputVal = 1234.5;
+        FormattedNumber fn = formatter.format(formatterInput);
+        DecimalQuantity_DualStorageBCD dq = (DecimalQuantity_DualStorageBCD)
+                fn.getFixedDecimal();
+
+        double expectedNOperand = 1234.5;
+        double expectedIOperand = 1234;
+        double expectedFOperand = 50;
+        double expectedTOperand = 5;
+        double expectedVOperand = 2;
+        double expectedWOperand = 1;
+        double expectedEOperand = 3;
+        String expectedString = "1,23450 millier";
+        double actualNOperand = dq.getPluralOperand(Operand.n);
+        double actualIOperand = dq.getPluralOperand(Operand.i);
+        double actualFOperand = dq.getPluralOperand(Operand.f);
+        double actualTOperand = dq.getPluralOperand(Operand.t);
+        double actualVOperand = dq.getPluralOperand(Operand.v);
+        double actualWOperand = dq.getPluralOperand(Operand.w);
+        double actualEOperand = dq.getPluralOperand(Operand.e);
+        String actualString = fn.toString();
+
+        assertDoubleEquals(
+                String.format("compact decimal fraction n operand: %f", inputVal),
+                expectedNOperand,
+                actualNOperand);
+        assertDoubleEquals(
+                String.format("compact decimal fraction i operand: %f", inputVal),
+                expectedIOperand,
+                actualIOperand);
+        assertDoubleEquals(
+                String.format("compact decimal fraction f operand: %f", inputVal),
+                expectedFOperand,
+                actualFOperand);
+        assertDoubleEquals(
+                String.format("compact decimal fraction t operand: %f", inputVal),
+                expectedTOperand,
+                actualTOperand);
+        assertDoubleEquals(
+                String.format("compact decimal fraction v operand: %f", inputVal),
+                expectedVOperand,
+                actualVOperand);
+        assertDoubleEquals(
+                String.format("compact decimal fraction w operand: %f", inputVal),
+                expectedWOperand,
+                actualWOperand);
+        assertDoubleEquals(
+                String.format("compact decimal fraction e operand: %f", inputVal),
+                expectedEOperand,
+                actualEOperand);
+        assertEquals(
+                String.format("compact decimal fraction toString: %f", inputVal),
+                expectedString,
+                actualString);
+    }
+
+    @Test
+    public void testSuppressedExponentUnchangedByInitialScaling() {
+        ULocale locale = new ULocale("fr-FR");
+        LocalizedNumberFormatter withLocale = NumberFormatter.withLocale(locale);
+        LocalizedNumberFormatter compactLong =
+                withLocale.notation(Notation.compactLong());
+        LocalizedNumberFormatter compactScaled =
+                compactLong.scale(Scale.powerOfTen(3));
+
+        Object[][] casesData = {
+                // input, compact long string output,
+                // compact n operand, compact i operand, compact e operand
+                {123456789, "123 millions", 123000000.0, 123000000.0, 6.0},
+                {1234567,   "1,2 million",  1200000.0,   1200000.0,   6.0},
+                {123456,    "123 mille",    123000.0,    123000.0,    3.0},
+                {123,       "123",          123.0,       123.0,       0.0},
+        };
+
+        for (Object[] caseDatum : casesData) {
+            int input = (int) caseDatum[0];
+            String expectedString = (String) caseDatum[1];
+            double expectedNOperand = (double) caseDatum[2];
+            double expectedIOperand = (double) caseDatum[3];
+            double expectedEOperand = (double) caseDatum[4];
+
+            FormattedNumber fnCompactScaled = compactScaled.format(input);
+            DecimalQuantity_DualStorageBCD dqCompactScaled =
+                    (DecimalQuantity_DualStorageBCD) fnCompactScaled.getFixedDecimal();
+            double compactScaledEOperand = dqCompactScaled.getPluralOperand(Operand.e);
+
+            FormattedNumber fnCompact = compactLong.format(input);
+            DecimalQuantity_DualStorageBCD dqCompact =
+                    (DecimalQuantity_DualStorageBCD) fnCompact.getFixedDecimal();
+            String actualString = fnCompact.toString();
+            double compactNOperand = dqCompact.getPluralOperand(Operand.n);
+            double compactIOperand = dqCompact.getPluralOperand(Operand.i);
+            double compactEOperand = dqCompact.getPluralOperand(Operand.e);
+            assertEquals(
+                    String.format("formatted number compactLong toString: %s", input),
+                    expectedString,
+                    actualString);
+            assertDoubleEquals(
+                    String.format("compact decimal %d, n operand vs. expected", input),
+                    expectedNOperand,
+                    compactNOperand);
+            assertDoubleEquals(
+                    String.format("compact decimal %d, i operand vs. expected", input),
+                    expectedIOperand,
+                    compactIOperand);
+            assertDoubleEquals(
+                    String.format("compact decimal %d, e operand vs. expected", input),
+                    expectedEOperand,
+                    compactEOperand);
+
+            // By scaling by 10^3 in a locale that has words / compact notation
+            // based on powers of 10^3, we guarantee that the suppressed
+            // exponent will differ by 3.
+            assertDoubleEquals(
+                    String.format("decimal %d, e operand for compact vs. compact scaled", input),
+                    compactEOperand + 3,
+                    compactScaledEOperand);
+        }
+    }
+
+    static boolean doubleEquals(double d1, double d2) {
+        return (Math.abs(d1 - d2) < 1e-6) || (Math.abs((d1 - d2) / d1) < 1e-6);
+    }
+
     static void assertDoubleEquals(String message, double d1, double d2) {
-        boolean equal = (Math.abs(d1 - d2) < 1e-6) || (Math.abs((d1 - d2) / d1) < 1e-6);
+        boolean equal = doubleEquals(d1, d2);
         handleAssert(equal, message, d1, d2, null, false);
     }
 
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 4ba49dc..47000e6 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
@@ -350,6 +350,15 @@
                 "1 millón");
 
         assertFormatSingle(
+                "Compact Plural One with rounding",
+                "compact-long precision-integer",
+                "KK precision-integer",
+                NumberFormatter.with().notation(Notation.compactLong()).precision(Precision.integer()),
+                ULocale.forLanguageTag("es"),
+                1222222,
+                "1 millón");
+
+        assertFormatSingle(
                 "Compact Plural Other",
                 "compact-long",
                 "KK",