]> granicus.if.org Git - icu/commitdiff
ICU-13060 Assorted test cases, @internal tags, and fixes for DecimalFormat.
authorShane Carr <shane@unicode.org>
Wed, 22 Mar 2017 23:30:42 +0000 (23:30 +0000)
committerShane Carr <shane@unicode.org>
Wed, 22 Mar 2017 23:30:42 +0000 (23:30 +0000)
X-SVN-Rev: 39908

15 files changed:
icu4j/main/classes/core/src/com/ibm/icu/impl/number/Parse.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternString.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/Properties.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/Rounder.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/formatters/CompactDecimalFormat.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/formatters/CurrencyFormat.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/formatters/PositiveDecimalFormat.java
icu4j/main/classes/core/src/com/ibm/icu/text/CompactDecimalFormat.java
icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java
icu4j/main/classes/core/src/com/ibm/icu/text/NumberFormat.java
icu4j/main/classes/core/src/com/ibm/icu/text/PluralRules.java
icu4j/main/classes/core/src/com/ibm/icu/util/Currency.java
icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/CompactDecimalFormatTest.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java

index 5b45b47863b5d5e6f32d0a0d84399140d4c9498b..7fd26ca9e4c94859675eaff8c6408084465a1a07 100644 (file)
@@ -13,13 +13,13 @@ import java.util.concurrent.ConcurrentHashMap;
 
 import com.ibm.icu.impl.StandardPlural;
 import com.ibm.icu.impl.TextTrieMap;
-import com.ibm.icu.impl.number.Parse.ParseMode;
 import com.ibm.icu.impl.number.formatters.BigDecimalMultiplier;
 import com.ibm.icu.impl.number.formatters.CurrencyFormat;
 import com.ibm.icu.impl.number.formatters.MagnitudeMultiplier;
 import com.ibm.icu.impl.number.formatters.PaddingFormat;
 import com.ibm.icu.impl.number.formatters.PositiveDecimalFormat;
 import com.ibm.icu.impl.number.formatters.PositiveNegativeAffixFormat;
+import com.ibm.icu.impl.number.formatters.ScientificFormat;
 import com.ibm.icu.lang.UCharacter;
 import com.ibm.icu.text.CurrencyPluralInfo;
 import com.ibm.icu.text.DecimalFormatSymbols;
@@ -102,7 +102,8 @@ public class Parse {
           CurrencyFormat.ICurrencyProperties,
           BigDecimalMultiplier.IProperties,
           MagnitudeMultiplier.IProperties,
-          PositiveDecimalFormat.IProperties {
+          PositiveDecimalFormat.IProperties,
+          ScientificFormat.IProperties {
 
     boolean DEFAULT_PARSE_INTEGER_ONLY = false;
 
@@ -199,7 +200,7 @@ public class Parse {
   }
 
   /**
-   * @see #parse(String, ParsePosition, ParseMode, boolean, boolean, IProperties,
+   * @see Parse#parse(String, ParsePosition, ParseMode, boolean, boolean, IProperties,
    *     DecimalFormatSymbols)
    */
   private static enum StateName {
@@ -303,6 +304,7 @@ public class Parse {
     boolean sawPrefix;
     boolean sawSuffix;
     boolean sawDecimalPoint;
+    boolean sawExponentDigit;
 
     // Data for intermediate parsing steps:
     StateName returnTo1;
@@ -310,6 +312,7 @@ public class Parse {
     // For string literals:
     CharSequence currentString;
     int currentOffset;
+    boolean currentTrailing;
     // For affix patterns:
     CharSequence currentAffixPattern;
     long currentStepwiseParserTag;
@@ -349,12 +352,14 @@ public class Parse {
       sawPrefix = false;
       sawSuffix = false;
       sawDecimalPoint = false;
+      sawExponentDigit = false;
 
       // Data for intermediate parsing steps:
       returnTo1 = null;
       returnTo2 = null;
       currentString = null;
       currentOffset = 0;
+      currentTrailing = false;
       currentAffixPattern = null;
       currentStepwiseParserTag = 0L;
       currentCurrencyTrieState = null;
@@ -404,12 +409,14 @@ public class Parse {
       sawPrefix = other.sawPrefix;
       sawSuffix = other.sawSuffix;
       sawDecimalPoint = other.sawDecimalPoint;
+      sawExponentDigit = other.sawExponentDigit;
 
       // Data for intermediate parsing steps:
       returnTo1 = other.returnTo1;
       returnTo2 = other.returnTo2;
       currentString = other.currentString;
       currentOffset = other.currentOffset;
+      currentTrailing = other.currentTrailing;
       currentAffixPattern = other.currentAffixPattern;
       currentStepwiseParserTag = other.currentStepwiseParserTag;
       currentCurrencyTrieState = other.currentCurrencyTrieState;
@@ -427,6 +434,7 @@ public class Parse {
      */
     void appendDigit(byte digit, DigitType type) {
       if (type == DigitType.EXPONENT) {
+        sawExponentDigit = true;
         int newExponent = exponent * 10 + digit;
         if (newExponent < exponent) {
           // overflow
@@ -606,7 +614,11 @@ public class Parse {
     int groupingCp2;
     SeparatorType decimalType1;
     SeparatorType decimalType2;
+    // TODO(sffc): Remove this field if it is not necessary.
+    @SuppressWarnings("unused")
     SeparatorType groupingType1;
+    // TODO(sffc): Remove this field if it is not necessary.
+    @SuppressWarnings("unused")
     SeparatorType groupingType2;
     TextTrieMap<Byte> digitTrie;
     Set<AffixHolder> affixHolders = new HashSet<AffixHolder>();
@@ -1299,6 +1311,12 @@ public class Parse {
             continue;
           }
 
+          // Check for scientific notation.
+          if (properties.getMinimumExponentDigits() > 0 && !item.sawExponentDigit) {
+            if (DEBUGGING) System.out.println("-> reject due to lack of exponent");
+            continue;
+          }
+
           // Check that grouping sizes are valid.
           int grouping1 = properties.getGroupingSize();
           int grouping2 = properties.getSecondaryGroupingSize();
@@ -1621,7 +1639,7 @@ public class Parse {
 
   private static void acceptNan(int cp, StateName nextName, ParserState state, StateItem item) {
     CharSequence nan = state.symbols.getNaN();
-    long added = acceptString(cp, nextName, null, state, item, nan, 0);
+    long added = acceptString(cp, nextName, null, state, item, nan, 0, false);
 
     // Set state in the items that were added by the function call
     for (int i = Long.numberOfTrailingZeros(added); (1L << i) <= added; i++) {
@@ -1634,7 +1652,7 @@ public class Parse {
   private static void acceptInfinity(
       int cp, StateName nextName, ParserState state, StateItem item) {
     CharSequence inf = state.symbols.getInfinity();
-    long added = acceptString(cp, nextName, null, state, item, inf, 0);
+    long added = acceptString(cp, nextName, null, state, item, inf, 0, false);
 
     // Set state in the items that were added by the function call
     for (int i = Long.numberOfTrailingZeros(added); (1L << i) <= added; i++) {
@@ -1647,7 +1665,7 @@ public class Parse {
   private static void acceptExponentSeparator(
       int cp, StateName nextName, ParserState state, StateItem item) {
     CharSequence exp = state.symbols.getExponentSeparator();
-    acceptString(cp, nextName, null, state, item, exp, 0);
+    acceptString(cp, nextName, null, state, item, exp, 0, true);
   }
 
   private static void acceptPrefix(int cp, StateName nextName, ParserState state, StateItem item) {
@@ -1676,7 +1694,7 @@ public class Parse {
     if (holder == null) return;
     String str = prefix ? holder.p : holder.s;
     if (holder.strings) {
-      long added = acceptString(cp, nextName, null, state, item, str, 0);
+      long added = acceptString(cp, nextName, null, state, item, str, 0, false);
       // At most one item can be added upon consuming a string.
       if (added != 0) {
         int i = state.lastInsertedIndex();
@@ -1705,7 +1723,14 @@ public class Parse {
 
   private static void acceptStringOffset(int cp, ParserState state, StateItem item) {
     acceptString(
-        cp, item.returnTo1, item.returnTo2, state, item, item.currentString, item.currentOffset);
+        cp,
+        item.returnTo1,
+        item.returnTo2,
+        state,
+        item,
+        item.currentString,
+        item.currentOffset,
+        item.currentTrailing);
   }
 
   /**
@@ -1719,6 +1744,8 @@ public class Parse {
    * @param returnTo1 The state to return to after reaching the end of the string.
    * @param returnTo2 The state to save in <code>returnTo1</code> after reaching the end of the
    *     string. Set to null if returning to the main state loop.
+   * @param trailing true if this string should be ignored for the purposes of recording trailing
+   *     code points; false if it trailing count should be reset after reading the string.
    * @param state The current {@link ParserState}
    * @param item The current {@link StateItem}
    * @param str The string against which to check for a match.
@@ -1733,7 +1760,8 @@ public class Parse {
       ParserState state,
       StateItem item,
       CharSequence str,
-      int offset) {
+      int offset,
+      boolean trailing) {
     if (str == null || str.length() == 0) return 0L;
 
     // Fast path for fast mode
@@ -1771,10 +1799,11 @@ public class Parse {
         next.returnTo2 = returnTo2;
         next.currentString = str;
         next.currentOffset = offset;
+        next.currentTrailing = trailing;
       } else {
         // We've reached the end of the string.
         next.name = returnTo1;
-        next.trailingCount = 0;
+        if (!trailing) next.trailingCount = 0;
         next.returnTo1 = returnTo2;
         next.returnTo2 = null;
       }
@@ -1832,9 +1861,11 @@ public class Parse {
           resolvedPlusSign = true;
           break;
         case AffixPatternUtils.TYPE_PERCENT:
+          resolvedCp = '%'; // accept ASCII percent as well as locale percent
           resolvedStr = state.symbols.getPercentString();
           break;
         case AffixPatternUtils.TYPE_PERMILLE:
+          resolvedCp = '‰'; // accept ASCII permille as well as locale permille
           resolvedStr = state.symbols.getPerMillString();
           break;
         case AffixPatternUtils.TYPE_CURRENCY_SINGLE:
@@ -1905,9 +1936,10 @@ public class Parse {
       // String symbol
       if (hasNext) {
         added |=
-            acceptString(cp, StateName.INSIDE_AFFIX_PATTERN, returnTo, state, item, resolvedStr, 0);
+            acceptString(
+                cp, StateName.INSIDE_AFFIX_PATTERN, returnTo, state, item, resolvedStr, 0, false);
       } else {
-        added |= acceptString(cp, returnTo, null, state, item, resolvedStr, 0);
+        added |= acceptString(cp, returnTo, null, state, item, resolvedStr, 0, false);
       }
     }
     if (resolvedCurrency) {
@@ -1965,8 +1997,8 @@ public class Parse {
       str1 = state.symbols.getCurrencySymbol();
       str2 = state.symbols.getInternationalCurrencySymbol();
     }
-    added |= acceptString(cp, returnTo1, returnTo2, state, item, str1, 0);
-    added |= acceptString(cp, returnTo1, returnTo2, state, item, str2, 0);
+    added |= acceptString(cp, returnTo1, returnTo2, state, item, str1, 0, false);
+    added |= acceptString(cp, returnTo1, returnTo2, state, item, str2, 0, false);
     for (int i = Long.numberOfTrailingZeros(added); (1L << i) <= added; i++) {
       if (((1L << i) & added) != 0) {
         state.getItem(i).sawCurrency = true;
index ad5c2872a5d185db0b21070149e9019c0d111143..c5a3068ae198f7073066310be28a169e5c9307d9 100644 (file)
@@ -556,7 +556,6 @@ public class PatternString {
       int exponentDigits = 0;
       boolean hasPercentSign = false;
       boolean hasPerMilleSign = false;
-      boolean hasCurrencySign = false;
 
       StringBuilder padding = new StringBuilder();
       StringBuilder prefix = new StringBuilder();
@@ -690,7 +689,7 @@ public class PatternString {
             break;
 
           case '¤':
-            result.hasCurrencySign = true;
+            // no need to record that we saw it
             break;
         }
         consumeLiteral(state, destination);
index 9abc90c2fd7d48cfcee6545f426a6585d2c8c134..7818b2bcf2901bffcc4a2d2aa05e2ca5479d09ba 100644 (file)
@@ -617,7 +617,7 @@ public class Properties
     int count = ois.readInt();
 
     // 2) Read each field by its name and value
-    for (int i=0; i<count; i++) {
+    for (int i = 0; i < count; i++) {
       String name = (String) ois.readObject();
       Object value = ois.readObject();
 
@@ -917,6 +917,16 @@ public class Properties
   public String toString() {
     StringBuilder result = new StringBuilder();
     result.append("<Properties");
+    toStringBare(result);
+    result.append(">");
+    return result.toString();
+  }
+
+  /**
+   * Appends a string containing properties that differ from the default, but without being
+   * surrounded by &lt;Properties&gt;.
+   */
+  public void toStringBare(StringBuilder result) {
     Field[] fields = Properties.class.getDeclaredFields();
     for (Field field : fields) {
       Object myValue, defaultValue;
@@ -938,8 +948,6 @@ public class Properties
         result.append(" " + field.getName() + ":" + myValue);
       }
     }
-    result.append(">");
-    return result.toString();
   }
 
   /**
index cd2f076bf1c5dd216eca94277378d53ff7284439..19b78eef97da7a40588cb22844bf3e9116b4f18d 100644 (file)
@@ -230,24 +230,6 @@ public abstract class Rounder extends Format.BeforeFormat {
     input.setIntegerFractionLength(minInt, maxInt, minFrac, maxFrac);
   }
 
-  private static final ThreadLocal<Properties> threadLocalProperties =
-      new ThreadLocal<Properties>() {
-        @Override
-        protected Properties initialValue() {
-          return new Properties();
-        }
-      };
-
-  /**
-   * Gets a thread-local property bag that can be used to deliver properties to a constructor.
-   * Rounders themselves are guaranteed to not internally use a copy of this property bag.
-   *
-   * @return A clean, thread-local property bag.
-   */
-  public static Properties getThreadLocalProperties() {
-    return threadLocalProperties.get().clear();
-  }
-
   @Override
   public void before(FormatQuantity input, ModifierHolder mods) {
     apply(input);
index d3d8aef1147bcfbdb1b5940e11d3521e7d476110..1136a16bc5a65b261cb62f86361b1b0d6fa665bb 100644 (file)
@@ -4,6 +4,7 @@ package com.ibm.icu.impl.number.formatters;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.MissingResourceException;
 
 import com.ibm.icu.impl.ICUData;
 import com.ibm.icu.impl.ICUResourceBundle;
@@ -67,16 +68,45 @@ public class CompactDecimalFormat extends Format.BeforeFormat {
     return new CompactDecimalFormat(symbols, properties);
   }
 
+  private static final int DEFAULT_MIN_SIG = 1;
+  private static final int DEFAULT_MAX_SIG = 2;
+  private static final SignificantDigitsMode DEFAULT_SIG_MODE =
+      SignificantDigitsMode.OVERRIDE_MAXIMUM_FRACTION;
+
+  private static final ThreadLocal<Properties> threadLocalProperties =
+      new ThreadLocal<Properties>() {
+        @Override
+        protected Properties initialValue() {
+          return new Properties();
+        }
+      };
+
   private static Rounder getRounder(IProperties properties) {
     // Use rounding settings if they were specified, or else use the default CDF rounder.
-    Rounder rounder = RoundingFormat.getDefaultOrNull(properties);
+    // TODO: Detecting and overriding significant digits here is a bit of a hack, since detection
+    // is also performed in the "RoundingFormat.getDefaultOrNull" method.
+    // It would be more elegant to call some sort of "fallback" copy method.
+    Rounder rounder = null;
+    if (!SignificantDigitsRounder.useSignificantDigits(properties)) {
+      rounder = RoundingFormat.getDefaultOrNull(properties);
+    }
     if (rounder == null) {
-      rounder =
-          SignificantDigitsRounder.getInstance(
-              SignificantDigitsRounder.getThreadLocalProperties()
-                  .setMinimumSignificantDigits(1)
-                  .setMaximumSignificantDigits(2)
-                  .setSignificantDigitsMode(SignificantDigitsMode.OVERRIDE_MAXIMUM_FRACTION));
+      int _minSig = properties.getMinimumSignificantDigits();
+      int _maxSig = properties.getMaximumSignificantDigits();
+      SignificantDigitsMode _mode = properties.getSignificantDigitsMode();
+      Properties rprops = threadLocalProperties.get().clear();
+      // Settings needing possible override:
+      rprops.setMinimumSignificantDigits(_minSig > 0 ? _minSig : DEFAULT_MIN_SIG);
+      rprops.setMaximumSignificantDigits(_maxSig > 0 ? _maxSig : DEFAULT_MAX_SIG);
+      rprops.setSignificantDigitsMode(_mode != null ? _mode : DEFAULT_SIG_MODE);
+      // TODO: Should copyFrom() be used instead?  It requires a cast.
+      // Settings to copy verbatim:
+      rprops.setRoundingMode(properties.getRoundingMode());
+      rprops.setMinimumFractionDigits(properties.getMinimumFractionDigits());
+      rprops.setMaximumFractionDigits(properties.getMaximumFractionDigits());
+      rprops.setMinimumIntegerDigits(properties.getMinimumIntegerDigits());
+      rprops.setMaximumIntegerDigits(properties.getMaximumIntegerDigits());
+      rounder = SignificantDigitsRounder.getInstance(rprops);
     }
     return rounder;
   }
@@ -101,17 +131,31 @@ public class CompactDecimalFormat extends Format.BeforeFormat {
     ULocale ulocale = symbols.getULocale();
     CompactDecimalDataSink sink = new CompactDecimalDataSink(data, symbols, fingerprint);
     String nsName = NumberingSystem.getInstance(ulocale).getName();
-    ICUResourceBundle r =
+    ICUResourceBundle rb =
         (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, ulocale);
-    r.getAllItemsWithFallback("NumberElements/" + nsName, sink);
+    internalPopulateData(nsName, rb, sink, data);
+    if (data.isEmpty() && fingerprint.compactStyle == CompactStyle.LONG) {
+      // No long data is available; load short data instead
+      sink.compactStyle = CompactStyle.SHORT;
+      internalPopulateData(nsName, rb, sink, data);
+    }
+    threadLocalDataCache.get().put(fingerprint, data);
+    return data;
+  }
+
+  private static void internalPopulateData(
+      String nsName, ICUResourceBundle rb, CompactDecimalDataSink sink, CompactDecimalData data) {
+    try {
+      rb.getAllItemsWithFallback("NumberElements/" + nsName, sink);
+    } catch (MissingResourceException e) {
+      // Fall back to latn
+    }
     if (data.isEmpty() && !nsName.equals("latn")) {
-      r.getAllItemsWithFallback("NumberElements/latn", sink);
+      rb.getAllItemsWithFallback("NumberElements/latn", sink);
     }
     if (sink.exception != null) {
       throw sink.exception;
     }
-    threadLocalDataCache.get().put(fingerprint, data);
-    return data;
   }
 
   private static PositiveNegativeModifier getDefaultMod(
@@ -302,7 +346,7 @@ public class CompactDecimalFormat extends Format.BeforeFormat {
         currencySymbol = CurrencyFormat.getCurrencySymbol(symbols, properties);
       } else {
         compactType = CompactType.DECIMAL;
-        currencySymbol = symbols.getCurrencySymbol(); // fallback; should remain unused
+        currencySymbol = ""; // fallback; should remain unused
       }
       compactStyle = properties.getCompactStyle();
       uloc = symbols.getULocale();
@@ -337,12 +381,12 @@ public class CompactDecimalFormat extends Format.BeforeFormat {
 
   private static final class CompactDecimalDataSink extends UResource.Sink {
 
-    final CompactDecimalData data;
-    final DecimalFormatSymbols symbols;
-    final CompactStyle compactStyle;
-    final CompactType compactType;
-    final String currencySymbol;
-    final PNAffixGenerator pnag;
+    CompactDecimalData data;
+    DecimalFormatSymbols symbols;
+    CompactStyle compactStyle;
+    CompactType compactType;
+    String currencySymbol;
+    PNAffixGenerator pnag;
     IllegalArgumentException exception;
 
     /*
index b7575cc0e2f0545519200477325b4aea3d652763..f84d35a725963f689942ed41ddb074273ce9b6f6 100644 (file)
@@ -13,6 +13,7 @@ import com.ibm.icu.impl.number.Rounder;
 import com.ibm.icu.impl.number.modifiers.GeneralPluralModifier;
 import com.ibm.icu.impl.number.rounders.IncrementRounder;
 import com.ibm.icu.impl.number.rounders.MagnitudeRounder;
+import com.ibm.icu.impl.number.rounders.SignificantDigitsRounder;
 import com.ibm.icu.text.CurrencyPluralInfo;
 import com.ibm.icu.text.DecimalFormatSymbols;
 import com.ibm.icu.util.Currency;
@@ -288,6 +289,9 @@ public class CurrencyFormat {
       };
 
   public static Rounder getCurrencyRounder(DecimalFormatSymbols symbols, IProperties properties) {
+    if (SignificantDigitsRounder.useSignificantDigits(properties)) {
+      return SignificantDigitsRounder.getInstance(properties);
+    }
     Properties cprops = threadLocalProperties.get().clear();
     populateCurrencyRounderProperties(cprops, symbols, properties);
     if (cprops.getRoundingIncrement() != null) {
index f791ca467486aad1d6eeac5aec93d9e0489463b3..b4a33dcc8ac17411772ee8a8835c9207498b7078 100644 (file)
@@ -86,12 +86,13 @@ public class PositiveDecimalFormat implements Format.TargetFormat {
   }
 
   public static boolean allowsDecimalPoint(IProperties properties) {
-    return properties.getDecimalSeparatorAlwaysShown() || properties.getMaximumFractionDigits() != 0;
+    return properties.getDecimalSeparatorAlwaysShown()
+        || properties.getMaximumFractionDigits() != 0;
   }
 
   // Properties
   private final boolean alwaysShowDecimal;
-  private final int groupingSize;
+  private final int primaryGroupingSize;
   private final int secondaryGroupingSize;
   private final int minimumGroupingDigits;
 
@@ -104,14 +105,10 @@ public class PositiveDecimalFormat implements Format.TargetFormat {
   private final int codePointZero;
 
   public PositiveDecimalFormat(DecimalFormatSymbols symbols, IProperties properties) {
-    groupingSize =
-        (properties.getGroupingSize() < 0)
-            ? properties.getSecondaryGroupingSize()
-            : properties.getGroupingSize();
-    secondaryGroupingSize =
-        (properties.getSecondaryGroupingSize() < 0)
-            ? properties.getGroupingSize()
-            : properties.getSecondaryGroupingSize();
+    int _primary = properties.getGroupingSize();
+    int _secondary = properties.getSecondaryGroupingSize();
+    primaryGroupingSize = _primary > 0 ? _primary : _secondary > 0 ? _secondary : 0;
+    secondaryGroupingSize = _secondary > 0 ? _secondary : primaryGroupingSize;
 
     minimumGroupingDigits = properties.getMinimumGroupingDigits();
     alwaysShowDecimal = properties.getDecimalSeparatorAlwaysShown();
@@ -167,7 +164,9 @@ public class PositiveDecimalFormat implements Format.TargetFormat {
 
       // Add the decimal point
       if (input.getLowerDisplayMagnitude() < 0 || alwaysShowDecimal) {
-        length += string.insert(startIndex + length, decimalSeparator, NumberFormat.Field.DECIMAL_SEPARATOR);
+        length +=
+            string.insert(
+                startIndex + length, decimalSeparator, NumberFormat.Field.DECIMAL_SEPARATOR);
       }
 
       // Add the fraction digits
@@ -182,12 +181,16 @@ public class PositiveDecimalFormat implements Format.TargetFormat {
     int integerCount = input.getUpperDisplayMagnitude() + 1;
     for (int i = 0; i < integerCount; i++) {
       // Add grouping separator
-      if (groupingSize > 0 && i == groupingSize && integerCount - i >= minimumGroupingDigits) {
-        length += string.insert(startIndex, groupingSeparator, NumberFormat.Field.GROUPING_SEPARATOR);
+      if (primaryGroupingSize > 0
+          && i == primaryGroupingSize
+          && integerCount - i >= minimumGroupingDigits) {
+        length +=
+            string.insert(startIndex, groupingSeparator, NumberFormat.Field.GROUPING_SEPARATOR);
       } else if (secondaryGroupingSize > 0
-          && i > groupingSize
-          && (i - groupingSize) % secondaryGroupingSize == 0) {
-        length += string.insert(startIndex, groupingSeparator, NumberFormat.Field.GROUPING_SEPARATOR);
+          && i > primaryGroupingSize
+          && (i - primaryGroupingSize) % secondaryGroupingSize == 0) {
+        length +=
+            string.insert(startIndex, groupingSeparator, NumberFormat.Field.GROUPING_SEPARATOR);
       }
 
       // Get and append the next digit value
@@ -219,9 +222,13 @@ public class PositiveDecimalFormat implements Format.TargetFormat {
 
   @Override
   public void export(Properties properties) {
+    // For backwards compatibility, export 0 as secondary grouping if primary and secondary are the same
+    int effectiveSecondaryGroupingSize =
+        secondaryGroupingSize == primaryGroupingSize ? 0 : secondaryGroupingSize;
+
     properties.setDecimalSeparatorAlwaysShown(alwaysShowDecimal);
-    properties.setGroupingSize(groupingSize);
-    properties.setSecondaryGroupingSize(secondaryGroupingSize);
+    properties.setGroupingSize(primaryGroupingSize);
+    properties.setSecondaryGroupingSize(effectiveSecondaryGroupingSize);
     properties.setMinimumGroupingDigits(minimumGroupingDigits);
   }
 }
index 2f8c6b2fd785d848fe1d91958c885edb2406e67f..102a38b797373a01e21e0c48a42751e3096b4740 100644 (file)
@@ -9,19 +9,11 @@
 
 package com.ibm.icu.text;
 
-import java.io.IOException;
-import java.io.NotSerializableException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.text.AttributedCharacterIterator;
-import java.text.FieldPosition;
 import java.text.ParsePosition;
 import java.util.Locale;
 
-import com.ibm.icu.impl.number.FormatQuantity4;
 import com.ibm.icu.impl.number.Properties;
+import com.ibm.icu.util.CurrencyAmount;
 import com.ibm.icu.util.ULocale;
 
 /**
@@ -55,7 +47,7 @@ import com.ibm.icu.util.ULocale;
  */
 public class CompactDecimalFormat extends DecimalFormat {
 
-    private static final long serialVersionUID = 4716293295276629682L;
+  private static final long serialVersionUID = 4716293295276629682L;
 
   /**
    * Style parameter for CompactDecimalFormat.
@@ -123,125 +115,22 @@ public class CompactDecimalFormat extends DecimalFormat {
   }
 
   /**
-   * {@inheritDoc}
-   *
-   * @stable ICU 49
-   */
-  @Override
-  public boolean equals(Object obj) {
-    return super.equals(obj);
-  }
-
-  /**
-   * {@inheritDoc}
-   *
-   * @stable ICU 49
-   */
-  @Override
-  public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) {
-    FormatQuantity4 fq = new FormatQuantity4(number);
-    formatter.format(fq, toAppendTo, pos);
-    fq.populateUFieldPosition(pos);
-    return toAppendTo;
-  }
-
-  /**
-   * {@inheritDoc}
-   *
-   * @stable ICU 50
-   */
-  @Override
-  public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
-    if (!(obj instanceof Number)) throw new IllegalArgumentException();
-    Number number = (Number) obj;
-    FormatQuantity4 fq = new FormatQuantity4(number);
-    AttributedCharacterIterator result = formatter.formatToCharacterIterator(fq);
-    return result;
-  }
-
-  /**
-   * {@inheritDoc}
-   *
-   * @stable ICU 49
-   */
-  @Override
-  public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) {
-    FormatQuantity4 fq = new FormatQuantity4(number);
-    formatter.format(fq, toAppendTo, pos);
-    fq.populateUFieldPosition(pos);
-    return toAppendTo;
-  }
-
-  /**
-   * {@inheritDoc}
-   *
-   * @stable ICU 49
-   */
-  @Override
-  public StringBuffer format(BigInteger number, StringBuffer toAppendTo, FieldPosition pos) {
-    FormatQuantity4 fq = new FormatQuantity4(number);
-    formatter.format(fq, toAppendTo, pos);
-    fq.populateUFieldPosition(pos);
-    return toAppendTo;
-  }
-
-  /**
-   * {@inheritDoc}
-   *
-   * @stable ICU 49
-   */
-  @Override
-  public StringBuffer format(BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) {
-    FormatQuantity4 fq = new FormatQuantity4(number);
-    formatter.format(fq, toAppendTo, pos);
-    fq.populateUFieldPosition(pos);
-    return toAppendTo;
-  }
-
-  /**
-   * {@inheritDoc}
+   * Parsing is currently unsupported, and throws an UnsupportedOperationException.
    *
    * @stable ICU 49
    */
   @Override
-  public StringBuffer format(
-      com.ibm.icu.math.BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) {
-    FormatQuantity4 fq = new FormatQuantity4(number.toBigDecimal());
-    formatter.format(fq, toAppendTo, pos);
-    fq.populateUFieldPosition(pos);
-    return toAppendTo;
+  public Number parse(String text, ParsePosition parsePosition) {
+    throw new UnsupportedOperationException();
   }
 
-//  /**
-//   * {@inheritDoc}
-//   *
-//   * @internal ICU 57 technology preview
-//   * @deprecated This API might change or be removed in a future release.
-//   */
-//  @Override
-//  @Deprecated
-//  public StringBuffer format(CurrencyAmount currAmt, StringBuffer toAppendTo, FieldPosition pos) {
-//    // TODO(sffc)
-//    throw new UnsupportedOperationException();
-//  }
-
   /**
    * Parsing is currently unsupported, and throws an UnsupportedOperationException.
    *
    * @stable ICU 49
    */
   @Override
-  public Number parse(String text, ParsePosition parsePosition) {
+  public CurrencyAmount parseCurrency(CharSequence text, ParsePosition parsePosition) {
     throw new UnsupportedOperationException();
   }
-
-  // DISALLOW Serialization, at least while draft
-
-  private void writeObject(ObjectOutputStream out) throws IOException {
-    throw new NotSerializableException();
-  }
-
-  private void readObject(ObjectInputStream in) throws IOException {
-    throw new NotSerializableException();
-  }
 }
index 82e0cda2a5ee85091b163327850d7b5e4913524d..1fab6789e48a93310953d17e11677a4e55b1bf63 100644 (file)
@@ -418,7 +418,11 @@ public class DecimalFormat extends NumberFormat {
   //                               FORMAT AND PARSE APIS                                 //
   //=====================================================================================//
 
-  /** @stable ICU 2.0 */
+  /**
+   * {@inheritDoc}
+   *
+   * @stable ICU 2.0
+   */
   @Override
   public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition) {
     FormatQuantity4 fq = new FormatQuantity4(number);
@@ -427,7 +431,11 @@ public class DecimalFormat extends NumberFormat {
     return result;
   }
 
-  /** @stable ICU 2.0 */
+  /**
+   * {@inheritDoc}
+   *
+   * @stable ICU 2.0
+   */
   @Override
   public StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition) {
     FormatQuantity4 fq = new FormatQuantity4(number);
@@ -436,7 +444,11 @@ public class DecimalFormat extends NumberFormat {
     return result;
   }
 
-  /** @stable ICU 2.0 */
+  /**
+   * {@inheritDoc}
+   *
+   * @stable ICU 2.0
+   */
   @Override
   public StringBuffer format(BigInteger number, StringBuffer result, FieldPosition fieldPosition) {
     FormatQuantity4 fq = new FormatQuantity4(number);
@@ -445,7 +457,11 @@ public class DecimalFormat extends NumberFormat {
     return result;
   }
 
-  /** @stable ICU 2.0 */
+  /**
+   * {@inheritDoc}
+   *
+   * @stable ICU 2.0
+   */
   @Override
   public StringBuffer format(
       java.math.BigDecimal number, StringBuffer result, FieldPosition fieldPosition) {
@@ -455,7 +471,11 @@ public class DecimalFormat extends NumberFormat {
     return result;
   }
 
-  /** @stable ICU 2.0 */
+  /**
+   * {@inheritDoc}
+   *
+   * @stable ICU 2.0
+   */
   @Override
   public StringBuffer format(BigDecimal number, StringBuffer result, FieldPosition fieldPosition) {
     FormatQuantity4 fq = new FormatQuantity4(number.toBigDecimal());
@@ -464,7 +484,11 @@ public class DecimalFormat extends NumberFormat {
     return result;
   }
 
-  /** @stable ICU 3.6 */
+  /**
+   * {@inheritDoc}
+   *
+   * @stable ICU 3.6
+   */
   @Override
   public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
     if (!(obj instanceof Number)) throw new IllegalArgumentException();
@@ -474,7 +498,7 @@ public class DecimalFormat extends NumberFormat {
     return result;
   }
 
-  protected static final ThreadLocal<Properties> threadLocalCurrencyProperties =
+  private static final ThreadLocal<Properties> threadLocalCurrencyProperties =
       new ThreadLocal<Properties>() {
         @Override
         protected Properties initialValue() {
@@ -482,24 +506,42 @@ public class DecimalFormat extends NumberFormat {
         }
       };
 
+  /**
+   * {@inheritDoc}
+   *
+   * @stable ICU 3.0
+   */
   @Override
   public StringBuffer format(CurrencyAmount currAmt, StringBuffer toAppendTo, FieldPosition pos) {
     // TODO: This is ugly (although not as ugly as it was in ICU 58).
     // Currency should be a free parameter, not in property bag. Fix in ICU 60.
     Properties cprops = threadLocalCurrencyProperties.get();
+    SingularFormat fmt = null;
     synchronized (this) {
-      cprops.copyFrom(properties);
+      // Use the pre-compiled formatter if possible.  Otherwise, copy the properties
+      // and build our own formatter.
+      // TODO: Consider using a static format path here.
+      if (currAmt.getCurrency().equals(properties.getCurrency())) {
+        fmt = formatter;
+      } else {
+        cprops.copyFrom(properties);
+      }
+    }
+    if (fmt == null) {
+      cprops.setCurrency(currAmt.getCurrency());
+      fmt = Endpoint.fromBTA(cprops, symbols);
     }
-    cprops.setCurrency(currAmt.getCurrency());
     FormatQuantity4 fq = new FormatQuantity4(currAmt.getNumber());
-    // TODO: Use a static format path here
-    SingularFormat fmt = Endpoint.fromBTA(cprops, symbols);
     fmt.format(fq, toAppendTo, pos);
     fq.populateUFieldPosition(pos);
     return toAppendTo;
   }
 
-  /** @stable ICU 2.0 */
+  /**
+   * {@inheritDoc}
+   *
+   * @stable ICU 2.0
+   */
   @Override
   public Number parse(String text, ParsePosition parsePosition) {
     // Backwards compatibility: use currency parse mode if this is a currency instance
@@ -511,7 +553,11 @@ public class DecimalFormat extends NumberFormat {
     return result;
   }
 
-  /** @stable ICU 49 */
+  /**
+   * {@inheritDoc}
+   *
+   * @stable ICU 49
+   */
   @Override
   public CurrencyAmount parseCurrency(CharSequence text, ParsePosition parsePosition) {
     try {
@@ -1922,7 +1968,11 @@ public class DecimalFormat extends NumberFormat {
     return properties.equals(other.properties) && symbols.equals(other.symbols);
   }
 
-  /** @stable ICU 2.0 */
+  /**
+   * {@inheritDoc}
+   *
+   * @stable ICU 2.0
+   */
   @Override
   public synchronized int hashCode() {
     return properties.hashCode();
@@ -1936,9 +1986,26 @@ public class DecimalFormat extends NumberFormat {
         }
       };
 
+  /**
+   * Returns the default value of toString() with extra DecimalFormat-specific information appended
+   * to the end of the string. This extra information is intended for debugging purposes, and the
+   * format is not guaranteed to be stable.
+   *
+   * @stable ICU 2.0
+   */
   @Override
-  public synchronized String toString() {
-    return "<DecimalFormat " + symbols.toString() + " " + properties.toString() + ">";
+  public String toString() {
+    StringBuilder result = new StringBuilder();
+    result.append(getClass().getName());
+    result.append("@");
+    result.append(Integer.toHexString(hashCode()));
+    result.append(" { symbols@");
+    result.append(Integer.toHexString(symbols.hashCode()));
+    synchronized (this) {
+      properties.toStringBare(result);
+    }
+    result.append(" }");
+    return result.toString();
   }
 
   /**
@@ -2027,7 +2094,17 @@ public class DecimalFormat extends NumberFormat {
     refreshFormatter();
   }
 
+  /**
+   * @internal
+   * @deprecated This API is ICU internal only.
+   */
+  @Deprecated
   public static interface PropertySetter {
+    /**
+     * @internal
+     * @deprecated This API is ICU internal only.
+     */
+    @Deprecated
     public void set(Properties props);
   }
 
index 74d1928a7bb4106b6355d03750300f3a73d7072e..80f8d944c00382bc88e0de8a95c9c7f7fdcb3642 100644 (file)
@@ -1044,7 +1044,8 @@ public abstract class NumberFormat extends UFormat {
     // ===== End of factory stuff =====
 
     /**
-     * Overrides hashCode.
+     * {@inheritDoc}
+     *
      * @stable ICU 2.0
      */
     @Override
index 90a8eda81dd5e1b6093c7627ac251a7d45b75872..7c3aa236f7d80966e6dc5823efe13e9d7cf367a0 100644 (file)
@@ -418,18 +418,73 @@ public class PluralRules implements Serializable {
      */
     @Deprecated
     public static enum Operand {
-        /** The double value of the entire number. */
+        /**
+         * The double value of the entire number.
+         *
+         * @internal
+         * @deprecated This API is ICU internal only.
+         */
+        @Deprecated
         n,
-        /** The integer value, with the fraction digits truncated off. */
+
+        /**
+         * The integer value, with the fraction digits truncated off.
+         *
+         * @internal
+         * @deprecated This API is ICU internal only.
+         */
+        @Deprecated
         i,
-        /** All visible fraction digits as an integer, including trailing zeros. */
+
+        /**
+         * All visible fraction digits as an integer, including trailing zeros.
+         *
+         * @internal
+         * @deprecated This API is ICU internal only.
+         */
+        @Deprecated
         f,
-        /** Visible fraction digits, not including trailing zeros. */
+
+        /**
+         * Visible fraction digits as an integer, not including trailing zeros.
+         *
+         * @internal
+         * @deprecated This API is ICU internal only.
+         */
+        @Deprecated
         t,
-        /** Number of visible fraction digits. */
+
+        /**
+         * Number of visible fraction digits.
+         *
+         * @internal
+         * @deprecated This API is ICU internal only.
+         */
+        @Deprecated
         v,
+
+        /**
+         * Number of visible fraction digits, not including trailing zeros.
+         *
+         * @internal
+         * @deprecated This API is ICU internal only.
+         */
+        @Deprecated
         w,
-        /* deprecated */
+
+        /**
+         * 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.
+         * That is, using "j" instead of "i" is like implicitly adding "v is 0".
+         *
+         * <p>For example, "j is 3" is equivalent to "i is 3 and v is 0": it matches
+         * "3" but not "3.1" or "3.0".
+         *
+         * @internal
+         * @deprecated This API is ICU internal only.
+         */
+        @Deprecated
         j;
     }
 
@@ -439,8 +494,28 @@ public class PluralRules implements Serializable {
      */
     @Deprecated
     public static interface IFixedDecimal {
+        /**
+         * Returns the value corresponding to the specified operand (n, i, f, t, v, or w).
+         * If the operand is 'n', returns a double; otherwise, returns an integer.
+         *
+         * @internal
+         * @deprecated This API is ICU internal only.
+         */
+        @Deprecated
         public double getPluralOperand(Operand operand);
+
+        /**
+         * @internal
+         * @deprecated This API is ICU internal only.
+         */
+        @Deprecated
         public boolean isNaN();
+
+        /**
+         * @internal
+         * @deprecated This API is ICU internal only.
+         */
+        @Deprecated
         public boolean isInfinite();
     }
 
@@ -744,6 +819,8 @@ public class PluralRules implements Serializable {
         }
 
         /**
+         * {@inheritDoc}
+         *
          * @internal
          * @deprecated This API is ICU internal only.
          */
@@ -751,12 +828,13 @@ public class PluralRules implements Serializable {
         @Deprecated
         public double getPluralOperand(Operand operand) {
             switch(operand) {
-            default: return source;
+            case n: return source;
             case i: return integerValue;
             case f: return decimalDigits;
             case t: return decimalDigitsWithoutTrailingZeros;
             case v: return visibleDecimalDigitCount;
             case w: return visibleDecimalDigitCountWithoutTrailingZeros;
+            default: return source;
             }
         }
 
@@ -904,17 +982,25 @@ public class PluralRules implements Serializable {
             throw new NotSerializableException();
         }
 
-        /* (non-Javadoc)
-         * @see com.ibm.icu.text.PluralRules.IFixedDecimal#isNaN()
+        /**
+         * {@inheritDoc}
+         *
+         * @internal
+         * @deprecated This API is ICU internal only.
          */
+        @Deprecated
         @Override
         public boolean isNaN() {
             return Double.isNaN(source);
         }
 
-        /* (non-Javadoc)
-         * @see com.ibm.icu.text.PluralRules.IFixedDecimal#isInfinite()
+        /**
+         * {@inheritDoc}
+         *
+         * @internal
+         * @deprecated This API is ICU internal only.
          */
+        @Deprecated
         @Override
         public boolean isInfinite() {
             return Double.isInfinite(source);
index 8437c96884cc1c67e02c68f56ea3cef8f9a14c8b..69dbba94ccfabd950006d243451390e5df97c1f7 100644 (file)
@@ -762,15 +762,30 @@ public class Currency extends MeasureUnit {
         private String isoCode;
         private String currencyString;
 
+        /**
+         * @internal
+         * @deprecated This API is ICU internal only.
+         */
+        @Deprecated
         public CurrencyStringInfo(String isoCode, String currencyString) {
             this.isoCode = isoCode;
             this.currencyString = currencyString;
         }
 
+        /**
+         * @internal
+         * @deprecated This API is ICU internal only.
+         */
+        @Deprecated
         public String getISOCode() {
             return isoCode;
         }
 
+        /**
+         * @internal
+         * @deprecated This API is ICU internal only.
+         */
+        @Deprecated
         @SuppressWarnings("unused")
         public String getCurrencyString() {
             return currencyString;
index 7af68500597374b663cd5bfc20a7548ee1576f1c..eb7b094b18fbbedc9163281bb01861c238213c9b 100644 (file)
@@ -850,6 +850,17 @@ parse      output  breaks
 // JDK parses as -1945
 (1,945d1)      fail    K
 
+test parse strict scientific
+set locale en
+set pattern #E0
+set lenient 0
+begin
+parse  output  breaks
+123    fail    JK
+123E1  1230
+123E0  123
+123E   fail    JK
+
 test parse strict without prefix/suffix
 set locale en
 set pattern #
index ac8e8aca4dd031ada2e340c3bb572325ddf4c82b..f50ea6c6a03b37b7333e5bfc0b0e8a0e8f1d2ac2 100644 (file)
@@ -553,6 +553,66 @@ public class CompactDecimalFormatTest extends TestFmwk {
         }
     }
 
+    @Test
+    public void TestLongShortFallback() {
+        // smn, dz have long but not short
+        // es_US, es_GT, es_419, ee have short but not long
+        ULocale[] locs = new ULocale[] {
+            new ULocale("smn"),
+            new ULocale("es_US"),
+            new ULocale("es_GT"),
+            new ULocale("es_419"),
+            new ULocale("ee"),
+        };
+        double number = 12345.0;
+        // These expected values are the same in both ICU 58 and 59.
+        String[][] expectedShortLong = new String[][] {
+            { "12K", "12 tuhháát" },
+            { "12k", "12 mil" },
+            { "12k", "12 mil" },
+            { "12k", "12 mil" },
+            { "12K", "12K" },
+        };
+
+        for (int i=0; i<locs.length; i++) {
+            ULocale loc = locs[i];
+            String expectedShort = expectedShortLong[i][0];
+            String expectedLong = expectedShortLong[i][1];
+            CompactDecimalFormat cdfShort = CompactDecimalFormat.getInstance(loc, CompactStyle.SHORT);
+            CompactDecimalFormat cdfLong = CompactDecimalFormat.getInstance(loc, CompactStyle.LONG);
+            String actualShort = cdfShort.format(number);
+            String actualLong = cdfLong.format(number);
+            assertEquals("Short, locale " + loc, expectedShort, actualShort);
+            assertEquals("Long, locale " + loc, expectedLong, actualLong);
+        }
+    }
+
+    @Test
+    public void TestLocales() {
+        // Run a CDF over all locales to make sure there are no unexpected exceptions.
+        ULocale[] locs = ULocale.getAvailableLocales();
+        for (ULocale loc : locs) {
+            CompactDecimalFormat cdfShort = CompactDecimalFormat.getInstance(loc, CompactStyle.SHORT);
+            CompactDecimalFormat cdfLong = CompactDecimalFormat.getInstance(loc, CompactStyle.LONG);
+            for (double d = 12345.0; d > 0.01; d /= 10) {
+                String s1 = cdfShort.format(d);
+                String s2 = cdfLong.format(d);
+                assertNotNull("Short " + loc, s1);
+                assertNotNull("Long " + loc, s2);
+                assertNotEquals("Short " + loc, 0, s1.length());
+                assertNotEquals("Long " + loc, 0, s2.length());
+            }
+        }
+    }
+
+    @Test
+    public void TestDigitDisplay() {
+        CompactDecimalFormat cdf = CompactDecimalFormat.getInstance(ULocale.US, CompactStyle.SHORT);
+        cdf.setMinimumSignificantDigits(2);
+        String actual = cdf.format(70123.45678);
+        assertEquals("Should not display any extra fraction digits", "70K", actual);
+    }
+
     @Test
     public void TestBug12422() {
         CompactDecimalFormat cdf;
index d3f92feb643debf41dde92137c719c9836d81059..0adeb32d36ed4d8bad65389f6820f7414cf6cba9 100644 (file)
@@ -4683,7 +4683,9 @@ public class NumberFormatTest extends TestFmwk {
         NumberFormat fmt = NumberFormat.getInstance(new ULocale("en"));
         fmt.setMinimumIntegerDigits(10);
         FieldPosition pos = new FieldPosition(NumberFormat.Field.GROUPING_SEPARATOR);
-        fmt.format(1234, new StringBuffer(), pos);
+        StringBuffer sb = new StringBuffer();
+        fmt.format(1234567, sb, pos);
+        assertEquals("Should have multiple grouping separators", "0,001,234,567", sb.toString());
         assertEquals("FieldPosition should report the first occurence", 1, pos.getBeginIndex());
         assertEquals("FieldPosition should report the first occurence", 2, pos.getEndIndex());
     }
@@ -4940,6 +4942,27 @@ public class NumberFormatTest extends TestFmwk {
         assertNotEquals("df2 != df1", df2, df1);
     }
 
+    @Test
+    public void Test13055() {
+        DecimalFormat df = (DecimalFormat) NumberFormat.getPercentInstance();
+        df.setMaximumFractionDigits(0);
+        df.setRoundingMode(BigDecimal.ROUND_HALF_EVEN);
+        assertEquals("Should round percent toward even number", "216%", df.format(2.155));
+    }
+
+    @Test
+    public void Test13056() {
+        DecimalFormat df = new DecimalFormat("#,##0");
+        assertEquals("Primary grouping should return 3", 3, df.getGroupingSize());
+        assertEquals("Secondary grouping should return 0", 0, df.getSecondaryGroupingSize());
+        df.setSecondaryGroupingSize(3);
+        assertEquals("Primary grouping should still return 3", 3, df.getGroupingSize());
+        assertEquals("Secondary grouping should still return 0", 0, df.getSecondaryGroupingSize());
+        df.setGroupingSize(4);
+        assertEquals("Primary grouping should return 4", 4, df.getGroupingSize());
+        assertEquals("Secondary should remember explicit setting and return 3", 3, df.getSecondaryGroupingSize());
+    }
+
     @Test
     public void testPercentZero() {
         DecimalFormat df = (DecimalFormat) NumberFormat.getPercentInstance();
@@ -5017,6 +5040,89 @@ public class NumberFormatTest extends TestFmwk {
         assertEquals("Rounding mode ordinal from java.math.RoundingMode should be the same", df1, df2);
     }
 
+    @Test
+    public void testCurrencySignificantDigits() {
+        ULocale locale = new ULocale("en-US");
+        DecimalFormat df = (DecimalFormat) NumberFormat.getCurrencyInstance(locale);
+        df.setMaximumSignificantDigits(2);
+        String result = df.format(1234);
+        assertEquals("Currency rounding should obey significant digits", "$1,200", result);
+    }
+
+    @Test
+    public void testParseStrictScientific() {
+        // See ticket #13057
+        DecimalFormat df = (DecimalFormat) NumberFormat.getScientificInstance();
+        df.setParseStrict(true);
+        ParsePosition ppos = new ParsePosition(0);
+        Number result0 = df.parse("123E4", ppos);
+        assertEquals("Should accept number with exponent", 1230000L, result0);
+        assertEquals("Should consume the whole number", 5, ppos.getIndex());
+        ppos.setIndex(0);
+        result0 = df.parse("123", ppos);
+        assertNull("Should reject number without exponent", result0);
+        ppos.setIndex(0);
+        CurrencyAmount result1 = df.parseCurrency("USD123", ppos);
+        assertNull("Should reject currency without exponent", result1);
+    }
+
+    @Test
+    public void testParseLenientScientific() {
+        DecimalFormat df = (DecimalFormat) NumberFormat.getScientificInstance();
+        ParsePosition ppos = new ParsePosition(0);
+        Number result0 = df.parse("123E", ppos);
+        assertEquals("Should parse the number in lenient mode", 123L, result0);
+        assertEquals("Should stop before the E", 3, ppos.getIndex());
+        DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();
+        dfs.setExponentSeparator("EE");
+        df.setDecimalFormatSymbols(dfs);
+        ppos.setIndex(0);
+        result0 = df.parse("123EE", ppos);
+        assertEquals("Should parse the number in lenient mode", 123L, result0);
+        assertEquals("Should stop before the EE", 3, ppos.getIndex());
+    }
+
+    @Test
+    public void testParseAcceptAsciiPercentPermilleFallback() {
+        ULocale loc = new ULocale("ar");
+        DecimalFormat df = (DecimalFormat) NumberFormat.getPercentInstance(loc);
+        ParsePosition ppos = new ParsePosition(0);
+        Number result = df.parse("42%", ppos);
+        assertEquals("Should parse as 0.42 even in ar", new BigDecimal("0.42"), result);
+        assertEquals("Should consume the entire string even in ar", 3, ppos.getIndex());
+        // TODO: Is there a better way to make a localized permille formatter?
+        df.applyPattern(df.toPattern().replace("%", "‰"));
+        ppos.setIndex(0);
+        result = df.parse("42‰", ppos);
+        assertEquals("Should parse as 0.042 even in ar", new BigDecimal("0.042"), result);
+        assertEquals("Should consume the entire string even in ar", 3, ppos.getIndex());
+    }
+
+    @Test
+    public void testParseSubtraction() {
+        // TODO: Is this a case we need to support? It prevents us from automatically parsing
+        // minus signs that appear after the number, like  in "12-" vs "-12".
+        DecimalFormat df = new DecimalFormat();
+        String str = "12 - 5";
+        ParsePosition ppos = new ParsePosition(0);
+        Number n1 = df.parse(str, ppos);
+        Number n2 = df.parse(str, ppos);
+        assertEquals("Should parse 12 and -5", 7, n1.intValue() + n2.intValue());
+    }
+
+    @Test
+    public void testMultiCodePointPaddingInPattern() {
+        DecimalFormat df = new DecimalFormat("a*'நி'###0b");
+        String result = df.format(12);
+        assertEquals("Multi-codepoint padding should not be split", "aநிநி12b", result);
+        df = new DecimalFormat("a*😁###0b");
+        result = df.format(12);
+        assertEquals("Single-codepoint padding should not be split", "a😁😁12b", result);
+        df = new DecimalFormat("a*''###0b");
+        result = df.format(12);
+        assertEquals("Quote should be escapable in padding syntax", "a''12b", result);
+    }
+
     @Test
     public void testSignificantDigitsMode() {
         String[][] allExpected = {