From 7f9de6f1db80464a8b9f5040f47f5be9352271fc Mon Sep 17 00:00:00 2001 From: Shane Carr Date: Sat, 31 Mar 2018 03:10:44 +0000 Subject: [PATCH] ICU-13634 Fixing resolution of negative and percent signs in parsing; adding custom sign support to ScientificMatcher; and other minor fixes. X-SVN-Rev: 41180 --- icu4c/source/i18n/decimfmt.cpp | 8 +++ icu4c/source/i18n/number_decimalquantity.cpp | 15 ++++-- icu4c/source/i18n/number_decimalquantity.h | 2 +- icu4c/source/i18n/numparse_affixes.cpp | 8 ++- icu4c/source/i18n/numparse_impl.cpp | 1 + icu4c/source/i18n/numparse_parsednumber.cpp | 45 ++++++++-------- icu4c/source/i18n/numparse_scientific.cpp | 54 +++++++++++++++++-- icu4c/source/i18n/numparse_scientific.h | 2 + icu4c/source/i18n/numparse_symbols.cpp | 14 ----- icu4c/source/i18n/numparse_symbols.h | 4 -- icu4c/source/i18n/numparse_types.h | 3 ++ icu4c/source/test/intltest/numfmtst.cpp | 12 ++--- .../numberformattestspecification.txt | 9 ++++ .../ibm/icu/impl/number/DecimalQuantity.java | 3 ++ .../number/DecimalQuantity_AbstractBCD.java | 16 ++++-- .../DecimalQuantity_DualStorageBCD.java | 3 ++ .../icu/impl/number/parse/AffixMatcher.java | 8 ++- .../impl/number/parse/NumberParserImpl.java | 1 + .../icu/impl/number/parse/ParsedNumber.java | 39 +++++++------- .../icu/impl/number/parse/PercentMatcher.java | 8 --- .../impl/number/parse/PermilleMatcher.java | 8 --- .../impl/number/parse/ScientificMatcher.java | 43 +++++++++++++-- .../number/DecimalQuantity_SimpleStorage.java | 5 ++ .../icu/dev/test/format/NumberFormatTest.java | 19 +++++++ 24 files changed, 228 insertions(+), 102 deletions(-) diff --git a/icu4c/source/i18n/decimfmt.cpp b/icu4c/source/i18n/decimfmt.cpp index b15b962bf84..569a2a8a7b8 100644 --- a/icu4c/source/i18n/decimfmt.cpp +++ b/icu4c/source/i18n/decimfmt.cpp @@ -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(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() { diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index bb731938f12..40ad848ff4e 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -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(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)); } diff --git a/icu4c/source/i18n/number_decimalquantity.h b/icu4c/source/i18n/number_decimalquantity.h index 74c85248c4a..b205778e19a 100644 --- a/icu4c/source/i18n/number_decimalquantity.h +++ b/icu4c/source/i18n/number_decimalquantity.h @@ -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(); /** diff --git a/icu4c/source/i18n/numparse_affixes.cpp b/icu4c/source/i18n/numparse_affixes.cpp index 159d70dcda9..83ecdd144b8 100644 --- a/icu4c/source/i18n/numparse_affixes.cpp +++ b/icu4c/source/i18n/numparse_affixes.cpp @@ -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); + } } } diff --git a/icu4c/source/i18n/numparse_impl.cpp b/icu4c/source/i18n/numparse_impl.cpp index c528d8efa82..36ddc1f2f19 100644 --- a/icu4c/source/i18n/numparse_impl.cpp +++ b/icu4c/source/i18n/numparse_impl.cpp @@ -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, diff --git a/icu4c/source/i18n/numparse_parsednumber.cpp b/icu4c/source/i18n/numparse_parsednumber.cpp index 16da923459e..c658fc27d3e 100644 --- a/icu4c/source/i18n/numparse_parsednumber.cpp +++ b/icu4c/source/i18n/numparse_parsednumber.cpp @@ -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; + return quantity.toLong(); + } else { + return quantity.toDouble(); } - - // 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 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 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) { diff --git a/icu4c/source/i18n/numparse_scientific.cpp b/icu4c/source/i18n/numparse_scientific.cpp index 7e2dc52c949..849eab68372 100644 --- a/icu4c/source/i18n/numparse_scientific.cpp +++ b/icu4c/source/i18n/numparse_scientific.cpp @@ -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(); diff --git a/icu4c/source/i18n/numparse_scientific.h b/icu4c/source/i18n/numparse_scientific.h index 4084fda1a74..0581b7e5ed0 100644 --- a/icu4c/source/i18n/numparse_scientific.h +++ b/icu4c/source/i18n/numparse_scientific.h @@ -32,6 +32,8 @@ class ScientificMatcher : public NumberParseMatcher, public UMemory { private: UnicodeString fExponentSeparatorString; DecimalMatcher fExponentMatcher; + UnicodeString fCustomMinusSign; + UnicodeString fCustomPlusSign; }; diff --git a/icu4c/source/i18n/numparse_symbols.cpp b/icu4c/source/i18n/numparse_symbols.cpp index 51922752b62..d66e3e704bd 100644 --- a/icu4c/source/i18n/numparse_symbols.cpp +++ b/icu4c/source/i18n/numparse_symbols.cpp @@ -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); } diff --git a/icu4c/source/i18n/numparse_symbols.h b/icu4c/source/i18n/numparse_symbols.h index 7264734fc0d..6f0edc71079 100644 --- a/icu4c/source/i18n/numparse_symbols.h +++ b/icu4c/source/i18n/numparse_symbols.h @@ -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; diff --git a/icu4c/source/i18n/numparse_types.h b/icu4c/source/i18n/numparse_types.h index 420a5f88c53..4e4456538b3 100644 --- a/icu4c/source/i18n/numparse_types.h +++ b/icu4c/source/i18n/numparse_types.h @@ -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. diff --git a/icu4c/source/test/intltest/numfmtst.cpp b/icu4c/source/test/intltest/numfmtst.cpp index e481acca225..c1c194cfb1d 100644 --- a/icu4c/source/test/intltest/numfmtst.cpp +++ b/icu4c/source/test/intltest/numfmtst.cpp @@ -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); diff --git a/icu4c/source/test/testdata/numberformattestspecification.txt b/icu4c/source/test/testdata/numberformattestspecification.txt index 509da32c8d8..4759745b217 100644 --- a/icu4c/source/test/testdata/numberformattestspecification.txt +++ b/icu4c/source/test/testdata/numberformattestspecification.txt @@ -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 + diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java index 1a5d6f73ad6..ae2d4690996 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java @@ -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". diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java index d513ef3c160..1366f0a4113 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java @@ -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; } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java index 67fb1e30664..72a923a2166 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java @@ -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]); } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java index 6f511581407..04bbadbc228 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java @@ -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); + } } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java index 95e313651cc..f91a61ebd4f 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java @@ -302,6 +302,7 @@ public class NumberParserImpl { for (NumberParseMatcher matcher : matchers) { matcher.postProcess(result); } + result.postProcess(); } private void parseGreedyRecursive(StringSegment segment, ParsedNumber result) { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java index 9b472eed2d8..40277d7700e 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsedNumber.java @@ -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,35 +149,23 @@ 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; + return quantity.toLong(); + } else { + return quantity.toBigDecimal(); } - 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 d; - } boolean isBetterThan(ParsedNumber other) { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PercentMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PercentMatcher.java index afac0a6f721..3944c316843 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PercentMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PercentMatcher.java @@ -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 ""; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PermilleMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PermilleMatcher.java index d28e96b8e48..3ad6e09eac0 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PermilleMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PermilleMatcher.java @@ -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 ""; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ScientificMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ScientificMatcher.java index 8fdd5b5bd83..9e0f1fa71e1 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ScientificMatcher.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ScientificMatcher.java @@ -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(); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java index b910a9ca739..64cb95552f4 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/impl/number/DecimalQuantity_SimpleStorage.java @@ -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 diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java index 370a843f510..5f1ca8bfdf6 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java @@ -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%"); + } }