@Override
public void adjustMagnitude(int delta) {
if (precision != 0) {
- scale += delta;
- origDelta += delta;
+ // TODO: Math.addExact is not in 1.6 or 1.7
+ scale = Math.addExact(scale, delta);
+ origDelta = Math.addExact(origDelta, delta);
}
}
*/
public class DecimalMatcher implements NumberParseMatcher {
-
public boolean requireGroupingMatch = false;
public boolean decimalEnabled = true;
public boolean groupingEnabled = true;
@Override
public boolean match(StringSegment segment, ParsedNumber result) {
+ return match(segment, result, false);
+ }
+
+ public boolean match(StringSegment segment, ParsedNumber result, boolean negativeExponent) {
assert frozen;
if (result.seenNumber() && !isScientific) {
// A number has already been consumed.
// If found, save it in the DecimalQuantity or scientific adjustment.
if (digit >= 0) {
if (isScientific) {
- exponent = digit + exponent * 10;
+ int nextExponent = digit + exponent * 10;
+ if (nextExponent < exponent) {
+ // Overflow
+ exponent = Integer.MAX_VALUE;
+ } else {
+ exponent = nextExponent;
+ }
} else {
if (result.quantity == null) {
result.quantity = new DecimalQuantity_DualStorageBCD();
}
if (isScientific) {
- result.quantity.adjustMagnitude(exponent);
+ boolean overflow = (exponent == Integer.MAX_VALUE);
+ if (!overflow) {
+ try {
+ result.quantity.adjustMagnitude(negativeExponent ? -exponent : exponent);
+ } catch (ArithmeticException e) {
+ overflow = true;
+ }
+ }
+ if (overflow) {
+ if (negativeExponent) {
+ // Set to zero
+ result.quantity.clear();
+ } else {
+ // Set to infinity
+ result.quantity = null;
+ result.flags |= ParsedNumber.FLAG_INFINITY;
+ }
+ }
} else if (result.quantity == null) {
// No-op: strings that start with a separator without any other digits
} else if (seenBothSeparators || (separator != -1 && decimalUniSet.contains(separator))) {
// No-op
}
+ @Override
+ public String toString() {
+ return "<NanMatcher>";
+ }
+
}
parser.parse(input, true, result);
ppos.setIndex(result.charsConsumed);
if (result.charsConsumed > 0) {
- return result.getDouble();
+ return result.getNumber();
} else {
return null;
}
assert 0 != (result.flags & ParsedNumber.FLAG_HAS_DEFAULT_CURRENCY);
currency = CustomSymbolCurrency.resolve(properties.getCurrency(), symbols.getULocale(), symbols);
}
- return new CurrencyAmount(result.getDouble(), currency);
+ return new CurrencyAmount(result.getNumber(), currency);
} else {
return null;
}
/// CURRENCY MATCHER ///
////////////////////////
- parseCurrency = parseCurrency || patternInfo.hasCurrencySign();
- if (parseCurrency) {
+ if (parseCurrency || patternInfo.hasCurrencySign()) {
parser.addMatcher(new CurrencyMatcher(locale));
}
}
parser.addMatcher(new MinusSignMatcher());
parser.addMatcher(new NanMatcher(symbols));
+ parser.addMatcher(new PercentMatcher());
+ parser.addMatcher(new PermilleMatcher());
DecimalMatcher decimalMatcher = new DecimalMatcher();
decimalMatcher.requireGroupingMatch = isStrict;
decimalMatcher.groupingEnabled = properties.getGroupingSize() > 0;
// 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.number.DecimalQuantity_DualStorageBCD;
public static final int FLAG_HAS_DEFAULT_CURRENCY = 0x0010;
public static final int FLAG_HAS_DECIMAL_SEPARATOR = 0x0020;
public static final int FLAG_NAN = 0x0040;
+ public static final int FLAG_INFINITY = 0x0080;
/** A Comparator that favors ParsedNumbers with the most chars consumed. */
public static final Comparator<ParsedNumber> COMPARATOR = new Comparator<ParsedNumber>() {
}
public boolean seenNumber() {
- return quantity != null || 0 != (flags & FLAG_NAN);
+ return quantity != null || 0 != (flags & FLAG_NAN) || 0 != (flags & FLAG_INFINITY);
}
- public double getDouble() {
- if (0 != (flags & FLAG_NAN)) {
- return Double.NaN;
+ public Number getNumber() {
+ boolean sawNegative = 0 != (flags & FLAG_NEGATIVE);
+ boolean sawNaN = 0 != (flags & FLAG_NAN);
+ boolean sawInfinity = 0 != (flags & FLAG_INFINITY);
+
+ // Check for NaN, infinity, and -0.0
+ if (sawNaN) {
+ return Double.NaN;
+ }
+ if (sawInfinity) {
+ if (sawNegative) {
+ return Double.NEGATIVE_INFINITY;
+ } else {
+ return Double.POSITIVE_INFINITY;
+ }
}
- double d = quantity.toDouble();
+ if (quantity.isZero() && sawNegative) {
+ return -0.0;
+ }
+
+ BigDecimal d = quantity.toBigDecimal();
if (0 != (flags & FLAG_NEGATIVE)) {
- d = -d;
+ d = d.negate();
}
return d;
}
--- /dev/null
+// © 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;
+
+import com.ibm.icu.text.UnicodeSet;
+
+/**
+ * @author sffc
+ *
+ */
+public class PercentMatcher extends SymbolMatcher {
+
+ public PercentMatcher() {
+ // FIXME
+ super("%", new UnicodeSet("[%]"));
+ }
+
+ @Override
+ protected boolean isDisabled(ParsedNumber result) {
+ return 0 != (result.flags & ParsedNumber.FLAG_PERCENT);
+ }
+
+ @Override
+ protected void accept(ParsedNumber result) {
+ result.flags |= ParsedNumber.FLAG_PERCENT;
+ }
+
+ @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>";
+ }
+}
--- /dev/null
+// © 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;
+
+import com.ibm.icu.text.UnicodeSet;
+
+/**
+ * @author sffc
+ *
+ */
+public class PermilleMatcher extends SymbolMatcher {
+
+ public PermilleMatcher() {
+ // FIXME
+ super("‰", new UnicodeSet("[‰]"));
+ }
+
+ @Override
+ protected boolean isDisabled(ParsedNumber result) {
+ return 0 != (result.flags & ParsedNumber.FLAG_PERMILLE);
+ }
+
+ @Override
+ protected void accept(ParsedNumber result) {
+ result.flags |= ParsedNumber.FLAG_PERMILLE;
+ }
+
+ @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>";
+ }
+}
}
int digitsOffset = segment.getOffset();
- int oldMagnitude = result.quantity.getMagnitude();
- boolean digitsReturnValue = exponentMatcher.match(segment, result);
+ boolean digitsReturnValue = exponentMatcher.match(segment, result, minusSign);
if (segment.getOffset() != digitsOffset) {
// At least one exponent digit was matched.
result.flags |= ParsedNumber.FLAG_HAS_EXPONENT;
- if (minusSign) {
- // Switch the magnitude adjustment from positive to negative. Extracting the exponent from the
- // change in the quantity's magnitude feels a bit like a hack; better would be for the
- // DecimalMatcher to somehow return the exponent directly.
- int exponent = result.quantity.getMagnitude() - oldMagnitude;
- result.quantity.adjustMagnitude(-2 * exponent);
- }
} else {
// No exponent digits were matched; un-match the exponent separator.
segment.adjustOffset(-overlap1);
1E2147483646 1E2147483646
1E-2147483649 0
1E-2147483648 0
-// S returns zero here
-1E-2147483647 1E-2147483647 S
+// S and P return zero here
+1E-2147483647 1E-2147483647 SP
1E-2147483646 1E-2147483646
test format push limits
100 9999999999999.9950000000001 9999999999999.9950000000001 C
2 9999999999999.9950000000001 10000000000000.00 C
2 9999999.99499999 9999999.99
-// K doesn't support halfDowm rounding mode?
+// K doesn't support halfDown rounding mode?
2 9999999.995 9999999.99 K
2 9999999.99500001 10000000.00
100 56565656565656565656565656565656565656565656565656565656565656 56565656565656565656565656565656565656565656565656565656565656.00 C
set pattern #,##0
begin
parse output breaks
-// K and J return null; S and C return 99
- 9 9 9 CJKS
+// K and J return null; S, C, and P return 99
+ 9 9 9 CJKSP
// K returns null
9 999 9999 K
test parse ignorables
set locale ar
// Note: Prefixes contain RLMs, as do some of the test cases.
+// P does not work on most of these right now.
set pattern x ab0c df
set negativePrefix y gh
set negativeSuffix i jk
begin
parse output breaks
-x ab56c df 56
-x ab56c df 56 K
-x ab56c df 56 K
-x ab56c df 56 JK
-x ab56c df 56 K
-x ab56 56 JK
-x a b56 56 JK
+x ab56c df 56 P
+x ab56c df 56 KP
+x ab56c df 56 KP
+x ab56c df 56 JKP
+x ab56c df 56 KP
+x ab56 56 JKP
+x a b56 56 JKP
56cdf 56 JK
56c df 56 JK
56cd f 56 JK
56cdf 56 JK
56c df 56 JK
56c df 56 JK
-y gh56i jk -56
-y gh56i jk -56 K
-y gh56i jk -56 K
-y gh56i jk -56 JK
-y gh56i jk -56 K
-y gh56 -56 JK
-y g h56 -56 JK
+y gh56i jk -56 P
+y gh56i jk -56 KP
+y gh56i jk -56 KP
+y gh56i jk -56 JKP
+y gh56i jk -56 KP
+y gh56 -56 JKP
+y g h56 -56 JKP
// S stops parsing after the 'i' for these and returns -56
// C stops before the 'i' and gets 56
56ijk -56 CJK
-56i jk -56 CJK
-56ij k -56 CJK
-56ijk -56 CJK
+56i jk -56 CJKP
+56ij k -56 CJKP
+56ijk -56 CJKP
56ijk -56 CJK
-56i jk -56 CJK
-56i jk -56 CJK
+56i jk -56 CJKP
+56i jk -56 CJKP
// S and C get 56 (accepts ' ' gs grouping); J and K get null
-5 6 fail CS
+5 6 fail CSP
56 5 JK
test parse spaces in grouping
set pattern #,##0
begin
parse output breaks
-// C, J and S get "12" here
-1 2 1 CJS
-1 23 1 CJS
+// C, J, S, and P get "12" here
+1 2 1 CJSP
+1 23 1 CJSP
// K gets 1 here; doesn't pick up the grouping separator
1 234 1234 K
parse output breaks
55% 0.55
// J and K get null
-55 0.55 JK
+// P requires the symbol to be present and gets 55
+55 0.55 JKP
test trailing grouping separators in pattern
// This test is for #13115
ParsedNumber resultObject = new ParsedNumber();
parser.parse(input, true, resultObject);
assertNotNull(message, resultObject.quantity);
- assertEquals(message, resultDouble, resultObject.getDouble(), 0.0);
+ assertEquals(message, resultDouble, resultObject.getNumber().doubleValue(), 0.0);
assertEquals(message, expectedCharsConsumed, resultObject.charsConsumed);
}
ParsedNumber resultObject = new ParsedNumber();
parser.parse(input, false, resultObject);
assertNotNull(message, resultObject.quantity);
- assertEquals(message, resultDouble, resultObject.getDouble(), 0.0);
+ assertEquals(message, resultDouble, resultObject.getNumber().doubleValue(), 0.0);
assertEquals(message, expectedCharsConsumed, resultObject.charsConsumed);
}
ParsedNumber resultObject = new ParsedNumber();
parser.parse(input, true, resultObject);
assertNotNull(message, resultObject.quantity);
- assertEquals(message, resultDouble, resultObject.getDouble(), 0.0);
+ assertEquals(message, resultDouble, resultObject.getNumber().doubleValue(), 0.0);
assertEquals(message, expectedCharsConsumed, resultObject.charsConsumed);
}
}