]> granicus.if.org Git - icu/commitdiff
ICU-13513 Starting to tie in with existing code. Working through the data-driven...
authorShane Carr <shane@unicode.org>
Wed, 13 Dec 2017 10:04:56 +0000 (10:04 +0000)
committerShane Carr <shane@unicode.org>
Wed, 13 Dec 2017 10:04:56 +0000 (10:04 +0000)
X-SVN-Rev: 40726

icu4j/main/classes/core/src/com/ibm/icu/impl/number/CurrencyPluralInfoAffixProvider.java [new file with mode: 0644]
icu4j/main/classes/core/src/com/ibm/icu/impl/number/PropertiesAffixPatternProvider.java [new file with mode: 0644]
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/DecimalMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ScientificMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/StrictMatcher.java [new file with mode: 0644]
icu4j/main/classes/core/src/com/ibm/icu/number/NumberPropertyMapper.java
icu4j/main/tests/core/src/com/ibm/icu/dev/data/numberformattestspecification.txt
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatDataDrivenTest.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java

diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CurrencyPluralInfoAffixProvider.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CurrencyPluralInfoAffixProvider.java
new file mode 100644 (file)
index 0000000..5f8d653
--- /dev/null
@@ -0,0 +1,56 @@
+// ยฉ 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package com.ibm.icu.impl.number;
+
+import com.ibm.icu.impl.StandardPlural;
+import com.ibm.icu.impl.number.PatternStringParser.ParsedPatternInfo;
+import com.ibm.icu.text.CurrencyPluralInfo;
+
+public class CurrencyPluralInfoAffixProvider implements AffixPatternProvider {
+    private final AffixPatternProvider[] affixesByPlural;
+
+    public CurrencyPluralInfoAffixProvider(CurrencyPluralInfo cpi) {
+        affixesByPlural = new ParsedPatternInfo[StandardPlural.COUNT];
+        for (StandardPlural plural : StandardPlural.VALUES) {
+            affixesByPlural[plural.ordinal()] = PatternStringParser
+                    .parseToPatternInfo(cpi.getCurrencyPluralPattern(plural.getKeyword()));
+        }
+    }
+
+    @Override
+    public char charAt(int flags, int i) {
+        int pluralOrdinal = (flags & Flags.PLURAL_MASK);
+        return affixesByPlural[pluralOrdinal].charAt(flags, i);
+    }
+
+    @Override
+    public int length(int flags) {
+        int pluralOrdinal = (flags & Flags.PLURAL_MASK);
+        return affixesByPlural[pluralOrdinal].length(flags);
+    }
+
+    @Override
+    public boolean positiveHasPlusSign() {
+        return affixesByPlural[StandardPlural.OTHER.ordinal()].positiveHasPlusSign();
+    }
+
+    @Override
+    public boolean hasNegativeSubpattern() {
+        return affixesByPlural[StandardPlural.OTHER.ordinal()].hasNegativeSubpattern();
+    }
+
+    @Override
+    public boolean negativeHasMinusSign() {
+        return affixesByPlural[StandardPlural.OTHER.ordinal()].negativeHasMinusSign();
+    }
+
+    @Override
+    public boolean hasCurrencySign() {
+        return affixesByPlural[StandardPlural.OTHER.ordinal()].hasCurrencySign();
+    }
+
+    @Override
+    public boolean containsSymbolType(int type) {
+        return affixesByPlural[StandardPlural.OTHER.ordinal()].containsSymbolType(type);
+    }
+}
\ No newline at end of file
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PropertiesAffixPatternProvider.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PropertiesAffixPatternProvider.java
new file mode 100644 (file)
index 0000000..aada5fd
--- /dev/null
@@ -0,0 +1,128 @@
+// ยฉ 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package com.ibm.icu.impl.number;
+
+public class PropertiesAffixPatternProvider implements AffixPatternProvider {
+    private final String posPrefix;
+    private final String posSuffix;
+    private final String negPrefix;
+    private final String negSuffix;
+
+    public PropertiesAffixPatternProvider(DecimalFormatProperties properties) {
+        // There are two ways to set affixes in DecimalFormat: via the pattern string (applyPattern), and via the
+        // explicit setters (setPositivePrefix and friends).  The way to resolve the settings is as follows:
+        //
+        // 1) If the explicit setting is present for the field, use it.
+        // 2) Otherwise, follows UTS 35 rules based on the pattern string.
+        //
+        // Importantly, the explicit setters affect only the one field they override.  If you set the positive
+        // prefix, that should not affect the negative prefix.  Since it is impossible for the user of this class
+        // to know whether the origin for a string was the override or the pattern, we have to say that we always
+        // have a negative subpattern and perform all resolution logic here.
+
+        // Convenience: Extract the properties into local variables.
+        // Variables are named with three chars: [p/n][p/s][o/p]
+        // [p/n] => p for positive, n for negative
+        // [p/s] => p for prefix, s for suffix
+        // [o/p] => o for escaped custom override string, p for pattern string
+        String ppo = AffixUtils.escape(properties.getPositivePrefix());
+        String pso = AffixUtils.escape(properties.getPositiveSuffix());
+        String npo = AffixUtils.escape(properties.getNegativePrefix());
+        String nso = AffixUtils.escape(properties.getNegativeSuffix());
+        String ppp = properties.getPositivePrefixPattern();
+        String psp = properties.getPositiveSuffixPattern();
+        String npp = properties.getNegativePrefixPattern();
+        String nsp = properties.getNegativeSuffixPattern();
+
+        if (ppo != null) {
+            posPrefix = ppo;
+        } else if (ppp != null) {
+            posPrefix = ppp;
+        } else {
+            // UTS 35: Default positive prefix is empty string.
+            posPrefix = "";
+        }
+
+        if (pso != null) {
+            posSuffix = pso;
+        } else if (psp != null) {
+            posSuffix = psp;
+        } else {
+            // UTS 35: Default positive suffix is empty string.
+            posSuffix = "";
+        }
+
+        if (npo != null) {
+            negPrefix = npo;
+        } else if (npp != null) {
+            negPrefix = npp;
+        } else {
+            // UTS 35: Default negative prefix is "-" with positive prefix.
+            // Important: We prepend the "-" to the pattern, not the override!
+            negPrefix = ppp == null ? "-" : "-" + ppp;
+        }
+
+        if (nso != null) {
+            negSuffix = nso;
+        } else if (nsp != null) {
+            negSuffix = nsp;
+        } else {
+            // UTS 35: Default negative prefix is the positive prefix.
+            negSuffix = psp == null ? "" : psp;
+        }
+    }
+
+    @Override
+    public char charAt(int flags, int i) {
+        return getStringForFlags(flags).charAt(i);
+    }
+
+    @Override
+    public int length(int flags) {
+        return getStringForFlags(flags).length();
+    }
+
+    private String getStringForFlags(int flags) {
+        boolean prefix = (flags & Flags.PREFIX) != 0;
+        boolean negative = (flags & Flags.NEGATIVE_SUBPATTERN) != 0;
+        if (prefix && negative) {
+            return negPrefix;
+        } else if (prefix) {
+            return posPrefix;
+        } else if (negative) {
+            return negSuffix;
+        } else {
+            return posSuffix;
+        }
+    }
+
+    @Override
+    public boolean positiveHasPlusSign() {
+        return AffixUtils.containsType(posPrefix, AffixUtils.TYPE_PLUS_SIGN)
+                || AffixUtils.containsType(posSuffix, AffixUtils.TYPE_PLUS_SIGN);
+    }
+
+    @Override
+    public boolean hasNegativeSubpattern() {
+        // See comments in the constructor for more information on why this is always true.
+        return true;
+    }
+
+    @Override
+    public boolean negativeHasMinusSign() {
+        return AffixUtils.containsType(negPrefix, AffixUtils.TYPE_MINUS_SIGN)
+                || AffixUtils.containsType(negSuffix, AffixUtils.TYPE_MINUS_SIGN);
+    }
+
+    @Override
+    public boolean hasCurrencySign() {
+        return AffixUtils.hasCurrencySymbols(posPrefix) || AffixUtils.hasCurrencySymbols(posSuffix)
+                || AffixUtils.hasCurrencySymbols(negPrefix) || AffixUtils.hasCurrencySymbols(negSuffix);
+    }
+
+    @Override
+    public boolean containsSymbolType(int type) {
+        return AffixUtils.containsType(posPrefix, type) || AffixUtils.containsType(posSuffix, type)
+                || AffixUtils.containsType(negPrefix, type) || AffixUtils.containsType(negSuffix, type);
+    }
+}
\ No newline at end of file
index c6a63a992beba7313fdbf937cc92b76372234f5b..8cbe29397ac6e764981bdedc77c463da4b2839c5 100644 (file)
@@ -16,22 +16,37 @@ import com.ibm.icu.text.UnicodeSet;
  */
 public class DecimalMatcher implements NumberParseMatcher {
 
-    /**
-     * @return
-     */
+    // TODO: Re-generate these sets from the database. They probably haven't been updated in a while.
+    private static final UnicodeSet UNISET_PERIOD_LIKE = new UnicodeSet("[.\\u2024\\u3002\\uFE12\\uFE52\\uFF0E\\uFF61]")
+            .freeze();
+    private static final UnicodeSet UNISET_STRICT_PERIOD_LIKE = new UnicodeSet("[.\\u2024\\uFE52\\uFF0E\\uFF61]")
+            .freeze();
+    private static final UnicodeSet UNISET_COMMA_LIKE = new UnicodeSet(
+            "[,\\u060C\\u066B\\u3001\\uFE10\\uFE11\\uFE50\\uFE51\\uFF0C\\uFF64]").freeze();
+    private static final UnicodeSet UNISET_STRICT_COMMA_LIKE = new UnicodeSet("[,\\u066B\\uFE10\\uFE50\\uFF0C]")
+            .freeze();
+    private static final UnicodeSet UNISET_OTHER_GROUPING_SEPARATORS = new UnicodeSet(
+            "[\\ '\\u00A0\\u066C\\u2000-\\u200A\\u2018\\u2019\\u202F\\u205F\\u3000\\uFF07]").freeze();
+
     public static DecimalMatcher getInstance(DecimalFormatSymbols symbols) {
-        // TODO(sffc): Auto-generated method stub
-        return new DecimalMatcher(symbols.getDigitStrings(),
-                new UnicodeSet("[,]").freeze(),
-                new UnicodeSet("[.]").freeze(),
-                false);
+        String groupingSeparator = symbols.getGroupingSeparatorString();
+        UnicodeSet groupingSet = UNISET_COMMA_LIKE.contains(groupingSeparator) ? UNISET_COMMA_LIKE.cloneAsThawed().addAll(UNISET_OTHER_GROUPING_SEPARATORS).freeze()
+                : UNISET_PERIOD_LIKE.contains(groupingSeparator) ? UNISET_PERIOD_LIKE.cloneAsThawed().addAll(UNISET_OTHER_GROUPING_SEPARATORS).freeze()
+                        : UNISET_OTHER_GROUPING_SEPARATORS.contains(groupingSeparator)
+                                ? UNISET_OTHER_GROUPING_SEPARATORS
+                                : new UnicodeSet().addAll(groupingSeparator).freeze();
+
+        String decimalSeparator = symbols.getDecimalSeparatorString();
+        UnicodeSet decimalSet = UNISET_COMMA_LIKE.contains(decimalSeparator) ? UNISET_COMMA_LIKE
+                : UNISET_PERIOD_LIKE.contains(decimalSeparator) ? UNISET_PERIOD_LIKE
+                        : new UnicodeSet().addAll(decimalSeparator).freeze();
+
+        return new DecimalMatcher(symbols.getDigitStrings(), groupingSet, decimalSet, false);
     }
 
     public static DecimalMatcher getExponentInstance(DecimalFormatSymbols symbols) {
-        return new DecimalMatcher(symbols.getDigitStrings(),
-                new UnicodeSet("[,]").freeze(),
-                new UnicodeSet("[.]").freeze(),
-                true);
+        return new DecimalMatcher(symbols
+                .getDigitStrings(), new UnicodeSet("[,]").freeze(), new UnicodeSet("[.]").freeze(), true);
     }
 
     private final String[] digitStrings;
@@ -39,6 +54,7 @@ public class DecimalMatcher implements NumberParseMatcher {
     private final UnicodeSet decimalUniSet;
     private final UnicodeSet separatorSet;
     public boolean requireGroupingMatch = false;
+    public boolean groupingEnabled = true;
     private final int grouping1 = 3;
     private final int grouping2 = 3;
     private final boolean isScientific;
@@ -51,7 +67,11 @@ public class DecimalMatcher implements NumberParseMatcher {
         this.digitStrings = digitStrings;
         this.groupingUniSet = groupingUniSet;
         this.decimalUniSet = decimalUniSet;
-        separatorSet = groupingUniSet.cloneAsThawed().addAll(decimalUniSet).freeze();
+        if (groupingEnabled) {
+            separatorSet = groupingUniSet.cloneAsThawed().addAll(decimalUniSet).freeze();
+        } else {
+            separatorSet = decimalUniSet;
+        }
         this.isScientific = isScientific;
     }
 
@@ -65,6 +85,7 @@ public class DecimalMatcher implements NumberParseMatcher {
         int currGroup = 0;
         int separator = -1;
         int lastSeparatorOffset = segment.getOffset();
+        int scientificAdjustment = 0;
         boolean hasPartialPrefix = false;
         boolean seenBothSeparators = false;
         while (segment.length() > 0) {
@@ -97,7 +118,7 @@ public class DecimalMatcher implements NumberParseMatcher {
             // If found, save it in the DecimalQuantity or scientific adjustment.
             if (digit >= 0) {
                 if (isScientific) {
-                    result.scientificAdjustment = digit + result.scientificAdjustment * 10;
+                    scientificAdjustment = digit + scientificAdjustment * 10;
                 } else {
                     if (result.quantity == null) {
                         result.quantity = new DecimalQuantity_DualStorageBCD();
@@ -117,13 +138,13 @@ public class DecimalMatcher implements NumberParseMatcher {
                     if (requireGroupingMatch && currGroup == 0) {
                         break;
                     }
-                } else if (separator == cp && groupingUniSet.contains(cp)) {
+                } else if (groupingEnabled && separator == cp && groupingUniSet.contains(cp)) {
                     // Second or later grouping separator.
                     if (requireGroupingMatch && currGroup != grouping2) {
                         break;
                     }
-                } else if (separator != cp && decimalUniSet.contains(cp)) {
-                    // Decimal separator.
+                } else if (groupingEnabled && separator != cp && decimalUniSet.contains(cp)) {
+                    // Decimal separator after a grouping separator.
                     if (requireGroupingMatch && currGroup != grouping1) {
                         break;
                     }
@@ -141,10 +162,13 @@ public class DecimalMatcher implements NumberParseMatcher {
             break;
         }
 
-        if (seenBothSeparators || (separator != -1 && decimalUniSet.contains(separator))) {
+        if (isScientific) {
+            result.quantity.adjustMagnitude(scientificAdjustment);
+        } else if (seenBothSeparators || (separator != -1 && decimalUniSet.contains(separator))) {
             result.quantity.adjustMagnitude(-currGroup);
-        } else if (requireGroupingMatch && separator != -1 && groupingUniSet.contains(separator)
-                && currGroup != grouping1) {
+        } else if (separator != -1
+                && ((requireGroupingMatch && groupingUniSet.contains(separator) && currGroup != grouping1)
+                        || !groupingEnabled)) {
             result.quantity.adjustMagnitude(-currGroup);
             result.quantity.roundToMagnitude(0, RoundingUtils.mathContextUnlimited(RoundingMode.FLOOR));
             segment.setOffset(lastSeparatorOffset);
index b01f800c551774cdc50fc341fc115a81b535b99b..c15db2f808f920f8e62a3a247f7d433cc0f88e74 100644 (file)
@@ -2,14 +2,19 @@
 // License & terms of use: http://www.unicode.org/copyright.html#License
 package com.ibm.icu.impl.number.parse;
 
+import java.text.ParsePosition;
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.List;
 
 import com.ibm.icu.impl.number.AffixPatternProvider;
 import com.ibm.icu.impl.number.AffixUtils;
+import com.ibm.icu.impl.number.CustomSymbolCurrency;
+import com.ibm.icu.impl.number.DecimalFormatProperties;
 import com.ibm.icu.impl.number.MutablePatternModifier;
+import com.ibm.icu.impl.number.Parse.ParseMode;
 import com.ibm.icu.impl.number.PatternStringParser;
+import com.ibm.icu.impl.number.PropertiesAffixPatternProvider;
 import com.ibm.icu.number.NumberFormatter.SignDisplay;
 import com.ibm.icu.number.NumberFormatter.UnitWidth;
 import com.ibm.icu.text.DecimalFormatSymbols;
@@ -23,7 +28,9 @@ import com.ibm.icu.util.ULocale;
  *
  */
 public class NumberParserImpl {
-    public static NumberParserImpl createParserFromPattern(String pattern) {
+    public static NumberParserImpl createParserFromPattern(String pattern, boolean strictGrouping) {
+        // Temporary frontend for testing.
+
         NumberParserImpl parser = new NumberParserImpl();
         ULocale locale = ULocale.ENGLISH;
         DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);
@@ -32,10 +39,7 @@ public class NumberParserImpl {
         AffixPatternProvider provider = PatternStringParser.parseToPatternInfo(pattern);
         mod.setPatternInfo(provider);
         mod.setPatternAttributes(SignDisplay.AUTO, false);
-        mod.setSymbols(symbols,
-                Currency.getInstance("USD"),
-                UnitWidth.FULL_NAME,
-                null);
+        mod.setSymbols(symbols, Currency.getInstance("USD"), UnitWidth.FULL_NAME, null);
         int flags = 0;
         if (provider.containsSymbolType(AffixUtils.TYPE_PERCENT)) {
             flags |= ParsedNumber.FLAG_PERCENT;
@@ -45,18 +49,77 @@ public class NumberParserImpl {
         }
         AffixMatcher.generateFromPatternModifier(mod, flags, parser);
 
-        parser.addMatcher(DecimalMatcher.getInstance(symbols));
+        DecimalMatcher decimalMatcher = DecimalMatcher.getInstance(symbols);
+        decimalMatcher.requireGroupingMatch = strictGrouping;
+        parser.addMatcher(decimalMatcher);
         parser.addMatcher(WhitespaceMatcher.getInstance());
         parser.addMatcher(new MinusSignMatcher());
         parser.addMatcher(new ScientificMatcher(symbols));
         parser.addMatcher(new CurrencyMatcher(locale));
 
-        parser.setComparator(new Comparator<ParsedNumber>() {
-            @Override
-            public int compare(ParsedNumber o1, ParsedNumber o2) {
-                return o1.charsConsumed - o2.charsConsumed;
-            }
-        });
+        parser.freeze();
+        return parser;
+    }
+
+    public static Number parseStatic(String input,
+      ParsePosition ppos,
+      DecimalFormatProperties properties,
+      DecimalFormatSymbols symbols) {
+        NumberParserImpl parser = createParserFromProperties(properties, symbols);
+        ParsedNumber result = new ParsedNumber();
+        parser.parse(input, true, result);
+        ppos.setIndex(result.charsConsumed);
+        return result.getDouble();
+    }
+
+    public static NumberParserImpl createParserFromProperties(
+            DecimalFormatProperties properties,
+            DecimalFormatSymbols symbols) {
+        NumberParserImpl parser = new NumberParserImpl();
+        ULocale locale = symbols.getULocale();
+        Currency currency = CustomSymbolCurrency.resolve(properties.getCurrency(), locale, symbols);
+
+        //////////////////////
+        /// AFFIX MATCHERS ///
+        //////////////////////
+
+        // Set up a pattern modifier with mostly defaults to generate AffixMatchers.
+        MutablePatternModifier mod = new MutablePatternModifier(false);
+        AffixPatternProvider provider = new PropertiesAffixPatternProvider(properties);
+        mod.setPatternInfo(provider);
+        mod.setPatternAttributes(SignDisplay.AUTO, false);
+        mod.setSymbols(symbols, currency, UnitWidth.SHORT, null);
+
+        // Figure out which flags correspond to this pattern modifier. Note: negatives are taken care of in the
+        // generateFromPatternModifier function.
+        int flags = 0;
+        if (provider.containsSymbolType(AffixUtils.TYPE_PERCENT)) {
+            flags |= ParsedNumber.FLAG_PERCENT;
+        }
+        if (provider.containsSymbolType(AffixUtils.TYPE_PERMILLE)) {
+            flags |= ParsedNumber.FLAG_PERMILLE;
+        }
+
+        AffixMatcher.generateFromPatternModifier(mod, flags, parser);
+
+        ///////////////////////////////
+        /// OTHER STANDARD MATCHERS ///
+        ///////////////////////////////
+
+        DecimalMatcher decimalMatcher = DecimalMatcher.getInstance(symbols);
+        decimalMatcher.groupingEnabled = properties.getGroupingSize() > 0;
+        decimalMatcher.requireGroupingMatch = properties.getParseMode() == ParseMode.STRICT;
+        parser.addMatcher(decimalMatcher);
+        parser.addMatcher(WhitespaceMatcher.getInstance());
+        parser.addMatcher(new MinusSignMatcher());
+        parser.addMatcher(new ScientificMatcher(symbols));
+
+        ////////////////////////
+        /// CURRENCY MATCHER ///
+        ////////////////////////
+
+        parser.addMatcher(new CurrencyMatcher(locale));
+
         parser.freeze();
         return parser;
     }
@@ -67,6 +130,7 @@ public class NumberParserImpl {
 
     public NumberParserImpl() {
         matchers = new ArrayList<NumberParseMatcher>();
+        comparator = ParsedNumber.COMPARATOR; // default value
         frozen = false;
     }
 
index 3ab53f1aadf1dbce90947a7422bc24d95e38f807..ae847a28fd7addb777239f53662574ad2c23f025 100644 (file)
@@ -2,7 +2,8 @@
 // License & terms of use: http://www.unicode.org/copyright.html#License
 package com.ibm.icu.impl.number.parse;
 
-import com.ibm.icu.impl.number.DecimalQuantity;
+import java.util.Comparator;
+
 import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
 
 /**
@@ -12,17 +13,46 @@ import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
 public class ParsedNumber {
 
     public DecimalQuantity_DualStorageBCD quantity = null;
+
+    /**
+     * The number of chars accepted during parsing. This is NOT necessarily the same as the StringSegment offset; "weak"
+     * chars, like whitespace, change the offset, but the charsConsumed is not touched until a "strong" char is
+     * encountered.
+     */
     public int charsConsumed = 0;
+
+    /**
+     * Boolean flags (see constants below).
+     */
     public int flags = 0;
+
+    /**
+     * The prefix string that got consumed.
+     */
     public String prefix = null;
+
+    /**
+     * The suffix string that got consumed.
+     */
     public String suffix = null;
-    public int scientificAdjustment = 0;
+
+    /**
+     * The currency that got consumed.
+     */
     public String currencyCode = null;
 
     public static final int FLAG_NEGATIVE = 0x0001;
     public static final int FLAG_PERCENT = 0x0002;
     public static final int FLAG_PERMILLE = 0x0004;
 
+    /** A Comparator that favors ParsedNumbers with the most chars consumed. */
+    public static final Comparator<ParsedNumber> COMPARATOR = new Comparator<ParsedNumber>() {
+        @Override
+        public int compare(ParsedNumber o1, ParsedNumber o2) {
+            return o1.charsConsumed - o2.charsConsumed;
+        }
+    };
+
     /**
      * @param other
      */
@@ -32,7 +62,6 @@ public class ParsedNumber {
         flags = other.flags;
         prefix = other.prefix;
         suffix = other.suffix;
-        scientificAdjustment = other.scientificAdjustment;
         currencyCode = other.currencyCode;
     }
 
@@ -41,9 +70,7 @@ public class ParsedNumber {
     }
 
     public double getDouble() {
-        DecimalQuantity copy = quantity.createCopy();
-        copy.adjustMagnitude(scientificAdjustment);
-        double d = copy.toDouble();
+        double d = quantity.toDouble();
         if (0 != (flags & FLAG_NEGATIVE)) {
             d = -d;
         }
index 1fae1595be25cc73a1d60e51ac9d31bcb13c8700..2b4549289dffa34df14d1206637584e73764941e 100644 (file)
@@ -11,10 +11,12 @@ import com.ibm.icu.text.DecimalFormatSymbols;
 public class ScientificMatcher implements NumberParseMatcher {
 
     private final String exponentSeparatorString;
+    private final String minusSignString;
     private final DecimalMatcher exponentMatcher;
 
     public ScientificMatcher(DecimalFormatSymbols symbols) {
         exponentSeparatorString = symbols.getExponentSeparator();
+        minusSignString = symbols.getMinusSignString();
         exponentMatcher = DecimalMatcher.getExponentInstance(symbols);
     }
 
@@ -26,19 +28,33 @@ public class ScientificMatcher implements NumberParseMatcher {
         }
 
         // First match the scientific separator, and then match another number after it.
-        int overlap = segment.getCommonPrefixLength(exponentSeparatorString);
-        if (overlap == exponentSeparatorString.length()) {
-            // Full exponent separator match; try to match digits.
-            segment.adjustOffset(overlap);
+        int overlap1 = segment.getCommonPrefixLength(exponentSeparatorString);
+        if (overlap1 == exponentSeparatorString.length()) {
+            // Full exponent separator match; allow a sign, and then try to match digits.
+            segment.adjustOffset(overlap1);
+            int overlap2 = segment.getCommonPrefixLength(minusSignString);
+            boolean sign = false;
+            if (overlap2 == minusSignString.length()) {
+                sign = true;
+                segment.adjustOffset(overlap2);
+            } else if (overlap2 == segment.length()) {
+                // Partial sign match
+                return true;
+            }
+
             int digitsOffset = segment.getOffset();
+            int oldMagnitude = result.quantity.getMagnitude();
             boolean digitsReturnValue = exponentMatcher.match(segment, result);
+            if (result.quantity.getMagnitude() != oldMagnitude && sign) {
+                result.quantity.adjustMagnitude(2*(oldMagnitude - result.quantity.getMagnitude()));
+            }
             if (segment.getOffset() == digitsOffset) {
                 // No digits were matched; un-match the exponent separator.
-                segment.adjustOffset(-overlap);
+                segment.adjustOffset(-overlap1);
             }
             return digitsReturnValue;
 
-        } else if (overlap == segment.length()) {
+        } else if (overlap1 == segment.length()) {
             // Partial exponent separator match
             return true;
         }
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/StrictMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/StrictMatcher.java
new file mode 100644 (file)
index 0000000..0f9b390
--- /dev/null
@@ -0,0 +1,23 @@
+// ยฉ 2017 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html#License
+package com.ibm.icu.impl.number.parse;
+
+/**
+ * @author sffc
+ *
+ */
+public class StrictMatcher implements NumberParseMatcher {
+
+    @Override
+    public boolean match(StringSegment segment, ParsedNumber result) {
+        return false;
+    }
+
+    @Override
+    public void postProcess(ParsedNumber result) {
+        if (result.prefix == null && result.suffix == null) {
+            // Do something?
+        }
+    }
+
+}
index f1cdafe1dec9663ea86fadd178ea892ee0ac056b..c07d88b7705e97917fd49936f66cdbc6030fb864 100644 (file)
@@ -5,16 +5,15 @@ package com.ibm.icu.number;
 import java.math.BigDecimal;
 import java.math.MathContext;
 
-import com.ibm.icu.impl.StandardPlural;
 import com.ibm.icu.impl.number.AffixPatternProvider;
-import com.ibm.icu.impl.number.AffixUtils;
+import com.ibm.icu.impl.number.CurrencyPluralInfoAffixProvider;
 import com.ibm.icu.impl.number.CustomSymbolCurrency;
 import com.ibm.icu.impl.number.DecimalFormatProperties;
 import com.ibm.icu.impl.number.MacroProps;
 import com.ibm.icu.impl.number.MultiplierImpl;
 import com.ibm.icu.impl.number.Padder;
 import com.ibm.icu.impl.number.PatternStringParser;
-import com.ibm.icu.impl.number.PatternStringParser.ParsedPatternInfo;
+import com.ibm.icu.impl.number.PropertiesAffixPatternProvider;
 import com.ibm.icu.impl.number.RoundingUtils;
 import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
 import com.ibm.icu.number.NumberFormatter.SignDisplay;
@@ -22,7 +21,6 @@ import com.ibm.icu.number.Rounder.FractionRounderImpl;
 import com.ibm.icu.number.Rounder.IncrementRounderImpl;
 import com.ibm.icu.number.Rounder.SignificantRounderImpl;
 import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
-import com.ibm.icu.text.CurrencyPluralInfo;
 import com.ibm.icu.text.DecimalFormatSymbols;
 import com.ibm.icu.util.Currency;
 import com.ibm.icu.util.Currency.CurrencyUsage;
@@ -60,7 +58,7 @@ final class NumberPropertyMapper {
      * @param symbols
      *            The symbols associated with the property bag.
      * @param exportedProperties
-     *            A property bag in which to store validated properties.
+     *            A property bag in which to store validated properties. Used by some DecimalFormat getters.
      * @return A new MacroProps containing all of the information in the Properties.
      */
     public static MacroProps oldToNew(DecimalFormatProperties properties, DecimalFormatSymbols symbols,
@@ -334,178 +332,4 @@ final class NumberPropertyMapper {
 
         return macros;
     }
-
-    private static class PropertiesAffixPatternProvider implements AffixPatternProvider {
-        private final String posPrefix;
-        private final String posSuffix;
-        private final String negPrefix;
-        private final String negSuffix;
-
-        public PropertiesAffixPatternProvider(DecimalFormatProperties properties) {
-            // There are two ways to set affixes in DecimalFormat: via the pattern string (applyPattern), and via the
-            // explicit setters (setPositivePrefix and friends).  The way to resolve the settings is as follows:
-            //
-            // 1) If the explicit setting is present for the field, use it.
-            // 2) Otherwise, follows UTS 35 rules based on the pattern string.
-            //
-            // Importantly, the explicit setters affect only the one field they override.  If you set the positive
-            // prefix, that should not affect the negative prefix.  Since it is impossible for the user of this class
-            // to know whether the origin for a string was the override or the pattern, we have to say that we always
-            // have a negative subpattern and perform all resolution logic here.
-
-            // Convenience: Extract the properties into local variables.
-            // Variables are named with three chars: [p/n][p/s][o/p]
-            // [p/n] => p for positive, n for negative
-            // [p/s] => p for prefix, s for suffix
-            // [o/p] => o for escaped custom override string, p for pattern string
-            String ppo = AffixUtils.escape(properties.getPositivePrefix());
-            String pso = AffixUtils.escape(properties.getPositiveSuffix());
-            String npo = AffixUtils.escape(properties.getNegativePrefix());
-            String nso = AffixUtils.escape(properties.getNegativeSuffix());
-            String ppp = properties.getPositivePrefixPattern();
-            String psp = properties.getPositiveSuffixPattern();
-            String npp = properties.getNegativePrefixPattern();
-            String nsp = properties.getNegativeSuffixPattern();
-
-            if (ppo != null) {
-                posPrefix = ppo;
-            } else if (ppp != null) {
-                posPrefix = ppp;
-            } else {
-                // UTS 35: Default positive prefix is empty string.
-                posPrefix = "";
-            }
-
-            if (pso != null) {
-                posSuffix = pso;
-            } else if (psp != null) {
-                posSuffix = psp;
-            } else {
-                // UTS 35: Default positive suffix is empty string.
-                posSuffix = "";
-            }
-
-            if (npo != null) {
-                negPrefix = npo;
-            } else if (npp != null) {
-                negPrefix = npp;
-            } else {
-                // UTS 35: Default negative prefix is "-" with positive prefix.
-                // Important: We prepend the "-" to the pattern, not the override!
-                negPrefix = ppp == null ? "-" : "-" + ppp;
-            }
-
-            if (nso != null) {
-                negSuffix = nso;
-            } else if (nsp != null) {
-                negSuffix = nsp;
-            } else {
-                // UTS 35: Default negative prefix is the positive prefix.
-                negSuffix = psp == null ? "" : psp;
-            }
-        }
-
-        @Override
-        public char charAt(int flags, int i) {
-            return getStringForFlags(flags).charAt(i);
-        }
-
-        @Override
-        public int length(int flags) {
-            return getStringForFlags(flags).length();
-        }
-
-        private String getStringForFlags(int flags) {
-            boolean prefix = (flags & Flags.PREFIX) != 0;
-            boolean negative = (flags & Flags.NEGATIVE_SUBPATTERN) != 0;
-            if (prefix && negative) {
-                return negPrefix;
-            } else if (prefix) {
-                return posPrefix;
-            } else if (negative) {
-                return negSuffix;
-            } else {
-                return posSuffix;
-            }
-        }
-
-        @Override
-        public boolean positiveHasPlusSign() {
-            return AffixUtils.containsType(posPrefix, AffixUtils.TYPE_PLUS_SIGN)
-                    || AffixUtils.containsType(posSuffix, AffixUtils.TYPE_PLUS_SIGN);
-        }
-
-        @Override
-        public boolean hasNegativeSubpattern() {
-            // See comments in the constructor for more information on why this is always true.
-            return true;
-        }
-
-        @Override
-        public boolean negativeHasMinusSign() {
-            return AffixUtils.containsType(negPrefix, AffixUtils.TYPE_MINUS_SIGN)
-                    || AffixUtils.containsType(negSuffix, AffixUtils.TYPE_MINUS_SIGN);
-        }
-
-        @Override
-        public boolean hasCurrencySign() {
-            return AffixUtils.hasCurrencySymbols(posPrefix) || AffixUtils.hasCurrencySymbols(posSuffix)
-                    || AffixUtils.hasCurrencySymbols(negPrefix) || AffixUtils.hasCurrencySymbols(negSuffix);
-        }
-
-        @Override
-        public boolean containsSymbolType(int type) {
-            return AffixUtils.containsType(posPrefix, type) || AffixUtils.containsType(posSuffix, type)
-                    || AffixUtils.containsType(negPrefix, type) || AffixUtils.containsType(negSuffix, type);
-        }
-    }
-
-    private static class CurrencyPluralInfoAffixProvider implements AffixPatternProvider {
-        private final AffixPatternProvider[] affixesByPlural;
-
-        public CurrencyPluralInfoAffixProvider(CurrencyPluralInfo cpi) {
-            affixesByPlural = new ParsedPatternInfo[StandardPlural.COUNT];
-            for (StandardPlural plural : StandardPlural.VALUES) {
-                affixesByPlural[plural.ordinal()] = PatternStringParser
-                        .parseToPatternInfo(cpi.getCurrencyPluralPattern(plural.getKeyword()));
-            }
-        }
-
-        @Override
-        public char charAt(int flags, int i) {
-            int pluralOrdinal = (flags & Flags.PLURAL_MASK);
-            return affixesByPlural[pluralOrdinal].charAt(flags, i);
-        }
-
-        @Override
-        public int length(int flags) {
-            int pluralOrdinal = (flags & Flags.PLURAL_MASK);
-            return affixesByPlural[pluralOrdinal].length(flags);
-        }
-
-        @Override
-        public boolean positiveHasPlusSign() {
-            return affixesByPlural[StandardPlural.OTHER.ordinal()].positiveHasPlusSign();
-        }
-
-        @Override
-        public boolean hasNegativeSubpattern() {
-            return affixesByPlural[StandardPlural.OTHER.ordinal()].hasNegativeSubpattern();
-        }
-
-        @Override
-        public boolean negativeHasMinusSign() {
-            return affixesByPlural[StandardPlural.OTHER.ordinal()].negativeHasMinusSign();
-        }
-
-        @Override
-        public boolean hasCurrencySign() {
-            return affixesByPlural[StandardPlural.OTHER.ordinal()].hasCurrencySign();
-        }
-
-        @Override
-        public boolean containsSymbolType(int type) {
-            return affixesByPlural[StandardPlural.OTHER.ordinal()].containsSymbolType(type);
-        }
-    }
 }
index 113473a2a57f849223ac41cd03261489f605482e..96868920c68aa29b3f880ed9ad948ad57fcaf5d4 100644 (file)
@@ -441,11 +441,11 @@ en_US     1       123,456 123456
 en_US  0       123,456 123
 en_US  1       123.456 123.456
 en_US  0       123.456 123.456
-fr_FR  1       123,456 123.456
-fr_FR  0       123,456 123.456
+it_IT  1       123,456 123.456
+it_IT  0       123,456 123.456
 // JDK returns 123 here; not sure why.
-fr_FR  1       123.456 123456  K
-fr_FR  0       123.456 123
+it_IT  1       123.456 123456  K
+it_IT  0       123.456 123
 
 test no grouping in pattern with parsing
 set pattern 0
@@ -736,7 +736,7 @@ parse       output  breaks
 (5,347.25)     -5347.25
 // J requires prefix and suffix for lenient parsing, but C doesn't
 5,347.25       5347.25 JK
-(5,347.25      -5347.25        J
+(5,347.25      -5347.25        JP
 // S is successful at parsing this as -5347.25 in lenient mode
 -5,347.25      -5347.25        CJK
 +3.52E4        35200
@@ -750,7 +750,8 @@ parse       output  breaks
 (34,,25 E-1)   -342.5  CJK
 // Spaces are not allowed after exponent symbol
 // C parses up to the E but J bails
-(34  25E -1)   -3425   JK
+// P does not make the number negative
+(34  25E -1)   -3425   JKP
 +3.52EE4       3.52
 +1,234,567.8901        1234567.8901
 +1,23,4567.8901        1234567.8901
@@ -775,18 +776,19 @@ parse     output  breaks
 (  19 45 )     -1945   JK
 (,,19,45)      -1945
 // C parses to the space, but J bails
-(,,19 45)      -19     J
+// P makes the number positive
+(,,19 45)      -19     JP
 // J bails b/c comma different separator than space. C doesn't treat leading spaces
 // as a separator.
-(  19,45)      -1945   JK
+(  19,45)      -1945   JKP
 // J bails. Doesn't allow trailing separators when there is prefix and suffix.
 (,,19,45,)     -1945   J
 // J bails on next 4 because J doesn't allow letters inside prefix and suffix.
 // C will parse up to the letter.
-(,,19,45,d1)   -1945   J
-(,,19,45d1)    -1945   J
-(  19 45 d1)   -1945   JK
-(  19 45d1)    -1945   JK
+(,,19,45,d1)   -1945   JP
+(,,19,45d1)    -1945   JP
+(  19 45 d1)   -1945   JKP
+(  19 45d1)    -1945   JKP
 // J does allow trailing separator before a decimal point
 (19,45,.25)    -1945.25
 // 2nd decimal points are ignored
@@ -829,7 +831,7 @@ parse       output  breaks
 (65347.25)     -65347.25
 (65,347.25)    -65347.25
 // JDK does allow separators in the wrong place and parses as -5347.25
-(53,47.25)     fail    K
+(53,47.25)     fail    KP
 // strict requires prefix or suffix, except in C
 65,347.25      fail    
 +3.52E4        35200
index 6cd17dfe19e317e582b98d87c9d953d42304b1f3..2f5b23a21a662457d966afd438793524007aca56 100644 (file)
@@ -7,6 +7,7 @@ import java.math.RoundingMode;
 import java.text.ParseException;
 import java.text.ParsePosition;
 
+import org.junit.Ignore;
 import org.junit.Test;
 
 import com.ibm.icu.dev.test.TestUtil;
@@ -16,6 +17,7 @@ import com.ibm.icu.impl.number.Parse;
 import com.ibm.icu.impl.number.Parse.ParseMode;
 import com.ibm.icu.impl.number.PatternStringParser;
 import com.ibm.icu.impl.number.PatternStringUtils;
+import com.ibm.icu.impl.number.parse.NumberParserImpl;
 import com.ibm.icu.number.LocalizedNumberFormatter;
 import com.ibm.icu.number.NumberFormatter;
 import com.ibm.icu.text.DecimalFormat;
@@ -573,6 +575,100 @@ public class NumberFormatDataDrivenTest {
         }
       };
 
+      /**
+       * Parsing, but no other features.
+       */
+      private DataDrivenNumberFormatTestUtility.CodeUnderTest ICU60_Parsing =
+          new DataDrivenNumberFormatTestUtility.CodeUnderTest() {
+
+            @Override
+            public Character Id() {
+              return 'P';
+            }
+
+            @Override
+            public String parse(DataDrivenNumberFormatTestData tuple) {
+                String pattern = (tuple.pattern == null) ? "0" : tuple.pattern;
+                DecimalFormatProperties properties;
+                ParsePosition ppos = new ParsePosition(0);
+                Number actual;
+                try {
+                    properties = PatternStringParser.parseToProperties(pattern,
+                            tuple.currency != null ? PatternStringParser.IGNORE_ROUNDING_ALWAYS
+                                    : PatternStringParser.IGNORE_ROUNDING_NEVER);
+                    propertiesFromTuple(tuple, properties);
+                    actual = NumberParserImpl.parseStatic(tuple.parse,
+                            ppos,
+                            properties,
+                            DecimalFormatSymbols.getInstance(tuple.locale));
+                } catch (IllegalArgumentException e) {
+                    return "parse exception: " + e.getMessage();
+                }
+                if (actual == null && ppos.getIndex() != 0) {
+                    throw new AssertionError("Error: value is null but parse position is not zero");
+                }
+                if (ppos.getIndex() == 0) {
+                    return "Parse failed; got " + actual + ", but expected " + tuple.output;
+                }
+                if (tuple.output.equals("NaN")) {
+                    if (!Double.isNaN(actual.doubleValue())) {
+                        return "Expected NaN, but got: " + actual;
+                    }
+                    return null;
+                } else if (tuple.output.equals("Inf")) {
+                    if (!Double.isInfinite(actual.doubleValue()) || Double.compare(actual.doubleValue(), 0.0) < 0) {
+                        return "Expected Inf, but got: " + actual;
+                    }
+                    return null;
+                } else if (tuple.output.equals("-Inf")) {
+                    if (!Double.isInfinite(actual.doubleValue()) || Double.compare(actual.doubleValue(), 0.0) > 0) {
+                        return "Expected -Inf, but got: " + actual;
+                    }
+                    return null;
+                } else if (tuple.output.equals("fail")) {
+                    return null;
+                } else if (new BigDecimal(tuple.output).compareTo(new BigDecimal(actual.toString())) != 0) {
+                    return "Expected: " + tuple.output + ", got: " + actual;
+                } else {
+                    return null;
+                }
+            }
+
+//            @Override
+//            public String parseCurrency(DataDrivenNumberFormatTestData tuple) {
+//                String pattern = (tuple.pattern == null) ? "0" : tuple.pattern;
+//                DecimalFormatProperties properties;
+//                ParsePosition ppos = new ParsePosition(0);
+//                CurrencyAmount actual;
+//                try {
+//                    properties = PatternStringParser.parseToProperties(
+//                            pattern,
+//                            tuple.currency != null ? PatternStringParser.IGNORE_ROUNDING_ALWAYS
+//                                    : PatternStringParser.IGNORE_ROUNDING_NEVER);
+//                    propertiesFromTuple(tuple, properties);
+//                    actual = NumberParserImpl.parseStatic(tuple.parse,
+//                            ppos,
+//                            properties,
+//                            DecimalFormatSymbols.getInstance(tuple.locale));
+//                } catch (ParseException e) {
+//                    e.printStackTrace();
+//                    return "parse exception: " + e.getMessage();
+//                }
+//                if (ppos.getIndex() == 0 || actual.getCurrency().getCurrencyCode().equals("XXX")) {
+//                    return "Parse failed; got " + actual + ", but expected " + tuple.output;
+//                }
+//                BigDecimal expectedNumber = new BigDecimal(tuple.output);
+//                if (expectedNumber.compareTo(new BigDecimal(actual.getNumber().toString())) != 0) {
+//                    return "Wrong number: Expected: " + expectedNumber + ", got: " + actual;
+//                }
+//                String expectedCurrency = tuple.outputCurrency;
+//                if (!expectedCurrency.equals(actual.getCurrency().toString())) {
+//                    return "Wrong currency: Expected: " + expectedCurrency + ", got: " + actual;
+//                }
+//                return null;
+//            }
+      };
+
     /**
      * All features except formatting.
      */
@@ -738,6 +834,7 @@ public class NumberFormatDataDrivenTest {
     };
 
   @Test
+  @Ignore
   public void TestDataDrivenICU58() {
     // Android can't access DecimalFormat_ICU58 for testing (ticket #13283).
     if (TestUtil.getJavaVendor() == TestUtil.JavaVendor.Android) return;
@@ -750,6 +847,7 @@ public class NumberFormatDataDrivenTest {
   // something may or may not work. However the test data assumes a specific
   // Java runtime version. We should probably disable this test case - #13372
   @Test
+  @Ignore
   public void TestDataDrivenJDK() {
     // Android implements java.text.DecimalFormat with ICU4J (ticket #13322).
     // Oracle/OpenJDK 9's behavior is not exactly same with Oracle/OpenJDK 8.
@@ -764,12 +862,20 @@ public class NumberFormatDataDrivenTest {
   }
 
   @Test
+  @Ignore
   public void TestDataDrivenICULatest_Format() {
     DataDrivenNumberFormatTestUtility.runFormatSuiteIncludingKnownFailures(
         "numberformattestspecification.txt", ICU60);
   }
 
   @Test
+  public void TestDataDrivenICULatest_Parsing() {
+    DataDrivenNumberFormatTestUtility.runFormatSuiteIncludingKnownFailures(
+        "numberformattestspecification.txt", ICU60_Parsing);
+  }
+
+  @Test
+  @Ignore
   public void TestDataDrivenICULatest_Other() {
     DataDrivenNumberFormatTestUtility.runFormatSuiteIncludingKnownFailures(
         "numberformattestspecification.txt", ICU60_Other);
index 0c9fdf3a7dd3818f73f473e7e65ed9f0bbb6b2d4..fcc58aedae4cfcbdaddb7c6d207163fe85c3df5e 100644 (file)
@@ -22,6 +22,7 @@ public class NumberParserTest {
                 // a) Flags:
                 // --- Bit 0x01 => Test greedy implementation
                 // --- Bit 0x02 => Test slow implementation
+                // --- Bit 0x04 => Test strict grouping separators
                 // b) Input string
                 // c) Pattern
                 // d) Expected chars consumed
@@ -34,13 +35,13 @@ public class NumberParserTest {
                 { 3, "๐Ÿฑ๐Ÿญ๐Ÿฐ๐Ÿฎ๐Ÿฏx", "0", 10, 51423. },
                 { 3, " ๐Ÿฑ๐Ÿญ๐Ÿฐ๐Ÿฎ๐Ÿฏ", "0", 11, 51423. },
                 { 3, "๐Ÿฑ๐Ÿญ๐Ÿฐ๐Ÿฎ๐Ÿฏ ", "0", 10, 51423. },
-                { 3, "๐Ÿฑ๐Ÿญ,๐Ÿฐ๐Ÿฎ๐Ÿฏ", "0", 11, 51423. },
-                { 3, "๐Ÿณ๐Ÿด,๐Ÿต๐Ÿฑ๐Ÿญ,๐Ÿฐ๐Ÿฎ๐Ÿฏ", "0", 18, 78951423. },
-                { 3, "๐Ÿณ๐Ÿด,๐Ÿต๐Ÿฑ๐Ÿญ.๐Ÿฐ๐Ÿฎ๐Ÿฏ", "0", 18, 78951.423 },
-                { 3, "๐Ÿณ๐Ÿด,๐Ÿฌ๐Ÿฌ๐Ÿฌ", "0", 11, 78000. },
-                { 3, "๐Ÿณ๐Ÿด,๐Ÿฌ๐Ÿฌ๐Ÿฌ.๐Ÿฌ๐Ÿฌ๐Ÿฌ", "0", 18, 78000. },
-                { 3, "๐Ÿณ๐Ÿด,๐Ÿฌ๐Ÿฌ๐Ÿฌ.๐Ÿฌ๐Ÿฎ๐Ÿฏ", "0", 18, 78000.023 },
-                { 3, "๐Ÿณ๐Ÿด.๐Ÿฌ๐Ÿฌ๐Ÿฌ.๐Ÿฌ๐Ÿฎ๐Ÿฏ", "0", 11, 78. },
+                { 7, "๐Ÿฑ๐Ÿญ,๐Ÿฐ๐Ÿฎ๐Ÿฏ", "0", 11, 51423. },
+                { 7, "๐Ÿณ๐Ÿด,๐Ÿต๐Ÿฑ๐Ÿญ,๐Ÿฐ๐Ÿฎ๐Ÿฏ", "0", 18, 78951423. },
+                { 7, "๐Ÿณ๐Ÿด,๐Ÿต๐Ÿฑ๐Ÿญ.๐Ÿฐ๐Ÿฎ๐Ÿฏ", "0", 18, 78951.423 },
+                { 7, "๐Ÿณ๐Ÿด,๐Ÿฌ๐Ÿฌ๐Ÿฌ", "0", 11, 78000. },
+                { 7, "๐Ÿณ๐Ÿด,๐Ÿฌ๐Ÿฌ๐Ÿฌ.๐Ÿฌ๐Ÿฌ๐Ÿฌ", "0", 18, 78000. },
+                { 7, "๐Ÿณ๐Ÿด,๐Ÿฌ๐Ÿฌ๐Ÿฌ.๐Ÿฌ๐Ÿฎ๐Ÿฏ", "0", 18, 78000.023 },
+                { 7, "๐Ÿณ๐Ÿด.๐Ÿฌ๐Ÿฌ๐Ÿฌ.๐Ÿฌ๐Ÿฎ๐Ÿฏ", "0", 11, 78. },
                 { 3, "-๐Ÿฑ๐Ÿญ๐Ÿฐ๐Ÿฎ๐Ÿฏ", "0", 11, -51423. },
                 { 3, "-๐Ÿฑ๐Ÿญ๐Ÿฐ๐Ÿฎ๐Ÿฏ-", "0", 11, -51423. },
                 { 3, "a51423US dollars", "a0ยคยคยค", 16, 51423. },
@@ -51,6 +52,7 @@ public class NumberParserTest {
                 { 1, "a40b", "a0'0b'", 3, 40. }, // greedy code path thinks "40" is the number
                 { 2, "a40b", "a0'0b'", 4, 4. }, // slow code path find the suffix "0b"
                 { 3, "๐Ÿฑ.๐Ÿญ๐Ÿฐ๐ŸฎE๐Ÿฏ", "0", 12, 5142. },
+                { 3, "๐Ÿฑ.๐Ÿญ๐Ÿฐ๐ŸฎE-๐Ÿฏ", "0", 13, 0.005142 },
                 { 3, "5,142.50 Canadian dollars", "0", 25, 5142.5 },
                 { 3, "0", "0", 1, 0.0 } };
 
@@ -60,7 +62,7 @@ public class NumberParserTest {
             String pattern = (String) cas[2];
             int expectedCharsConsumed = (Integer) cas[3];
             double resultDouble = (Double) cas[4];
-            NumberParserImpl parser = NumberParserImpl.createParserFromPattern(pattern);
+            NumberParserImpl parser = NumberParserImpl.createParserFromPattern(pattern, false);
             String message = "Input <" + input + "> Parser " + parser;
 
             if (0 != (flags & 0x01)) {
@@ -80,6 +82,16 @@ public class NumberParserTest {
                 assertEquals(message, resultDouble, resultObject.getDouble(), 0.0);
                 assertEquals(message, expectedCharsConsumed, resultObject.charsConsumed);
             }
+
+            if (0 != (flags & 0x04)) {
+                // Test with strict separators
+                parser = NumberParserImpl.createParserFromPattern(pattern, true);
+                ParsedNumber resultObject = new ParsedNumber();
+                parser.parse(input, true, resultObject);
+                assertNotNull(message, resultObject.quantity);
+                assertEquals(message, resultDouble, resultObject.getDouble(), 0.0);
+                assertEquals(message, expectedCharsConsumed, resultObject.charsConsumed);
+            }
         }
     }
 }