mirror of
https://github.com/unicode-org/icu.git
synced 2025-04-13 17:01:16 +00:00
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
This commit is contained in:
parent
db9c74b3f4
commit
7f9de6f1db
24 changed files with 228 additions and 102 deletions
icu4c/source
i18n
decimfmt.cppnumber_decimalquantity.cppnumber_decimalquantity.hnumparse_affixes.cppnumparse_impl.cppnumparse_parsednumber.cppnumparse_scientific.cppnumparse_scientific.hnumparse_symbols.cppnumparse_symbols.hnumparse_types.h
test
icu4j/main
classes/core/src/com/ibm/icu/impl/number
tests/core/src/com/ibm/icu/dev
|
@ -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() {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -32,6 +32,8 @@ class ScientificMatcher : public NumberParseMatcher, public UMemory {
|
|||
private:
|
||||
UnicodeString fExponentSeparatorString;
|
||||
DecimalMatcher fExponentMatcher;
|
||||
UnicodeString fCustomMinusSign;
|
||||
UnicodeString fCustomPlusSign;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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".
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -302,6 +302,7 @@ public class NumberParserImpl {
|
|||
for (NumberParseMatcher matcher : matchers) {
|
||||
matcher.postProcess(result);
|
||||
}
|
||||
result.postProcess();
|
||||
}
|
||||
|
||||
private void parseGreedyRecursive(StringSegment segment, ParsedNumber result) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>";
|
||||
|
|
|
@ -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>";
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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%");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue