]> granicus.if.org Git - icu/commitdiff
ICU-13634 Fixing resolution of negative and percent signs in parsing; adding custom...
authorShane Carr <shane@unicode.org>
Sat, 31 Mar 2018 03:10:44 +0000 (03:10 +0000)
committerShane Carr <shane@unicode.org>
Sat, 31 Mar 2018 03:10:44 +0000 (03:10 +0000)
X-SVN-Rev: 41180

24 files changed:
icu4c/source/i18n/decimfmt.cpp
icu4c/source/i18n/number_decimalquantity.cpp
icu4c/source/i18n/number_decimalquantity.h
icu4c/source/i18n/numparse_affixes.cpp
icu4c/source/i18n/numparse_impl.cpp
icu4c/source/i18n/numparse_parsednumber.cpp
icu4c/source/i18n/numparse_scientific.cpp
icu4c/source/i18n/numparse_scientific.h
icu4c/source/i18n/numparse_symbols.cpp
icu4c/source/i18n/numparse_symbols.h
icu4c/source/i18n/numparse_types.h
icu4c/source/test/intltest/numfmtst.cpp
icu4c/source/test/testdata/numberformattestspecification.txt
icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.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/PercentMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PermilleMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ScientificMatcher.java
icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java

index b15b962bf84f5b4081a5e0600847f1e18631a941..569a2a8a7b8689c1a0a2cf0d9ea54da2b51d4d10 100644 (file)
@@ -298,6 +298,7 @@ int32_t DecimalFormat::getAttribute(UNumberFormatAttribute attr, UErrorCode& sta
 }
 
 void DecimalFormat::setGroupingUsed(UBool enabled) {
+    NumberFormat::setGroupingUsed(enabled); // to set field for compatibility
     if (enabled) {
         // Set to a reasonable default value
         fProperties->groupingSize = 3;
@@ -649,6 +650,7 @@ ERoundingMode DecimalFormat::getRoundingMode(void) const {
 }
 
 void DecimalFormat::setRoundingMode(ERoundingMode roundingMode) {
+    NumberFormat::setMaximumIntegerDigits(roundingMode); // to set field for compatibility
     fProperties->roundingMode = static_cast<UNumberFormatRoundingMode>(roundingMode);
     refreshFormatterNoError();
 }
@@ -985,6 +987,12 @@ void DecimalFormat::refreshFormatter(UErrorCode& status) {
     fParserWithCurrency.adoptInsteadAndCheckErrorCode(
             NumberParserImpl::createParserFromProperties(
                     *fProperties, *fSymbols, true, status), status);
+
+    // In order for the getters to work, we need to populate some fields in NumberFormat.
+    NumberFormat::setMaximumIntegerDigits(fExportedProperties->maximumIntegerDigits);
+    NumberFormat::setMinimumIntegerDigits(fExportedProperties->minimumIntegerDigits);
+    NumberFormat::setMaximumFractionDigits(fExportedProperties->maximumFractionDigits);
+    NumberFormat::setMinimumFractionDigits(fExportedProperties->minimumFractionDigits);
 }
 
 void DecimalFormat::refreshFormatterNoError() {
index bb731938f1212965fb4b8ced5d52e8f6c2ef63e1..40ad848ff4e4045857df77e2b6aecdc437950776 100644 (file)
@@ -485,7 +485,9 @@ int64_t DecimalQuantity::toLong() const {
     for (int32_t magnitude = scale + precision - 1; magnitude >= 0; magnitude--) {
         result = result * 10 + getDigitPos(magnitude - scale);
     }
-    if (isNegative()) { result = -result; }
+    if (isNegative()) {
+        result = -result;
+    }
     return result;
 }
 
@@ -544,7 +546,9 @@ double DecimalQuantity::toDouble() const {
     UnicodeString numberString = toNumberString();
     int32_t count;
     double result = converter.StringToDouble(reinterpret_cast<const uint16_t*>(numberString.getBuffer()), numberString.length(), &count);
-    if (isNegative()) { result = -result; }
+    if (isNegative()) {
+        result = -result;
+    }
     return result;
 }
 
@@ -560,7 +564,9 @@ double DecimalQuantity::toDoubleFromOriginal() const {
         for (; delta <= -22; delta += 22) result /= 1e22;
         result /= DOUBLE_MULTIPLIERS[-delta];
     }
-    if (isNegative()) { result *= -1; }
+    if (isNegative()) {
+        result = -result;
+    }
     return result;
 }
 
@@ -1080,6 +1086,9 @@ UnicodeString DecimalQuantity::toString() const {
 
 UnicodeString DecimalQuantity::toNumberString() const {
     UnicodeString result;
+    if (precision == 0) {
+        result.append(u'0');
+    }
     for (int32_t i = 0; i < precision; i++) {
         result.append(u'0' + getDigitPos(precision - i - 1));
     }
index 74c85248c4a9f033fe44064482e078cbec889d4d..b205778e19a251ee26754a0bcd96c326479f121f 100644 (file)
@@ -95,7 +95,7 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory {
      */
     void multiplyBy(int32_t multiplicand);
 
-    /** Flips the sign from positive to negative and back. C++-only: not currently needed in Java. */
+    /** Flips the sign from positive to negative and back. */
     void negate();
 
     /**
index 159d70dcda9e6fe8ca34236263d75b229166d03b..83ecdd144b8f633c41bca615e3699491508aad71 100644 (file)
@@ -340,7 +340,7 @@ void AffixMatcherWarehouse::createAffixMatchers(const AffixPatternProvider& patt
             continue;
         }
 
-        // Flags for setting in the ParsedNumber
+        // Flags for setting in the ParsedNumber; the token matchers may add more.
         int flags = (signum == -1) ? FLAG_NEGATIVE : 0;
 
         // Note: it is indeed possible for posPrefix and posSuffix to both be null.
@@ -438,6 +438,12 @@ void AffixMatcher::postProcess(ParsedNumber& result) const {
             result.suffix = UnicodeString();
         }
         result.flags |= fFlags;
+        if (fPrefix != nullptr) {
+            fPrefix->postProcess(result);
+        }
+        if (fSuffix != nullptr) {
+            fSuffix->postProcess(result);
+        }
     }
 }
 
index c528d8efa82107e3871d89b18412cb818524e931..36ddc1f2f191f5da19a7c51e78ea8ba2ed1d134d 100644 (file)
@@ -233,6 +233,7 @@ void NumberParserImpl::parse(const UnicodeString& input, int32_t start, bool gre
     for (int32_t i = 0; i < fNumMatchers; i++) {
         fMatchers[i]->postProcess(result);
     }
+    result.postProcess();
 }
 
 void NumberParserImpl::parseGreedyRecursive(StringSegment& segment, ParsedNumber& result,
index 16da923459e5df4b438c5b72b8906189aebded2f..c658fc27d3e0a8e6390d3cef876962358bedd534 100644 (file)
@@ -37,6 +37,18 @@ void ParsedNumber::setCharsConsumed(const StringSegment& segment) {
     charEnd = segment.getOffset();
 }
 
+void ParsedNumber::postProcess() {
+    if (!quantity.bogus && 0 != (flags & FLAG_NEGATIVE)) {
+        quantity.negate();
+    }
+    if (!quantity.bogus && 0 != (flags & FLAG_PERCENT)) {
+        quantity.adjustMagnitude(-2);
+    }
+    if (!quantity.bogus && 0 != (flags & FLAG_PERMILLE)) {
+        quantity.adjustMagnitude(-3);
+    }
+}
+
 bool ParsedNumber::success() const {
     return charEnd > 0 && 0 == (flags & FLAG_FAIL);
 }
@@ -46,7 +58,6 @@ bool ParsedNumber::seenNumber() const {
 }
 
 double ParsedNumber::getDouble() const {
-    bool sawNegative = 0 != (flags & FLAG_NEGATIVE);
     bool sawNaN = 0 != (flags & FLAG_NAN);
     bool sawInfinity = 0 != (flags & FLAG_INFINITY);
 
@@ -55,34 +66,25 @@ double ParsedNumber::getDouble() const {
         return NAN;
     }
     if (sawInfinity) {
-        if (sawNegative) {
+        if (0 != (flags & FLAG_NEGATIVE)) {
             return -INFINITY;
         } else {
             return INFINITY;
         }
     }
-    if (quantity.isZero() && sawNegative) {
+    U_ASSERT(!quantity.bogus);
+    if (quantity.isZero() && quantity.isNegative()) {
         return -0.0;
     }
 
     if (quantity.fitsInLong()) {
-        long l = quantity.toLong();
-        if (0 != (flags & FLAG_NEGATIVE)) {
-            l *= -1;
-        }
-        return l;
-    }
-
-    // TODO: MIN_LONG. It is supported in quantity.toLong() if quantity had the negative flag.
-    double d = quantity.toDouble();
-    if (0 != (flags & FLAG_NEGATIVE)) {
-        d *= -1;
+        return quantity.toLong();
+    } else {
+        return quantity.toDouble();
     }
-    return d;
 }
 
 void ParsedNumber::populateFormattable(Formattable& output) const {
-    bool sawNegative = 0 != (flags & FLAG_NEGATIVE);
     bool sawNaN = 0 != (flags & FLAG_NAN);
     bool sawInfinity = 0 != (flags & FLAG_INFINITY);
 
@@ -92,7 +94,7 @@ void ParsedNumber::populateFormattable(Formattable& output) const {
         return;
     }
     if (sawInfinity) {
-        if (sawNegative) {
+        if (0 != (flags & FLAG_NEGATIVE)) {
             output.setDouble(-INFINITY);
             return;
         } else {
@@ -100,17 +102,14 @@ void ParsedNumber::populateFormattable(Formattable& output) const {
             return;
         }
     }
-    if (quantity.isZero() && sawNegative) {
+    U_ASSERT(!quantity.bogus);
+    if (quantity.isZero() && quantity.isNegative()) {
         output.setDouble(-0.0);
         return;
     }
 
     // All other numbers
-    LocalPointer<DecimalQuantity> actualQuantity(new DecimalQuantity(quantity));
-    if (0 != (flags & FLAG_NEGATIVE)) {
-        actualQuantity->negate();
-    }
-    output.adoptDecimalQuantity(actualQuantity.orphan());
+    output.adoptDecimalQuantity(new DecimalQuantity(quantity));
 }
 
 bool ParsedNumber::isBetterThan(const ParsedNumber& other) {
index 7e2dc52c949bae481b21f06f6a1a94ce3921f70d..849eab6837233056c512e37d57686f266579d4f2 100644 (file)
@@ -18,9 +18,36 @@ using namespace icu::numparse;
 using namespace icu::numparse::impl;
 
 
+namespace {
+
+inline const UnicodeSet& minusSignSet() {
+    return *unisets::get(unisets::MINUS_SIGN);
+}
+
+inline const UnicodeSet& plusSignSet() {
+    return *unisets::get(unisets::PLUS_SIGN);
+}
+
+} // namespace
+
+
 ScientificMatcher::ScientificMatcher(const DecimalFormatSymbols& dfs, const Grouper& grouper)
         : fExponentSeparatorString(dfs.getConstSymbol(DecimalFormatSymbols::kExponentialSymbol)),
-          fExponentMatcher(dfs, grouper, PARSE_FLAG_INTEGER_ONLY) {
+          fExponentMatcher(dfs, grouper, PARSE_FLAG_INTEGER_ONLY | PARSE_FLAG_GROUPING_DISABLED) {
+
+    const UnicodeString& minusSign = dfs.getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol);
+    if (minusSignSet().contains(minusSign)) {
+        fCustomMinusSign.setToBogus();
+    } else {
+        fCustomMinusSign = minusSign;
+    }
+
+    const UnicodeString& plusSign = dfs.getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol);
+    if (plusSignSet().contains(plusSign)) {
+        fCustomPlusSign.setToBogus();
+    } else {
+        fCustomPlusSign = plusSign;
+    }
 }
 
 bool ScientificMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const {
@@ -37,18 +64,35 @@ bool ScientificMatcher::match(StringSegment& segment, ParsedNumber& result, UErr
         // Full exponent separator match.
 
         // First attempt to get a code point, returning true if we can't get one.
-        segment.adjustOffset(overlap1);
-        if (segment.length() == 0) {
+        if (segment.length() == overlap1) {
             return true;
         }
+        segment.adjustOffset(overlap1);
 
         // Allow a sign, and then try to match digits.
         int8_t exponentSign = 1;
-        if (segment.startsWith(*unisets::get(unisets::MINUS_SIGN))) {
+        if (segment.startsWith(minusSignSet())) {
             exponentSign = -1;
             segment.adjustOffsetByCodePoint();
-        } else if (segment.startsWith(*unisets::get(unisets::PLUS_SIGN))) {
+        } else if (segment.startsWith(plusSignSet())) {
             segment.adjustOffsetByCodePoint();
+        } else if (segment.startsWith(fCustomMinusSign)) {
+            int32_t overlap2 = segment.getCommonPrefixLength(fCustomMinusSign);
+            if (overlap2 != fCustomMinusSign.length()) {
+                // Partial custom sign match; un-match the exponent separator.
+                segment.adjustOffset(-overlap1);
+                return true;
+            }
+            exponentSign = -1;
+            segment.adjustOffset(overlap2);
+        } else if (segment.startsWith(fCustomPlusSign)) {
+            int32_t overlap2 = segment.getCommonPrefixLength(fCustomPlusSign);
+            if (overlap2 != fCustomPlusSign.length()) {
+                // Partial custom sign match; un-match the exponent separator.
+                segment.adjustOffset(-overlap1);
+                return true;
+            }
+            segment.adjustOffset(overlap2);
         }
 
         int digitsOffset = segment.getOffset();
index 4084fda1a74fe4685e4652f969f69d0c12cf7928..0581b7e5ed015ff45b8af7e737c3eabba193fd77 100644 (file)
@@ -32,6 +32,8 @@ class ScientificMatcher : public NumberParseMatcher, public UMemory {
   private:
     UnicodeString fExponentSeparatorString;
     DecimalMatcher fExponentMatcher;
+    UnicodeString fCustomMinusSign;
+    UnicodeString fCustomPlusSign;
 };
 
 
index 51922752b621f4b3d24aafffb6c3e3ff5e567b44..d66e3e704bdd5c5c2535d169e3d00990f7f8b5ce 100644 (file)
@@ -152,13 +152,6 @@ PercentMatcher::PercentMatcher(const DecimalFormatSymbols& dfs)
         : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kPercentSymbol), unisets::PERCENT_SIGN) {
 }
 
-void PercentMatcher::postProcess(ParsedNumber& result) const {
-    SymbolMatcher::postProcess(result);
-    if (0 != (result.flags & FLAG_PERCENT) && !result.quantity.bogus) {
-        result.quantity.adjustMagnitude(-2);
-    }
-}
-
 bool PercentMatcher::isDisabled(const ParsedNumber& result) const {
     return 0 != (result.flags & FLAG_PERCENT);
 }
@@ -173,13 +166,6 @@ PermilleMatcher::PermilleMatcher(const DecimalFormatSymbols& dfs)
         : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kPerMillSymbol), unisets::PERMILLE_SIGN) {
 }
 
-void PermilleMatcher::postProcess(ParsedNumber& result) const {
-    SymbolMatcher::postProcess(result);
-    if (0 != (result.flags & FLAG_PERMILLE) && !result.quantity.bogus) {
-        result.quantity.adjustMagnitude(-3);
-    }
-}
-
 bool PermilleMatcher::isDisabled(const ParsedNumber& result) const {
     return 0 != (result.flags & FLAG_PERMILLE);
 }
index 7264734fc0d058e68a08b3b139c65b661ce331d4..6f0edc710799b30fad17f179ab8451878176dc4c 100644 (file)
@@ -124,8 +124,6 @@ class PercentMatcher : public SymbolMatcher {
 
     PercentMatcher(const DecimalFormatSymbols& dfs);
 
-    void postProcess(ParsedNumber& result) const override;
-
   protected:
     bool isDisabled(const ParsedNumber& result) const override;
 
@@ -139,8 +137,6 @@ class PermilleMatcher : public SymbolMatcher {
 
     PermilleMatcher(const DecimalFormatSymbols& dfs);
 
-    void postProcess(ParsedNumber& result) const override;
-
   protected:
     bool isDisabled(const ParsedNumber& result) const override;
 
index 420a5f88c53eea612ea7d9f247659c076778e497..4e4456538b383e3a65e1d1ae1cb5b3cafd77d34f 100644 (file)
@@ -146,6 +146,9 @@ class ParsedNumber {
      */
     void setCharsConsumed(const StringSegment& segment);
 
+    /** Apply certain number-related flags to the DecimalQuantity. */
+    void postProcess();
+
     /**
      * Returns whether this the parse was successful. To be successful, at least one char must have been
      * consumed, and the failure flag must not be set.
index e481acca225b7cb9dbcf6abcae2d62a71ef5d10d..c1c194cfb1d3bf6c3c2245864dd07ed080293952 100644 (file)
@@ -1688,13 +1688,13 @@ void NumberFormatTest::TestSecondaryGrouping(void) {
     CHECK(status, "DecimalFormat ct");
 
     expect2(f, (int32_t)123456789L, "12,34,56,789");
-    expectPat(f, "#,##,###");
+    expectPat(f, "#,##,##0");
     f.applyPattern("#,###", status);
     CHECK(status, "applyPattern");
 
     f.setSecondaryGroupingSize(4);
     expect2(f, (int32_t)123456789L, "12,3456,789");
-    expectPat(f, "#,####,###");
+    expectPat(f, "#,####,##0");
     NumberFormat *g = NumberFormat::createInstance(Locale("hi", "IN"), status);
     CHECK_DATA(status, "createInstance(hi_IN)");
 
@@ -1816,7 +1816,7 @@ void NumberFormatTest::TestScientific(void) {
     int32_t PAT_length = UPRV_LENGTHOF(PAT);
     int32_t DIGITS[] = {
         // min int, max int, min frac, max frac
-        0, 1, 0, 0, // "#E0"
+        1, 1, 0, 0, // "#E0"
         1, 1, 0, 4, // "0.####E0"
         2, 2, 3, 3, // "00.000E00"
         1, 3, 0, 4, // "##0.####E000"
@@ -2159,7 +2159,7 @@ void NumberFormatTest::TestPatterns2(void) {
 
     fmt.setFormatWidth(16);
     //              12  34567890123456
-    expectPat(fmt, "AA*^#,###,##0.00ZZ");
+    expectPat(fmt, "AA*^#####,##0.00ZZ");
 }
 
 void NumberFormatTest::TestSurrogateSupport(void) {
@@ -2223,9 +2223,9 @@ void NumberFormatTest::TestSurrogateSupport(void) {
            int32_t(-20), expStr, status);
 
     custom.setSymbol(DecimalFormatSymbols::kPercentSymbol, "percent");
-    patternStr = "'You''ve lost ' -0.00 %' of your money today'";
+    patternStr = "'You''ve lost ' 0.00 %' of your money today'";
     patternStr = patternStr.unescape();
-    expStr = UnicodeString(" minus You've lost   minus 2000decimal00 percent of your money today", "");
+    expStr = UnicodeString(" minus You've lost  2000decimal00 percent of your money today", "");
     status = U_ZERO_ERROR;
     expect2(new DecimalFormat(patternStr, custom, status),
            int32_t(-20), expStr, status);
index 509da32c8d8205c73db950ae8a5105186ec1b613..4759745b21785315d451838a37bc883ac4ac3c8a 100644 (file)
@@ -1601,6 +1601,15 @@ lenient  parse   output  breaks
 0      0       fail    JK
 0      +0      0       JK
 
+test parse with scientific-separator-affix overlap
+set locale en
+begin
+pattern        lenient parse   output  breaks
+0E0','x        1       5E3,x   5000
+0E0','x        0       5E3,x   5000
+0E0'.'x        1       5E3.x   5000
+0E0'.'x        0       5E3.x   5000
+
 
 
 
index 1a5d6f73ad6ce38096cd82a97724e6c3d0a1b1de..ae2d46909966539fe3af9b480e662dca7c8aa92f 100644 (file)
@@ -86,6 +86,9 @@ public interface DecimalQuantity extends PluralRules.IFixedDecimal {
      */
     public void multiplyBy(BigDecimal multiplicand);
 
+    /** Flips the sign from positive to negative and back. */
+    void negate();
+
     /**
      * Scales the number by a power of ten. For example, if the value is currently "1234.56", calling
      * this method with delta=-3 will change the value to "1.23456".
index d513ef3c1601a32a1a8dec13e175504172d11cbf..1366f0a411372f7330026cd7ed87a5a70cc67e6e 100644 (file)
@@ -199,6 +199,11 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
         setToBigDecimal(temp);
     }
 
+    @Override
+    public void negate() {
+      flags ^= NEGATIVE_FLAG;
+    }
+
     @Override
     public int getMagnitude() throws ArithmeticException {
         if (precision == 0) {
@@ -573,6 +578,9 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
         for (int magnitude = scale + precision - 1; magnitude >= 0; magnitude--) {
             result = result * 10 + getDigitPos(magnitude - scale);
         }
+        if (isNegative()) {
+            result = -result;
+        }
         return result;
     }
 
@@ -676,8 +684,9 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
             }
             result /= DOUBLE_MULTIPLIERS[-i];
         }
-        if (isNegative())
+        if (isNegative()) {
             result = -result;
+        }
         return result;
     }
 
@@ -704,8 +713,9 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
                 result /= 1e22;
             result /= DOUBLE_MULTIPLIERS[-delta];
         }
-        if (isNegative())
-            result *= -1;
+        if (isNegative()) {
+            result = -result;
+        }
         return result;
     }
 
index 67fb1e30664b95214e60999e923fe49f0a1cb0a1..72a923a21660e24213b8564d0282b0d31a1d7043 100644 (file)
@@ -428,6 +428,9 @@ public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_Abstra
     public String toNumberString() {
         StringBuilder sb = new StringBuilder();
         if (usingBytes) {
+            if (precision == 0) {
+                sb.append('0');
+            }
             for (int i = precision - 1; i >= 0; i--) {
                 sb.append(bcdBytes[i]);
             }
index 6f511581407aa0804848d9d095f35261662de0de..04bbadbc22823319f302340c9f54717c0358a1ff 100644 (file)
@@ -129,7 +129,7 @@ public class AffixMatcher implements NumberParseMatcher {
                 continue;
             }
 
-            // Flags for setting in the ParsedNumber
+            // Flags for setting in the ParsedNumber; the token matchers may add more.
             int flags = (signum == -1) ? ParsedNumber.FLAG_NEGATIVE : 0;
 
             // Note: it is indeed possible for posPrefix and posSuffix to both be null.
@@ -223,6 +223,12 @@ public class AffixMatcher implements NumberParseMatcher {
                 result.suffix = "";
             }
             result.flags |= flags;
+            if (prefix != null) {
+                prefix.postProcess(result);
+            }
+            if (suffix != null) {
+                suffix.postProcess(result);
+            }
         }
     }
 
index 95e313651cc6c68305a5561395b41efb9ea51d4b..f91a61ebd4f28aad8a67cbc84d7699a4c1a364da 100644 (file)
@@ -302,6 +302,7 @@ public class NumberParserImpl {
         for (NumberParseMatcher matcher : matchers) {
             matcher.postProcess(result);
         }
+        result.postProcess();
     }
 
     private void parseGreedyRecursive(StringSegment segment, ParsedNumber result) {
index 9b472eed2d878c9aff85c38bf9fbd74da3faa6a7..40277d7700e704244720725ec9bb97fa7295217c 100644 (file)
@@ -2,7 +2,6 @@
 // License & terms of use: http://www.unicode.org/copyright.html#License
 package com.ibm.icu.impl.number.parse;
 
-import java.math.BigDecimal;
 import java.util.Comparator;
 
 import com.ibm.icu.impl.StringSegment;
@@ -112,6 +111,19 @@ public class ParsedNumber {
         charEnd = segment.getOffset();
     }
 
+    /** Apply certain number-related flags to the DecimalQuantity. */
+    public void postProcess() {
+        if (quantity != null && 0 != (flags & FLAG_NEGATIVE)) {
+            quantity.negate();
+        }
+        if (quantity != null && 0 != (flags & FLAG_PERCENT)) {
+            quantity.adjustMagnitude(-2);
+        }
+        if (quantity != null && 0 != (flags & FLAG_PERMILLE)) {
+            quantity.adjustMagnitude(-3);
+        }
+    }
+
     /**
      * Returns whether this the parse was successful. To be successful, at least one char must have been
      * consumed, and the failure flag must not be set.
@@ -129,7 +141,6 @@ public class ParsedNumber {
     }
 
     public Number getNumber(boolean forceBigDecimal) {
-        boolean sawNegative = 0 != (flags & FLAG_NEGATIVE);
         boolean sawNaN = 0 != (flags & FLAG_NAN);
         boolean sawInfinity = 0 != (flags & FLAG_INFINITY);
 
@@ -138,34 +149,22 @@ public class ParsedNumber {
             return Double.NaN;
         }
         if (sawInfinity) {
-            if (sawNegative) {
+            if (0 != (flags & FLAG_NEGATIVE)) {
                 return Double.NEGATIVE_INFINITY;
             } else {
                 return Double.POSITIVE_INFINITY;
             }
         }
-        if (quantity.isZero() && sawNegative) {
+        assert quantity != null;
+        if (quantity.isZero() && quantity.isNegative()) {
             return -0.0;
         }
 
         if (quantity.fitsInLong() && !forceBigDecimal) {
-            long l = quantity.toLong();
-            if (0 != (flags & FLAG_NEGATIVE)) {
-                l *= -1;
-            }
-            return l;
-        }
-
-        BigDecimal d = quantity.toBigDecimal();
-        if (0 != (flags & FLAG_NEGATIVE)) {
-            d = d.negate();
-        }
-        // Special case: MIN_LONG
-        // TODO: It is supported in quantity.toLong() if quantity had the negative flag.
-        if (d.compareTo(BigDecimal.valueOf(Long.MIN_VALUE)) == 0 && !forceBigDecimal) {
-            return Long.MIN_VALUE;
+            return quantity.toLong();
+        } else {
+            return quantity.toBigDecimal();
         }
-        return d;
 
     }
 
index afac0a6f7218b192c59d822b5cea5264011329e7..3944c31684347cc0e2d9e6caf04a9d5240bc50d4 100644 (file)
@@ -41,14 +41,6 @@ public class PercentMatcher extends SymbolMatcher {
         result.setCharsConsumed(segment);
     }
 
-    @Override
-    public void postProcess(ParsedNumber result) {
-        super.postProcess(result);
-        if (0 != (result.flags & ParsedNumber.FLAG_PERCENT) && result.quantity != null) {
-            result.quantity.adjustMagnitude(-2);
-        }
-    }
-
     @Override
     public String toString() {
         return "<PercentMatcher>";
index d28e96b8e48bb4b94949ae8e64d376cad0ce2d52..3ad6e09eac0c08708abce3ca412f4b7a042fec0f 100644 (file)
@@ -41,14 +41,6 @@ public class PermilleMatcher extends SymbolMatcher {
         result.setCharsConsumed(segment);
     }
 
-    @Override
-    public void postProcess(ParsedNumber result) {
-        super.postProcess(result);
-        if (0 != (result.flags & ParsedNumber.FLAG_PERMILLE) && result.quantity != null) {
-            result.quantity.adjustMagnitude(-3);
-        }
-    }
-
     @Override
     public String toString() {
         return "<PermilleMatcher>";
index 8fdd5b5bd833e566120bbc58ca7d6a7b41d97573..9e0f1fa71e127de5e41f50f3bf9179e35ffe13bf 100644 (file)
@@ -5,6 +5,7 @@ package com.ibm.icu.impl.number.parse;
 import com.ibm.icu.impl.StringSegment;
 import com.ibm.icu.impl.number.Grouper;
 import com.ibm.icu.text.DecimalFormatSymbols;
+import com.ibm.icu.text.UnicodeSet;
 
 /**
  * @author sffc
@@ -14,6 +15,8 @@ public class ScientificMatcher implements NumberParseMatcher {
 
     private final String exponentSeparatorString;
     private final DecimalMatcher exponentMatcher;
+    private final String customMinusSign;
+    private final String customPlusSign;
 
     public static ScientificMatcher getInstance(DecimalFormatSymbols symbols, Grouper grouper) {
         // TODO: Static-initialize most common instances?
@@ -24,7 +27,20 @@ public class ScientificMatcher implements NumberParseMatcher {
         exponentSeparatorString = symbols.getExponentSeparator();
         exponentMatcher = DecimalMatcher.getInstance(symbols,
                 grouper,
-                ParsingUtils.PARSE_FLAG_INTEGER_ONLY);
+                ParsingUtils.PARSE_FLAG_INTEGER_ONLY | ParsingUtils.PARSE_FLAG_GROUPING_DISABLED);
+
+        String minusSign = symbols.getMinusSignString();
+        customMinusSign = minusSignSet().contains(minusSign) ? null : minusSign;
+        String plusSign = symbols.getPlusSignString();
+        customPlusSign = plusSignSet().contains(plusSign) ? null : plusSign;
+    }
+
+    private static UnicodeSet minusSignSet() {
+        return UnicodeSetStaticCache.get(UnicodeSetStaticCache.Key.MINUS_SIGN);
+    }
+
+    private static UnicodeSet plusSignSet() {
+        return UnicodeSetStaticCache.get(UnicodeSetStaticCache.Key.PLUS_SIGN);
     }
 
     @Override
@@ -42,18 +58,35 @@ public class ScientificMatcher implements NumberParseMatcher {
             // Full exponent separator match.
 
             // First attempt to get a code point, returning true if we can't get one.
-            segment.adjustOffset(overlap1);
-            if (segment.length() == 0) {
+            if (segment.length() == overlap1) {
                 return true;
             }
+            segment.adjustOffset(overlap1);
 
             // Allow a sign, and then try to match digits.
             int exponentSign = 1;
-            if (segment.startsWith(UnicodeSetStaticCache.get(UnicodeSetStaticCache.Key.MINUS_SIGN))) {
+            if (segment.startsWith(minusSignSet())) {
                 exponentSign = -1;
                 segment.adjustOffsetByCodePoint();
-            } else if (segment.startsWith(UnicodeSetStaticCache.get(UnicodeSetStaticCache.Key.PLUS_SIGN))) {
+            } else if (segment.startsWith(plusSignSet())) {
                 segment.adjustOffsetByCodePoint();
+            } else if (segment.startsWith(customMinusSign)) {
+                int overlap2 = segment.getCommonPrefixLength(customMinusSign);
+                if (overlap2 != customMinusSign.length()) {
+                    // Partial custom sign match; un-match the exponent separator.
+                    segment.adjustOffset(-overlap1);
+                    return true;
+                }
+                exponentSign = -1;
+                segment.adjustOffset(overlap2);
+            } else if (segment.startsWith(customPlusSign)) {
+                int overlap2 = segment.getCommonPrefixLength(customPlusSign);
+                if (overlap2 != customPlusSign.length()) {
+                    // Partial custom sign match; un-match the exponent separator.
+                    segment.adjustOffset(-overlap1);
+                    return true;
+                }
+                segment.adjustOffset(overlap2);
             }
 
             int digitsOffset = segment.getOffset();
index b910a9ca739905cd8b7f04aa76e4170b29b6fe36..64cb95552f470819a3a2b3b29d2be182e4445da4 100644 (file)
@@ -429,6 +429,11 @@ public class DecimalQuantity_SimpleStorage implements DecimalQuantity {
     }
   }
 
+  @Override
+  public void negate() {
+    flags ^= NEGATIVE_FLAG;
+  }
+
   /**
    * Divide the internal number by the specified quotient. This method forces the internal
    * representation into a BigDecimal. If you are dividing by a power of 10, use {@link
index 370a843f51049104e16c0f001c9cebbb77f3514a..5f1ca8bfdf6d763ef54aa8379a1485d4bae21acc 100644 (file)
@@ -5955,4 +5955,23 @@ public class NumberFormatTest extends TestFmwk {
                 result.doubleValue(),
                 0.0);
     }
+
+    @Test
+    public void testScientificCustomSign() {
+        DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
+        dfs.setMinusSignString("nnn");
+        dfs.setPlusSignString("ppp");
+        DecimalFormat df = new DecimalFormat("0E0", dfs);
+        df.setExponentSignAlwaysShown(true);
+        expect2(df, 0.5, "5Ennn1");
+        expect2(df, 50, "5Eppp1");
+    }
+
+    @Test
+    public void testParsePercentInPattern() {
+        DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
+        DecimalFormat df = new DecimalFormat("0x%", dfs);
+        df.setParseStrict(true);
+        expect2(df, 0.5, "50x%");
+    }
 }