}
void DecimalFormat::setGroupingUsed(UBool enabled) {
+ NumberFormat::setGroupingUsed(enabled); // to set field for compatibility
if (enabled) {
// Set to a reasonable default value
fProperties->groupingSize = 3;
}
void DecimalFormat::setRoundingMode(ERoundingMode roundingMode) {
+ NumberFormat::setMaximumIntegerDigits(roundingMode); // to set field for compatibility
fProperties->roundingMode = static_cast<UNumberFormatRoundingMode>(roundingMode);
refreshFormatterNoError();
}
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() {
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;
}
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;
}
for (; delta <= -22; delta += 22) result /= 1e22;
result /= DOUBLE_MULTIPLIERS[-delta];
}
- if (isNegative()) { result *= -1; }
+ if (isNegative()) {
+ result = -result;
+ }
return result;
}
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));
}
*/
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();
/**
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.
result.suffix = UnicodeString();
}
result.flags |= fFlags;
+ if (fPrefix != nullptr) {
+ fPrefix->postProcess(result);
+ }
+ if (fSuffix != nullptr) {
+ fSuffix->postProcess(result);
+ }
}
}
for (int32_t i = 0; i < fNumMatchers; i++) {
fMatchers[i]->postProcess(result);
}
+ result.postProcess();
}
void NumberParserImpl::parseGreedyRecursive(StringSegment& segment, ParsedNumber& result,
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);
}
}
double ParsedNumber::getDouble() const {
- bool sawNegative = 0 != (flags & FLAG_NEGATIVE);
bool sawNaN = 0 != (flags & FLAG_NAN);
bool sawInfinity = 0 != (flags & FLAG_INFINITY);
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);
return;
}
if (sawInfinity) {
- if (sawNegative) {
+ if (0 != (flags & FLAG_NEGATIVE)) {
output.setDouble(-INFINITY);
return;
} else {
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) {
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 {
// 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();
private:
UnicodeString fExponentSeparatorString;
DecimalMatcher fExponentMatcher;
+ UnicodeString fCustomMinusSign;
+ UnicodeString fCustomPlusSign;
};
: 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);
}
: 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);
}
PercentMatcher(const DecimalFormatSymbols& dfs);
- void postProcess(ParsedNumber& result) const override;
-
protected:
bool isDisabled(const ParsedNumber& result) const override;
PermilleMatcher(const DecimalFormatSymbols& dfs);
- void postProcess(ParsedNumber& result) const override;
-
protected:
bool isDisabled(const ParsedNumber& result) const override;
*/
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.
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)");
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"
fmt.setFormatWidth(16);
// 12 34567890123456
- expectPat(fmt, "AA*^#,###,##0.00ZZ");
+ expectPat(fmt, "AA*^#####,##0.00ZZ");
}
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);
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
+
*/
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".
setToBigDecimal(temp);
}
+ @Override
+ public void negate() {
+ flags ^= NEGATIVE_FLAG;
+ }
+
@Override
public int getMagnitude() throws ArithmeticException {
if (precision == 0) {
for (int magnitude = scale + precision - 1; magnitude >= 0; magnitude--) {
result = result * 10 + getDigitPos(magnitude - scale);
}
+ if (isNegative()) {
+ result = -result;
+ }
return result;
}
}
result /= DOUBLE_MULTIPLIERS[-i];
}
- if (isNegative())
+ if (isNegative()) {
result = -result;
+ }
return result;
}
result /= 1e22;
result /= DOUBLE_MULTIPLIERS[-delta];
}
- if (isNegative())
- result *= -1;
+ if (isNegative()) {
+ result = -result;
+ }
return result;
}
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]);
}
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.
result.suffix = "";
}
result.flags |= flags;
+ if (prefix != null) {
+ prefix.postProcess(result);
+ }
+ if (suffix != null) {
+ suffix.postProcess(result);
+ }
}
}
for (NumberParseMatcher matcher : matchers) {
matcher.postProcess(result);
}
+ result.postProcess();
}
private void parseGreedyRecursive(StringSegment segment, ParsedNumber result) {
// 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;
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.
}
public Number getNumber(boolean forceBigDecimal) {
- boolean sawNegative = 0 != (flags & FLAG_NEGATIVE);
boolean sawNaN = 0 != (flags & FLAG_NAN);
boolean sawInfinity = 0 != (flags & FLAG_INFINITY);
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;
}
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>";
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>";
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
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?
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
// 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();
}
}
+ @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
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%");
+ }
}