-//##header
/*
*******************************************************************************
- * Copyright (C) 1996-2011, International Business Machines Corporation and *
+ * Copyright (C) 1996-2013, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.math.BigDecimal;
import com.ibm.icu.math.MathContext;
+import com.ibm.icu.text.PluralRules.FixedDecimal;
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.CurrencyAmount;
import com.ibm.icu.util.ULocale;
* {@link #parse(String)} indicates parse failure by throwing a {@link
* java.text.ParseException}.
*
+ * <p>Parsing an extremely large or small absolute value (such as 1.0E10000 or 1.0E-10000)
+ * requires huge memory allocation for representing the parsed number. Such input may expose
+ * a risk of DoS attacks. To prevent huge memory allocation triggered by such inputs,
+ * <code>DecimalFormat</code> internally limits of maximum decimal digits to be 1000. Thus,
+ * an input string resulting more than 1000 digits in plain decimal representation (non-exponent)
+ * will be treated as either overflow (positive/negative infinite) or underflow (+0.0/-0.0).
+ *
* <h4>Formatting</h4>
*
* <p>Formatting is guided by several parameters, all of which can be specified either
* {@inheritDoc}
* @stable ICU 2.0
*/
+ @Override
public StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition) {
return format(number, result, fieldPosition, false);
}
+ // See if number is negative.
+ // usage: isNegative(multiply(numberToBeFormatted));
+ private boolean isNegative(double number) {
+ // Detecting whether a double is negative is easy with the exception of the value
+ // -0.0. This is a double which has a zero mantissa (and exponent), but a negative
+ // sign bit. It is semantically distinct from a zero with a positive sign bit, and
+ // this distinction is important to certain kinds of computations. However, it's a
+ // little tricky to detect, since (-0.0 == 0.0) and !(-0.0 < 0.0). How then, you
+ // may ask, does it behave distinctly from +0.0? Well, 1/(-0.0) ==
+ // -Infinity. Proper detection of -0.0 is needed to deal with the issues raised by
+ // bugs 4106658, 4106667, and 4147706. Liu 7/6/98.
+ return (number < 0.0) || (number == 0.0 && 1 / number < 0.0);
+ }
+
+ // Rounds the number and strips of the negative sign.
+ // usage: round(multiply(numberToBeFormatted))
+ private double round(double number) {
+ boolean isNegative = isNegative(number);
+ if (isNegative)
+ number = -number;
+
+ // Apply rounding after multiplier
+ if (roundingDouble > 0.0) {
+ // number = roundingDouble
+ // * round(number / roundingDouble, roundingMode, isNegative);
+ return round(
+ number, roundingDouble, roundingDoubleReciprocal, roundingMode,
+ isNegative);
+ }
+ return number;
+ }
+
+ // Multiplies given number by multipler (if there is one) returning the new
+ // number. If there is no multiplier, returns the number passed in unchanged.
+ private double multiply(double number) {
+ if (multiplier != 1) {
+ return number * multiplier;
+ }
+ return number;
+ }
+
// [Spark/CDL] The actual method to format number. If boolean value
// parseAttr == true, then attribute information will be recorded.
private StringBuffer format(double number, StringBuffer result, FieldPosition fieldPosition,
return result;
}
- // Do this BEFORE checking to see if value is infinite or negative!
- if (multiplier != 1)
- number *= multiplier;
-
-
- // Detecting whether a double is negative is easy with the exception of the value
- // -0.0. This is a double which has a zero mantissa (and exponent), but a negative
- // sign bit. It is semantically distinct from a zero with a positive sign bit, and
- // this distinction is important to certain kinds of computations. However, it's a
- // little tricky to detect, since (-0.0 == 0.0) and !(-0.0 < 0.0). How then, you
- // may ask, does it behave distinctly from +0.0? Well, 1/(-0.0) ==
- // -Infinity. Proper detection of -0.0 is needed to deal with the issues raised by
- // bugs 4106658, 4106667, and 4147706. Liu 7/6/98.
- boolean isNegative = (number < 0.0) || (number == 0.0 && 1 / number < 0.0);
- if (isNegative)
- number = -number;
-
- // Apply rounding after multiplier
- if (roundingDouble > 0.0) {
- // number = roundingDouble
- // * round(number / roundingDouble, roundingMode, isNegative);
- double newNumber = round(number, roundingDouble, roundingDoubleReciprocal, roundingMode,
- isNegative);
- number = newNumber;
- }
+ // Do this BEFORE checking to see if value is negative or infinite and
+ // before rounding.
+ number = multiply(number);
+ boolean isNegative = isNegative(number);
+ number = round(number);
if (Double.isInfinite(number)) {
int prefixLen = appendAffix(result, isNegative, true, parseAttr);
}
}
+ /**
+ * This is a special function used by the CompactDecimalFormat subclass.
+ * It completes only the rounding portion of the formatting and returns
+ * the resulting double. CompactDecimalFormat uses the result to compute
+ * the plural form to use.
+ *
+ * @param number The number to format.
+ * @return The number rounded to the correct number of significant digits
+ * with negative sign stripped off.
+ * @internal
+ * @deprecated
+ */
+ @Deprecated
+ double adjustNumberAsInFormatting(double number) {
+ if (Double.isNaN(number)) {
+ return number;
+ }
+ number = round(multiply(number));
+ if (Double.isInfinite(number)) {
+ return number;
+ }
+ return toDigitList(number).getDouble();
+ }
+
+ @Deprecated
+ DigitList toDigitList(double number) {
+ DigitList result = new DigitList();
+ result.set(number, precision(false), false);
+ return result;
+ }
+
+ /**
+ * This is a special function used by the CompactDecimalFormat subclass
+ * to determine if the number to be formatted is negative.
+ *
+ * @param number The number to format.
+ * @return True if number is negative.
+ * @internal
+ * @deprecated
+ */
+ @Deprecated
+ boolean isNumberNegative(double number) {
+ if (Double.isNaN(number)) {
+ return false;
+ }
+ return isNegative(multiply(number));
+ }
+
/**
* Round a double value to the nearest multiple of the given rounding increment,
* according to the given mode. This is equivalent to rounding value/roundingInc to
* @stable ICU 2.0
*/
// [Spark/CDL] Delegate to format_long_StringBuffer_FieldPosition_boolean
+ @Override
public StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition) {
return format(number, result, fieldPosition, false);
}
// If we are to do rounding, we need to move into the BigDecimal
// domain in order to do divide/multiply correctly.
- if (roundingIncrementICU != null) {
+ if (actualRoundingIncrementICU != null) {
return format(BigDecimal.valueOf(number), result, fieldPosition);
}
*
* @stable ICU 2.0
*/
+ @Override
public StringBuffer format(BigInteger number, StringBuffer result,
FieldPosition fieldPosition) {
return format(number, result, fieldPosition, false);
boolean parseAttr) {
// If we are to do rounding, we need to move into the BigDecimal
// domain in order to do divide/multiply correctly.
- if (roundingIncrementICU != null) {
+ if (actualRoundingIncrementICU != null) {
return format(new BigDecimal(number), result, fieldPosition);
}
*
* @stable ICU 2.0
*/
+ @Override
public StringBuffer format(java.math.BigDecimal number, StringBuffer result,
FieldPosition fieldPosition) {
return format(number, result, fieldPosition, false);
number = number.multiply(java.math.BigDecimal.valueOf(multiplier));
}
- if (roundingIncrement != null) {
- number = number.divide(roundingIncrement, 0, roundingMode).multiply(roundingIncrement);
+ if (actualRoundingIncrement != null) {
+ number = number.divide(actualRoundingIncrement, 0, roundingMode).multiply(actualRoundingIncrement);
}
synchronized (digitList) {
*
* @stable ICU 2.0
*/
+ @Override
public StringBuffer format(BigDecimal number, StringBuffer result,
FieldPosition fieldPosition) {
// This method is just a copy of the corresponding java.math.BigDecimal method
number = number.multiply(BigDecimal.valueOf(multiplier), mathContext);
}
- if (roundingIncrementICU != null) {
- number = number.divide(roundingIncrementICU, 0, roundingMode)
- .multiply(roundingIncrementICU, mathContext);
+ if (actualRoundingIncrementICU != null) {
+ number = number.divide(actualRoundingIncrementICU, 0, roundingMode)
+ .multiply(actualRoundingIncrementICU, mathContext);
}
synchronized (digitList) {
private StringBuffer subformat(int number, StringBuffer result, FieldPosition fieldPosition,
boolean isNegative, boolean isInteger, boolean parseAttr) {
if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
- return subformat(currencyPluralInfo.select(number), result, fieldPosition, isNegative,
+ // compute the plural category from the digitList plus other settings
+ return subformat(currencyPluralInfo.select(getFixedDecimal(number)),
+ result, fieldPosition, isNegative,
isInteger, parseAttr);
} else {
return subformat(result, fieldPosition, isNegative, isInteger, parseAttr);
}
}
+ /**
+ * This is ugly, but don't see a better way to do it without major restructuring of the code.
+ */
+ /*package*/ FixedDecimal getFixedDecimal(double number) {
+ // get the visible fractions and the number of fraction digits.
+ return getFixedDecimal(number, digitList);
+ }
+
+ FixedDecimal getFixedDecimal(double number, DigitList dl) {
+ int fractionalDigitsInDigitList = dl.count - dl.decimalAt;
+ int v;
+ long f;
+ int maxFractionalDigits;
+ int minFractionalDigits;
+ if (useSignificantDigits) {
+ maxFractionalDigits = maxSignificantDigits - dl.decimalAt;
+ minFractionalDigits = minSignificantDigits - dl.decimalAt;
+ if (minFractionalDigits < 0) {
+ minFractionalDigits = 0;
+ }
+ if (maxFractionalDigits < 0) {
+ maxFractionalDigits = 0;
+ }
+ } else {
+ maxFractionalDigits = getMaximumFractionDigits();
+ minFractionalDigits = getMinimumFractionDigits();
+ }
+ v = fractionalDigitsInDigitList;
+ if (v < minFractionalDigits) {
+ v = minFractionalDigits;
+ } else if (v > maxFractionalDigits) {
+ v = maxFractionalDigits;
+ }
+ f = 0;
+ if (v > 0) {
+ for (int i = Math.max(0, dl.decimalAt); i < dl.count; ++i) {
+ f *= 10;
+ f += (dl.digits[i] - '0');
+ }
+ for (int i = v; i < fractionalDigitsInDigitList; ++i) {
+ f *= 10;
+ }
+ }
+ return new FixedDecimal(number, v, f);
+ }
+
private StringBuffer subformat(double number, StringBuffer result, FieldPosition fieldPosition,
boolean isNegative,
boolean isInteger, boolean parseAttr) {
if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
- return subformat(currencyPluralInfo.select(number), result, fieldPosition, isNegative,
+ // compute the plural category from the digitList plus other settings
+ return subformat(currencyPluralInfo.select(getFixedDecimal(number)),
+ result, fieldPosition, isNegative,
isInteger, parseAttr);
} else {
return subformat(result, fieldPosition, isNegative, isInteger, parseAttr);
// digitList.count = 0;
// }
- int i;
- char [] digits = symbols.getDigitsLocal();
-
- char grouping = currencySignCount > 0 ? symbols.getMonetaryGroupingSeparator() :
- symbols.getGroupingSeparator();
- char decimal = currencySignCount > 0 ? symbols.getMonetaryDecimalSeparator() :
- symbols.getDecimalSeparator();
- boolean useSigDig = areSignificantDigitsUsed();
- int maxIntDig = getMaximumIntegerDigits();
- int minIntDig = getMinimumIntegerDigits();
+
// Per bug 4147706, DecimalFormat must respect the sign of numbers which format as
// zero. This allows sensible computations and preserves relations such as
int prefixLen = appendAffix(result, isNegative, true, parseAttr);
if (useExponentialNotation) {
- // Record field information for caller.
- if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) {
- fieldPosition.setBeginIndex(result.length());
- fieldPosition.setEndIndex(-1);
- } else if (fieldPosition.getField() == NumberFormat.FRACTION_FIELD) {
- fieldPosition.setBeginIndex(-1);
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
- fieldPosition.setBeginIndex(result.length());
- fieldPosition.setEndIndex(-1);
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.FRACTION) {
- fieldPosition.setBeginIndex(-1);
- }
+ subformatExponential(result, fieldPosition, parseAttr);
+ } else {
+ subformatFixed(result, fieldPosition, isInteger, parseAttr);
+ }
- // [Spark/CDL]
- // the begin index of integer part
- // the end index of integer part
- // the begin index of fractional part
- int intBegin = result.length();
- int intEnd = -1;
- int fracBegin = -1;
- int minFracDig = 0;
- if (useSigDig) {
- maxIntDig = minIntDig = 1;
- minFracDig = getMinimumSignificantDigits() - 1;
- } else {
- minFracDig = getMinimumFractionDigits();
- if (maxIntDig > MAX_SCIENTIFIC_INTEGER_DIGITS) {
- maxIntDig = 1;
- if (maxIntDig < minIntDig) {
- maxIntDig = minIntDig;
- }
- }
- if (maxIntDig > minIntDig) {
- minIntDig = 1;
- }
- }
+ int suffixLen = appendAffix(result, isNegative, false, parseAttr);
- // Minimum integer digits are handled in exponential format by adjusting the
- // exponent. For example, 0.01234 with 3 minimum integer digits is "123.4E-4".
+ addPadding(result, fieldPosition, prefixLen, suffixLen);
+ return result;
+ }
- // Maximum integer digits are interpreted as indicating the repeating
- // range. This is useful for engineering notation, in which the exponent is
- // restricted to a multiple of 3. For example, 0.01234 with 3 maximum integer
- // digits is "12.34e-3". If maximum integer digits are defined and are larger
- // than minimum integer digits, then minimum integer digits are ignored.
+ private void subformatFixed(StringBuffer result,
+ FieldPosition fieldPosition,
+ boolean isInteger,
+ boolean parseAttr) {
+ char [] digits = symbols.getDigitsLocal();
- int exponent = digitList.decimalAt;
- if (maxIntDig > 1 && maxIntDig != minIntDig) {
- // A exponent increment is defined; adjust to it.
- exponent = (exponent > 0) ? (exponent - 1) / maxIntDig : (exponent / maxIntDig) - 1;
- exponent *= maxIntDig;
+ char grouping = currencySignCount == CURRENCY_SIGN_COUNT_ZERO ?
+ symbols.getGroupingSeparator(): symbols.getMonetaryGroupingSeparator();
+ char decimal = currencySignCount == CURRENCY_SIGN_COUNT_ZERO ?
+ symbols.getDecimalSeparator() : symbols.getMonetaryDecimalSeparator();
+ boolean useSigDig = areSignificantDigitsUsed();
+ int maxIntDig = getMaximumIntegerDigits();
+ int minIntDig = getMinimumIntegerDigits();
+ int i;
+ // [Spark/CDL] Record the integer start index.
+ int intBegin = result.length();
+ // Record field information for caller.
+ if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) {
+ fieldPosition.setBeginIndex(result.length());
+ } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
+ fieldPosition.setBeginIndex(result.length());
+ }
+ long fractionalDigits = 0;
+ int fractionalDigitsCount = 0;
+ boolean recordFractionDigits = false;
+
+ int sigCount = 0;
+ int minSigDig = getMinimumSignificantDigits();
+ int maxSigDig = getMaximumSignificantDigits();
+ if (!useSigDig) {
+ minSigDig = 0;
+ maxSigDig = Integer.MAX_VALUE;
+ }
+
+ // Output the integer portion. Here 'count' is the total number of integer
+ // digits we will display, including both leading zeros required to satisfy
+ // getMinimumIntegerDigits, and actual digits present in the number.
+ int count = useSigDig ? Math.max(1, digitList.decimalAt) : minIntDig;
+ if (digitList.decimalAt > 0 && count < digitList.decimalAt) {
+ count = digitList.decimalAt;
+ }
+
+ // Handle the case where getMaximumIntegerDigits() is smaller than the real
+ // number of integer digits. If this is so, we output the least significant
+ // max integer digits. For example, the value 1997 printed with 2 max integer
+ // digits is just "97".
+
+ int digitIndex = 0; // Index into digitList.fDigits[]
+ if (count > maxIntDig && maxIntDig >= 0) {
+ count = maxIntDig;
+ digitIndex = digitList.decimalAt - count;
+ }
+
+ int sizeBeforeIntegerPart = result.length();
+ for (i = count - 1; i >= 0; --i) {
+ if (i < digitList.decimalAt && digitIndex < digitList.count
+ && sigCount < maxSigDig) {
+ // Output a real digit
+ result.append(digits[digitList.getDigitValue(digitIndex++)]);
+ ++sigCount;
} else {
- // No exponent increment is defined; use minimum integer digits.
- // If none is specified, as in "#E0", generate 1 integer digit.
- exponent -= (minIntDig > 0 || minFracDig > 0) ? minIntDig : 1;
- }
-
- // We now output a minimum number of digits, and more if there are more
- // digits, up to the maximum number of digits. We place the decimal point
- // after the "integer" digits, which are the first (decimalAt - exponent)
- // digits.
- int minimumDigits = minIntDig + minFracDig;
- // The number of integer digits is handled specially if the number
- // is zero, since then there may be no digits.
- int integerDigits = digitList.isZero() ? minIntDig : digitList.decimalAt - exponent;
- int totalDigits = digitList.count;
- if (minimumDigits > totalDigits)
- totalDigits = minimumDigits;
- if (integerDigits > totalDigits)
- totalDigits = integerDigits;
-
- for (i = 0; i < totalDigits; ++i) {
- if (i == integerDigits) {
- // Record field information for caller.
- if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) {
- fieldPosition.setEndIndex(result.length());
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
- fieldPosition.setEndIndex(result.length());
- }
-
- // [Spark/CDL] Add attribute for integer part
- if (parseAttr) {
- intEnd = result.length();
- addAttribute(Field.INTEGER, intBegin, result.length());
- }
- result.append(decimal);
- // [Spark/CDL] Add attribute for decimal separator
- if (parseAttr) {
- // Length of decimal separator is 1.
- int decimalSeparatorBegin = result.length() - 1;
- addAttribute(Field.DECIMAL_SEPARATOR, decimalSeparatorBegin,
- result.length());
- fracBegin = result.length();
- }
- // Record field information for caller.
- if (fieldPosition.getField() == NumberFormat.FRACTION_FIELD) {
- fieldPosition.setBeginIndex(result.length());
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.FRACTION) {
- fieldPosition.setBeginIndex(result.length());
- }
- }
- result.append((i < digitList.count)
- ? digits[digitList.getDigitValue(i)]
- : digits[0]);
- }
-
- // For ICU compatibility and format 0 to 0E0 with pattern "#E0" [Richard/GCL]
- if (digitList.isZero() && (totalDigits == 0)) {
+ // Output a zero (leading or trailing)
result.append(digits[0]);
- }
-
- // Record field information
- if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) {
- if (fieldPosition.getEndIndex() < 0) {
- fieldPosition.setEndIndex(result.length());
- }
- } else if (fieldPosition.getField() == NumberFormat.FRACTION_FIELD) {
- if (fieldPosition.getBeginIndex() < 0) {
- fieldPosition.setBeginIndex(result.length());
- }
- fieldPosition.setEndIndex(result.length());
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
- if (fieldPosition.getEndIndex() < 0) {
- fieldPosition.setEndIndex(result.length());
- }
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.FRACTION) {
- if (fieldPosition.getBeginIndex() < 0) {
- fieldPosition.setBeginIndex(result.length());
- }
- fieldPosition.setEndIndex(result.length());
- }
-
- // [Spark/CDL] Calcuate the end index of integer part and fractional
- // part if they are not properly processed yet.
- if (parseAttr) {
- if (intEnd < 0) {
- addAttribute(Field.INTEGER, intBegin, result.length());
- }
- if (fracBegin > 0) {
- addAttribute(Field.FRACTION, fracBegin, result.length());
+ if (sigCount > 0) {
+ ++sigCount;
}
}
- // The exponent is output using the pattern-specified minimum exponent
- // digits. There is no maximum limit to the exponent digits, since truncating
- // the exponent would result in an unacceptable inaccuracy.
- result.append(symbols.getExponentSeparator());
- // [Spark/CDL] For exponent symbol, add an attribute.
- if (parseAttr) {
- addAttribute(Field.EXPONENT_SYMBOL, result.length() -
- symbols.getExponentSeparator().length(), result.length());
- }
- // For zero values, we force the exponent to zero. We must do this here, and
- // not earlier, because the value is used to determine integer digit count
- // above.
- if (digitList.isZero())
- exponent = 0;
-
- boolean negativeExponent = exponent < 0;
- if (negativeExponent) {
- exponent = -exponent;
- result.append(symbols.getMinusSign());
- // [Spark/CDL] If exponent has sign, then add an exponent sign
- // attribute.
- if (parseAttr) {
- // Length of exponent sign is 1.
- addAttribute(Field.EXPONENT_SIGN, result.length() - 1, result.length());
- }
- } else if (exponentSignAlwaysShown) {
- result.append(symbols.getPlusSign());
- // [Spark/CDL] Add an plus sign attribute.
+ // Output grouping separator if necessary.
+ if (isGroupingPosition(i)) {
+ result.append(grouping);
+ // [Spark/CDL] Add grouping separator attribute here.
if (parseAttr) {
- // Length of exponent sign is 1.
- int expSignBegin = result.length() - 1;
- addAttribute(Field.EXPONENT_SIGN, expSignBegin, result.length());
+ // Length of grouping separator is 1.
+ addAttribute(Field.GROUPING_SEPARATOR, result.length() - 1, result.length());
}
}
- int expBegin = result.length();
- digitList.set(exponent);
- {
- int expDig = minExponentDigits;
- if (useExponentialNotation && expDig < 1) {
- expDig = 1;
- }
- for (i = digitList.decimalAt; i < expDig; ++i)
- result.append(digits[0]);
+ }
+
+ // Record field information for caller.
+ if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) {
+ fieldPosition.setEndIndex(result.length());
+ } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
+ fieldPosition.setEndIndex(result.length());
+ }
+
+ // This handles the special case of formatting 0. For zero only, we count the
+ // zero to the left of the decimal point as one signficant digit. Ordinarily we
+ // do not count any leading 0's as significant. If the number we are formatting
+ // is not zero, then either sigCount or digits.getCount() will be non-zero.
+ if (sigCount == 0 && digitList.count == 0) {
+ sigCount = 1;
+ }
+
+ // Determine whether or not there are any printable fractional digits. If
+ // we've used up the digits we know there aren't.
+ boolean fractionPresent = (!isInteger && digitIndex < digitList.count)
+ || (useSigDig ? (sigCount < minSigDig) : (getMinimumFractionDigits() > 0));
+
+ // If there is no fraction present, and we haven't printed any integer digits,
+ // then print a zero. Otherwise we won't print _any_ digits, and we won't be
+ // able to parse this string.
+ if (!fractionPresent && result.length() == sizeBeforeIntegerPart)
+ result.append(digits[0]);
+ // [Spark/CDL] Add attribute for integer part.
+ if (parseAttr) {
+ addAttribute(Field.INTEGER, intBegin, result.length());
+ }
+ // Output the decimal separator if we always do so.
+ if (decimalSeparatorAlwaysShown || fractionPresent) {
+ if (fieldPosition.getFieldAttribute() == Field.DECIMAL_SEPARATOR) {
+ fieldPosition.setBeginIndex(result.length());
}
- for (i = 0; i < digitList.decimalAt; ++i) {
- result.append((i < digitList.count) ? digits[digitList.getDigitValue(i)]
- : digits[0]);
+ result.append(decimal);
+ if (fieldPosition.getFieldAttribute() == Field.DECIMAL_SEPARATOR) {
+ fieldPosition.setEndIndex(result.length());
}
- // [Spark/CDL] Add attribute for exponent part.
+ // [Spark/CDL] Add attribute for decimal separator
if (parseAttr) {
- addAttribute(Field.EXPONENT, expBegin, result.length());
- }
- } else {
- // [Spark/CDL] Record the integer start index.
- int intBegin = result.length();
- // Record field information for caller.
- if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) {
- fieldPosition.setBeginIndex(result.length());
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
- fieldPosition.setBeginIndex(result.length());
- }
-
- int sigCount = 0;
- int minSigDig = getMinimumSignificantDigits();
- int maxSigDig = getMaximumSignificantDigits();
- if (!useSigDig) {
- minSigDig = 0;
- maxSigDig = Integer.MAX_VALUE;
+ addAttribute(Field.DECIMAL_SEPARATOR, result.length() - 1, result.length());
}
+ }
- // Output the integer portion. Here 'count' is the total number of integer
- // digits we will display, including both leading zeros required to satisfy
- // getMinimumIntegerDigits, and actual digits present in the number.
- int count = useSigDig ? Math.max(1, digitList.decimalAt) : minIntDig;
- if (digitList.decimalAt > 0 && count < digitList.decimalAt) {
- count = digitList.decimalAt;
- }
+ // Record field information for caller.
+ if (fieldPosition.getField() == NumberFormat.FRACTION_FIELD) {
+ fieldPosition.setBeginIndex(result.length());
+ } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.FRACTION) {
+ fieldPosition.setBeginIndex(result.length());
+ }
- // Handle the case where getMaximumIntegerDigits() is smaller than the real
- // number of integer digits. If this is so, we output the least significant
- // max integer digits. For example, the value 1997 printed with 2 max integer
- // digits is just "97".
+ // [Spark/CDL] Record the begin index of fraction part.
+ int fracBegin = result.length();
+ recordFractionDigits = fieldPosition instanceof UFieldPosition;
- int digitIndex = 0; // Index into digitList.fDigits[]
- if (count > maxIntDig && maxIntDig >= 0) {
- count = maxIntDig;
- digitIndex = digitList.decimalAt - count;
+ count = useSigDig ? Integer.MAX_VALUE : getMaximumFractionDigits();
+ if (useSigDig && (sigCount == maxSigDig ||
+ (sigCount >= minSigDig && digitIndex == digitList.count))) {
+ count = 0;
+ }
+ for (i = 0; i < count; ++i) {
+ // Here is where we escape from the loop. We escape if we've output the
+ // maximum fraction digits (specified in the for expression above). We
+ // also stop when we've output the minimum digits and either: we have an
+ // integer, so there is no fractional stuff to display, or we're out of
+ // significant digits.
+ if (!useSigDig && i >= getMinimumFractionDigits() &&
+ (isInteger || digitIndex >= digitList.count)) {
+ break;
}
- int sizeBeforeIntegerPart = result.length();
- for (i = count - 1; i >= 0; --i) {
- if (i < digitList.decimalAt && digitIndex < digitList.count
- && sigCount < maxSigDig) {
- // Output a real digit
- result.append(digits[digitList.getDigitValue(digitIndex++)]);
- ++sigCount;
- } else {
- // Output a zero (leading or trailing)
- result.append(digits[0]);
- if (sigCount > 0) {
- ++sigCount;
- }
+ // Output leading fractional zeros. These are zeros that come after the
+ // decimal but before any significant digits. These are only output if
+ // abs(number being formatted) < 1.0.
+ if (-1 - i > (digitList.decimalAt - 1)) {
+ result.append(digits[0]);
+ if (recordFractionDigits) {
+ ++fractionalDigitsCount;
+ fractionalDigits *= 10;
}
+ continue;
+ }
- // Output grouping separator if necessary.
- if (isGroupingPosition(i)) {
- result.append(grouping);
- // [Spark/CDL] Add grouping separator attribute here.
- if (parseAttr) {
- // Length of grouping separator is 1.
- addAttribute(Field.GROUPING_SEPARATOR, result.length() - 1, result.length());
- }
+ // Output a digit, if we have any precision left, or a zero if we
+ // don't. We don't want to output noise digits.
+ if (!isInteger && digitIndex < digitList.count) {
+ byte digit = digitList.getDigitValue(digitIndex++);
+ result.append(digits[digit]);
+ if (recordFractionDigits) {
+ ++fractionalDigitsCount;
+ fractionalDigits *= 10;
+ fractionalDigits += digit;
+ }
+ } else {
+ result.append(digits[0]);
+ if (recordFractionDigits) {
+ ++fractionalDigitsCount;
+ fractionalDigits *= 10;
}
}
- // Record field information for caller.
- if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) {
- fieldPosition.setEndIndex(result.length());
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
- fieldPosition.setEndIndex(result.length());
+ // If we reach the maximum number of significant digits, or if we output
+ // all the real digits and reach the minimum, then we are done.
+ ++sigCount;
+ if (useSigDig && (sigCount == maxSigDig ||
+ (digitIndex == digitList.count && sigCount >= minSigDig))) {
+ break;
}
+ }
- // Determine whether or not there are any printable fractional digits. If
- // we've used up the digits we know there aren't.
- boolean fractionPresent = (!isInteger && digitIndex < digitList.count)
- || (useSigDig ? (sigCount < minSigDig) : (getMinimumFractionDigits() > 0));
+ // Record field information for caller.
+ if (fieldPosition.getField() == NumberFormat.FRACTION_FIELD) {
+ fieldPosition.setEndIndex(result.length());
+ } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.FRACTION) {
+ fieldPosition.setEndIndex(result.length());
+ }
+ if (recordFractionDigits) {
+ ((UFieldPosition) fieldPosition).setFractionDigits(fractionalDigitsCount, fractionalDigits);
+ }
- // If there is no fraction present, and we haven't printed any integer digits,
- // then print a zero. Otherwise we won't print _any_ digits, and we won't be
- // able to parse this string.
- if (!fractionPresent && result.length() == sizeBeforeIntegerPart)
- result.append(digits[0]);
- // [Spark/CDL] Add attribute for integer part.
- if (parseAttr) {
- addAttribute(Field.INTEGER, intBegin, result.length());
- }
- // Output the decimal separator if we always do so.
- if (decimalSeparatorAlwaysShown || fractionPresent) {
- result.append(decimal);
- // [Spark/CDL] Add attribute for decimal separator
- if (parseAttr) {
- addAttribute(Field.DECIMAL_SEPARATOR, result.length() - 1, result.length());
+ // [Spark/CDL] Add attribute information if necessary.
+ if (parseAttr && (decimalSeparatorAlwaysShown || fractionPresent)) {
+ addAttribute(Field.FRACTION, fracBegin, result.length());
+ }
+ }
+
+ private void subformatExponential(StringBuffer result,
+ FieldPosition fieldPosition,
+ boolean parseAttr) {
+ char [] digits = symbols.getDigitsLocal();
+ char decimal = currencySignCount == CURRENCY_SIGN_COUNT_ZERO ?
+ symbols.getDecimalSeparator() : symbols.getMonetaryDecimalSeparator();
+ boolean useSigDig = areSignificantDigitsUsed();
+ int maxIntDig = getMaximumIntegerDigits();
+ int minIntDig = getMinimumIntegerDigits();
+ int i;
+ // Record field information for caller.
+ if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) {
+ fieldPosition.setBeginIndex(result.length());
+ fieldPosition.setEndIndex(-1);
+ } else if (fieldPosition.getField() == NumberFormat.FRACTION_FIELD) {
+ fieldPosition.setBeginIndex(-1);
+ } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
+ fieldPosition.setBeginIndex(result.length());
+ fieldPosition.setEndIndex(-1);
+ } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.FRACTION) {
+ fieldPosition.setBeginIndex(-1);
+ }
+
+
+ // [Spark/CDL]
+ // the begin index of integer part
+ // the end index of integer part
+ // the begin index of fractional part
+ int intBegin = result.length();
+ int intEnd = -1;
+ int fracBegin = -1;
+ int minFracDig = 0;
+ if (useSigDig) {
+ maxIntDig = minIntDig = 1;
+ minFracDig = getMinimumSignificantDigits() - 1;
+ } else {
+ minFracDig = getMinimumFractionDigits();
+ if (maxIntDig > MAX_SCIENTIFIC_INTEGER_DIGITS) {
+ maxIntDig = 1;
+ if (maxIntDig < minIntDig) {
+ maxIntDig = minIntDig;
}
}
-
- // Record field information for caller.
- if (fieldPosition.getField() == NumberFormat.FRACTION_FIELD) {
- fieldPosition.setBeginIndex(result.length());
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.FRACTION) {
- fieldPosition.setBeginIndex(result.length());
+ if (maxIntDig > minIntDig) {
+ minIntDig = 1;
}
+ }
+ long fractionalDigits = 0;
+ int fractionalDigitsCount = 0;
+ boolean recordFractionDigits = false;
- // [Spark/CDL] Record the begin index of fraction part.
- int fracBegin = result.length();
+ // Minimum integer digits are handled in exponential format by adjusting the
+ // exponent. For example, 0.01234 with 3 minimum integer digits is "123.4E-4".
- count = useSigDig ? Integer.MAX_VALUE : getMaximumFractionDigits();
- if (useSigDig && (sigCount == maxSigDig ||
- (sigCount >= minSigDig && digitIndex == digitList.count))) {
- count = 0;
- }
- for (i = 0; i < count; ++i) {
- // Here is where we escape from the loop. We escape if we've output the
- // maximum fraction digits (specified in the for expression above). We
- // also stop when we've output the minimum digits and either: we have an
- // integer, so there is no fractional stuff to display, or we're out of
- // significant digits.
- if (!useSigDig && i >= getMinimumFractionDigits() &&
- (isInteger || digitIndex >= digitList.count)) {
- break;
- }
+ // Maximum integer digits are interpreted as indicating the repeating
+ // range. This is useful for engineering notation, in which the exponent is
+ // restricted to a multiple of 3. For example, 0.01234 with 3 maximum integer
+ // digits is "12.34e-3". If maximum integer digits are defined and are larger
+ // than minimum integer digits, then minimum integer digits are ignored.
- // Output leading fractional zeros. These are zeros that come after the
- // decimal but before any significant digits. These are only output if
- // abs(number being formatted) < 1.0.
- if (-1 - i > (digitList.decimalAt - 1)) {
- result.append(digits[0]);
- continue;
+ int exponent = digitList.decimalAt;
+ if (maxIntDig > 1 && maxIntDig != minIntDig) {
+ // A exponent increment is defined; adjust to it.
+ exponent = (exponent > 0) ? (exponent - 1) / maxIntDig : (exponent / maxIntDig) - 1;
+ exponent *= maxIntDig;
+ } else {
+ // No exponent increment is defined; use minimum integer digits.
+ // If none is specified, as in "#E0", generate 1 integer digit.
+ exponent -= (minIntDig > 0 || minFracDig > 0) ? minIntDig : 1;
+ }
+
+ // We now output a minimum number of digits, and more if there are more
+ // digits, up to the maximum number of digits. We place the decimal point
+ // after the "integer" digits, which are the first (decimalAt - exponent)
+ // digits.
+ int minimumDigits = minIntDig + minFracDig;
+ // The number of integer digits is handled specially if the number
+ // is zero, since then there may be no digits.
+ int integerDigits = digitList.isZero() ? minIntDig : digitList.decimalAt - exponent;
+ int totalDigits = digitList.count;
+ if (minimumDigits > totalDigits)
+ totalDigits = minimumDigits;
+ if (integerDigits > totalDigits)
+ totalDigits = integerDigits;
+
+ for (i = 0; i < totalDigits; ++i) {
+ if (i == integerDigits) {
+ // Record field information for caller.
+ if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) {
+ fieldPosition.setEndIndex(result.length());
+ } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
+ fieldPosition.setEndIndex(result.length());
}
- // Output a digit, if we have any precision left, or a zero if we
- // don't. We don't want to output noise digits.
- if (!isInteger && digitIndex < digitList.count) {
- result.append(digits[digitList.getDigitValue(digitIndex++)]);
- } else {
- result.append(digits[0]);
+ // [Spark/CDL] Add attribute for integer part
+ if (parseAttr) {
+ intEnd = result.length();
+ addAttribute(Field.INTEGER, intBegin, result.length());
}
-
- // If we reach the maximum number of significant digits, or if we output
- // all the real digits and reach the minimum, then we are done.
- ++sigCount;
- if (useSigDig && (sigCount == maxSigDig ||
- (digitIndex == digitList.count && sigCount >= minSigDig))) {
- break;
+ result.append(decimal);
+ // [Spark/CDL] Add attribute for decimal separator
+ if (parseAttr) {
+ // Length of decimal separator is 1.
+ int decimalSeparatorBegin = result.length() - 1;
+ addAttribute(Field.DECIMAL_SEPARATOR, decimalSeparatorBegin,
+ result.length());
+ fracBegin = result.length();
+ }
+ // Record field information for caller.
+ if (fieldPosition.getField() == NumberFormat.FRACTION_FIELD) {
+ fieldPosition.setBeginIndex(result.length());
+ } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.FRACTION) {
+ fieldPosition.setBeginIndex(result.length());
}
+ recordFractionDigits = fieldPosition instanceof UFieldPosition;
+
+ }
+ byte digit = (i < digitList.count) ? digitList.getDigitValue(i) : (byte)0;
+ result.append(digits[digit]);
+ if (recordFractionDigits) {
+ ++fractionalDigitsCount;
+ fractionalDigits *= 10;
+ fractionalDigits += digit;
}
+ }
+
+ // For ICU compatibility and format 0 to 0E0 with pattern "#E0" [Richard/GCL]
+ if (digitList.isZero() && (totalDigits == 0)) {
+ result.append(digits[0]);
+ }
- // Record field information for caller.
- if (fieldPosition.getField() == NumberFormat.FRACTION_FIELD) {
+ // Record field information
+ if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) {
+ if (fieldPosition.getEndIndex() < 0) {
fieldPosition.setEndIndex(result.length());
- } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.FRACTION) {
+ }
+ } else if (fieldPosition.getField() == NumberFormat.FRACTION_FIELD) {
+ if (fieldPosition.getBeginIndex() < 0) {
+ fieldPosition.setBeginIndex(result.length());
+ }
+ fieldPosition.setEndIndex(result.length());
+ } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
+ if (fieldPosition.getEndIndex() < 0) {
fieldPosition.setEndIndex(result.length());
}
+ } else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.FRACTION) {
+ if (fieldPosition.getBeginIndex() < 0) {
+ fieldPosition.setBeginIndex(result.length());
+ }
+ fieldPosition.setEndIndex(result.length());
+ }
+ if (recordFractionDigits) {
+ ((UFieldPosition) fieldPosition).setFractionDigits(fractionalDigitsCount, fractionalDigits);
+ }
- // [Spark/CDL] Add attribute information if necessary.
- if (parseAttr && (decimalSeparatorAlwaysShown || fractionPresent)) {
+ // [Spark/CDL] Calcuate the end index of integer part and fractional
+ // part if they are not properly processed yet.
+ if (parseAttr) {
+ if (intEnd < 0) {
+ addAttribute(Field.INTEGER, intBegin, result.length());
+ }
+ if (fracBegin > 0) {
addAttribute(Field.FRACTION, fracBegin, result.length());
}
}
- int suffixLen = appendAffix(result, isNegative, false, parseAttr);
-
- addPadding(result, fieldPosition, prefixLen, suffixLen);
- return result;
+ // The exponent is output using the pattern-specified minimum exponent
+ // digits. There is no maximum limit to the exponent digits, since truncating
+ // the exponent would result in an unacceptable inaccuracy.
+ result.append(symbols.getExponentSeparator());
+ // [Spark/CDL] For exponent symbol, add an attribute.
+ if (parseAttr) {
+ addAttribute(Field.EXPONENT_SYMBOL, result.length() -
+ symbols.getExponentSeparator().length(), result.length());
+ }
+ // For zero values, we force the exponent to zero. We must do this here, and
+ // not earlier, because the value is used to determine integer digit count
+ // above.
+ if (digitList.isZero())
+ exponent = 0;
+
+ boolean negativeExponent = exponent < 0;
+ if (negativeExponent) {
+ exponent = -exponent;
+ result.append(symbols.getMinusSign());
+ // [Spark/CDL] If exponent has sign, then add an exponent sign
+ // attribute.
+ if (parseAttr) {
+ // Length of exponent sign is 1.
+ addAttribute(Field.EXPONENT_SIGN, result.length() - 1, result.length());
+ }
+ } else if (exponentSignAlwaysShown) {
+ result.append(symbols.getPlusSign());
+ // [Spark/CDL] Add an plus sign attribute.
+ if (parseAttr) {
+ // Length of exponent sign is 1.
+ int expSignBegin = result.length() - 1;
+ addAttribute(Field.EXPONENT_SIGN, expSignBegin, result.length());
+ }
+ }
+ int expBegin = result.length();
+ digitList.set(exponent);
+ {
+ int expDig = minExponentDigits;
+ if (useExponentialNotation && expDig < 1) {
+ expDig = 1;
+ }
+ for (i = digitList.decimalAt; i < expDig; ++i)
+ result.append(digits[0]);
+ }
+ for (i = 0; i < digitList.decimalAt; ++i) {
+ result.append((i < digitList.count) ? digits[digitList.getDigitValue(i)]
+ : digits[0]);
+ }
+ // [Spark/CDL] Add attribute for exponent part.
+ if (parseAttr) {
+ addAttribute(Field.EXPONENT, expBegin, result.length());
+ }
}
private final void addPadding(StringBuffer result, FieldPosition fieldPosition, int prefixLen,
* <code>null</code> if the parse failed
* @stable ICU 2.0
*/
+ @Override
public Number parse(String text, ParsePosition parsePosition) {
- return (Number) parse(text, parsePosition, false);
+ return (Number) parse(text, parsePosition, null);
}
/**
* code. This method will fail if this format is not a currency format, that is, if it
* does not contain the currency pattern symbol (U+00A4) in its prefix or suffix.
*
- * @param text the string to parse
+ * @param text the text to parse
* @param pos input-output position; on input, the position within text to match; must
* have 0 <= pos.getIndex() < text.length(); on output, the position after the last
* matched character. If the parse fails, the position in unchanged upon output.
* @return a CurrencyAmount, or null upon failure
- * @internal
+ * @stable ICU 49
*/
- public CurrencyAmount parseCurrency(String text, ParsePosition pos) {
- return (CurrencyAmount) parse(text, pos, true);
+ @Override
+ public CurrencyAmount parseCurrency(CharSequence text, ParsePosition pos) {
+ Currency[] currency = new Currency[1];
+ return (CurrencyAmount) parse(text.toString(), pos, currency);
}
/**
* match; must have 0 <= pos.getIndex() < text.length(); on output, the position after
* the last matched character. If the parse fails, the position in unchanged upon
* output.
- * @param parseCurrency if true, a CurrencyAmount is parsed and returned; otherwise a
+ * @param currency if non-null, a CurrencyAmount is parsed and returned; otherwise a
* Number is parsed and returned
* @return a Number or CurrencyAmount or null
*/
- private Object parse(String text, ParsePosition parsePosition, boolean parseCurrency) {
+ private Object parse(String text, ParsePosition parsePosition, Currency[] currency) {
int backup;
int i = backup = parsePosition.getIndex();
i = backup;
boolean[] status = new boolean[STATUS_LENGTH];
- Currency[] currency = parseCurrency ? new Currency[1] : null;
- if (currencySignCount > 0) {
- if (!parseForCurrency(text, parsePosition, parseCurrency, currency, status)) {
+ if (currencySignCount != CURRENCY_SIGN_COUNT_ZERO) {
+ if (!parseForCurrency(text, parsePosition, currency, status)) {
return null;
}
} else {
if (!subparse(text, parsePosition, digitList, status, currency, negPrefixPattern,
negSuffixPattern, posPrefixPattern, posSuffixPattern,
- Currency.SYMBOL_NAME)) {
+ false, Currency.SYMBOL_NAME)) {
parsePosition.setIndex(backup);
return null;
}
l = -l;
}
}
- n = new Long(l);
+ n = Long.valueOf(l);
} else {
BigInteger big = digitList.getBigInteger(status[STATUS_POSITIVE]);
- n = (big.bitLength() < 64) ? (Number) new Long(big.longValue()) : (Number) big;
+ n = (big.bitLength() < 64) ? (Number) Long.valueOf(big.longValue()) : (Number) big;
}
}
// Handle non-integral values or the case where parseBigDecimal is set
}
// Assemble into CurrencyAmount if necessary
- return parseCurrency ? (Object) new CurrencyAmount(n, currency[0]) : (Object) n;
+ return (currency != null) ? (Object) new CurrencyAmount(n, currency[0]) : (Object) n;
}
- private boolean parseForCurrency(String text, ParsePosition parsePosition, boolean parseCurrency,
+ private boolean parseForCurrency(String text, ParsePosition parsePosition,
Currency[] currency, boolean[] status) {
int origPos = parsePosition.getIndex();
if (!isReadyForParsing) {
if (style == NumberFormat.PLURALCURRENCYSTYLE) {
found = subparse(text, tmpPos, tmpDigitList, tmpStatus, currency,
negPrefixPattern, negSuffixPattern, posPrefixPattern, posSuffixPattern,
- Currency.LONG_NAME);
+ true, Currency.LONG_NAME);
} else {
found = subparse(text, tmpPos, tmpDigitList, tmpStatus, currency,
negPrefixPattern, negSuffixPattern, posPrefixPattern, posSuffixPattern,
- Currency.SYMBOL_NAME);
+ true, Currency.SYMBOL_NAME);
}
if (found) {
if (tmpPos.getIndex() > maxPosIndex) {
boolean result = subparse(text, tmpPos, tmpDigitList, tmpStatus, currency,
affix.getNegPrefix(), affix.getNegSuffix(),
affix.getPosPrefix(), affix.getPosSuffix(),
- affix.getPatternType());
+ true, affix.getPatternType());
if (result) {
found = true;
if (tmpPos.getIndex() > maxPosIndex) {
// complexAffixCompare will not find match, since there is no ISO code matches
// "\u00A4", and the parse stops at "\u00A4". We will just use simple affix
// comparison (look for exact match) to pass it.
+ //
+ // TODO: We should parse against simple affix first when
+ // output currency is not requested. After the complex currency
+ // parsing implementation was introduced, the default currency
+ // instance parsing slowed down because of the new code flow.
+ // I filed #10312 - Yoshito
tmpStatus = new boolean[STATUS_LENGTH];
tmpPos = new ParsePosition(origPos);
tmpDigitList = new DigitList();
- int savedCurrencySignCount = currencySignCount;
- // set currencySignCount to 0 so that compareAffix function will fall to
- // compareSimpleAffix path, not compareComplexAffix path.
- currencySignCount = 0;
+
+ // Disable complex currency parsing and try it again.
boolean result = subparse(text, tmpPos, tmpDigitList, tmpStatus, currency,
negativePrefix, negativeSuffix, positivePrefix, positiveSuffix,
- Currency.SYMBOL_NAME);
- currencySignCount = savedCurrencySignCount;
+ false /* disable complex currency parsing */, Currency.SYMBOL_NAME);
if (result) {
if (tmpPos.getIndex() > maxPosIndex) {
maxPosIndex = tmpPos.getIndex();
formatPattern = savedFormatPattern;
}
+ // currency formatting style options
+ private static final int CURRENCY_SIGN_COUNT_ZERO = 0;
private static final int CURRENCY_SIGN_COUNT_IN_SYMBOL_FORMAT = 1;
private static final int CURRENCY_SIGN_COUNT_IN_ISO_FORMAT = 2;
private static final int CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT = 3;
0xFF0C, 0xFF0C,
0xFF0E, 0xFF0E,
0xFF61, 0xFF61).freeze();
+
+ private static final UnicodeSet minusSigns =
+ new UnicodeSet(
+ 0x002D, 0x002D,
+ 0x207B, 0x207B,
+ 0x208B, 0x208B,
+ 0x2212, 0x2212,
+ 0x2796, 0x2796,
+ 0xFE63, 0xFE63,
+ 0xFF0D, 0xFF0D).freeze();
+
+ private static final UnicodeSet plusSigns =
+ new UnicodeSet(
+ 0x002B, 0x002B,
+ 0x207A, 0x207A,
+ 0x208A, 0x208A,
+ 0x2795, 0x2795,
+ 0xFB29, 0xFB29,
+ 0xFE62, 0xFE62,
+ 0xFF0B, 0xFF0B).freeze();
+
// When parsing a number with big exponential value, it requires to transform the
// value into a string representation to construct BigInteger instance. We want to
// set the maximum size because it can easily trigger OutOfMemoryException.
- // PARSE_MAX_EXPONENT is currently set to 1000, which is much bigger than MAX_VALUE of
- // Double ( See the problem reported by ticket#5698
- private static final int PARSE_MAX_EXPONENT = 1000;
+ // PARSE_MAX_EXPONENT is currently set to 1000 (See getParseMaxDigits()),
+ // which is much bigger than MAX_VALUE of Double ( See the problem reported by ticket#5698
+ private int PARSE_MAX_EXPONENT = 1000;
/**
* Parses the given text into a number. The text is parsed beginning at parsePosition,
* @param negSuffix negative suffix pattern
* @param posPrefix positive prefix pattern
* @param negSuffix negative suffix pattern
+ * @param complexCurrencyParsing whether it is complex currency parsing or not.
* @param type type of currency to parse against, LONG_NAME only or not.
*/
private final boolean subparse(
String text, ParsePosition parsePosition, DigitList digits,
boolean status[], Currency currency[], String negPrefix, String negSuffix, String posPrefix,
- String posSuffix, int type) {
+ String posSuffix, boolean parseComplexCurrency, int type) {
int position = parsePosition.getIndex();
int oldStart = parsePosition.getIndex();
}
// Match positive and negative prefixes; prefer longest match.
- int posMatch = compareAffix(text, position, false, true, posPrefix, type, currency);
- int negMatch = compareAffix(text, position, true, true, negPrefix, type, currency);
+ int posMatch = compareAffix(text, position, false, true, posPrefix, parseComplexCurrency, type, currency);
+ int negMatch = compareAffix(text, position, true, true, negPrefix, parseComplexCurrency, type, currency);
if (posMatch >= 0 && negMatch >= 0) {
if (posMatch > negMatch) {
negMatch = -1;
digits.decimalAt = digits.count = 0;
char [] digitSymbols = symbols.getDigitsLocal();
- char decimal = currencySignCount > 0 ? symbols.getMonetaryDecimalSeparator() : symbols
- .getDecimalSeparator();
- char grouping = symbols.getGroupingSeparator();
+ char decimal = (currencySignCount == CURRENCY_SIGN_COUNT_ZERO) ?
+ symbols.getDecimalSeparator() : symbols.getMonetaryDecimalSeparator();
+ char grouping = (currencySignCount == CURRENCY_SIGN_COUNT_ZERO) ?
+ symbols.getGroupingSeparator() : symbols.getMonetaryGroupingSeparator();
String exponentSep = symbols.getExponentSeparator();
boolean sawDecimal = false;
boolean strictParse = isParseStrict();
boolean strictFail = false; // did we exit with a strict parse failure?
int lastGroup = -1; // where did we last see a grouping separator?
+ int digitStart = position; // where did the digit start?
int gs2 = groupingSize2 == 0 ? groupingSize : groupingSize2;
// equivalent grouping and decimal support
break;
}
}
-
+
if (digit == 0) {
// group. If there was a group separator before that, the group
// must == the secondary group length, else it can be <= the the
// secondary group length.
- if ((lastGroup != -1 && countCodePoints(text,lastGroup,backup) - 1 != gs2)
- || (lastGroup == -1 && countCodePoints(text,oldStart,position) - 1 > gs2)) {
+ if ((lastGroup != -1 && countCodePoints(text, lastGroup, backup) - 1 != gs2)
+ || (lastGroup == -1 && countCodePoints(text, digitStart, position) - 1 > gs2)) {
strictFail = true;
break;
}
{
if (strictParse) {
if (backup != -1) {
- if ((lastGroup != -1 && countCodePoints(text,lastGroup,backup) - 1 != gs2)
- || (lastGroup == -1 && countCodePoints(text,oldStart,position) - 1 > gs2)) {
+ if ((lastGroup != -1 && countCodePoints(text, lastGroup, backup) - 1 != gs2)
+ || (lastGroup == -1 && countCodePoints(text, digitStart, position) - 1 > gs2)) {
strictFail = true;
break;
}
// Adjust for exponent, if any
exponent += digits.decimalAt;
- if (exponent < -PARSE_MAX_EXPONENT) {
+ if (exponent < -getParseMaxDigits()) {
status[STATUS_UNDERFLOW] = true;
- } else if (exponent > PARSE_MAX_EXPONENT) {
+ } else if (exponent > getParseMaxDigits()) {
status[STATUS_INFINITE] = true;
} else {
digits.decimalAt = (int) exponent;
// Match positive and negative suffixes; prefer longest match.
if (posMatch >= 0) {
- posMatch = compareAffix(text, position, false, false, posSuffix, type, currency);
+ posMatch = compareAffix(text, position, false, false, posSuffix, parseComplexCurrency, type, currency);
}
if (negMatch >= 0) {
- negMatch = compareAffix(text, position, true, false, negSuffix, type, currency);
+ negMatch = compareAffix(text, position, true, false, negSuffix, parseComplexCurrency, type, currency);
}
if (posMatch >= 0 && negMatch >= 0) {
if (posMatch > negMatch) {
return true;
}
- // Utility method used to count the number of codepoints
+ // Utility method used to count the number of codepoints
private int countCodePoints(String str,int start, int end) {
int count = 0;
int index = start;
* @param isNegative
* @param isPrefix
* @param affixPat affix pattern used for currency affix comparison
+ * @param copmplexCurrencyParsing whether it is currency parsing or not
* @param type compare against currency type, LONG_NAME only or not.
* @param currency return value for parsed currency, for generic currency parsing
* mode, or null for normal parsing. In generic currency parsing mode, any currency
* @return length of input that matches, or -1 if match failure
*/
private int compareAffix(String text, int pos, boolean isNegative, boolean isPrefix,
- String affixPat, int type, Currency[] currency) {
- if (currency != null || currencyChoice != null || currencySignCount > 0) {
+ String affixPat, boolean complexCurrencyParsing, int type, Currency[] currency) {
+ if (currency != null || currencyChoice != null || (currencySignCount != CURRENCY_SIGN_COUNT_ZERO && complexCurrencyParsing)) {
return compareComplexAffix(affixPat, text, pos, type, currency);
}
if (isPrefix) {
}
+ /**
+ * Check for bidi marks: LRM, RLM, ALM
+ */
+ private static boolean isBidiMark(int c) {
+ return (c==0x200E || c==0x200F || c==0x061C);
+ }
+
+ /**
+ * Remove bidi marks from affix
+ */
+ private static final int TRIM_BUFLEN = 32;
+ private static String trimMarksFromAffix(String affix) {
+ boolean hasBidiMark = false;
+ int idx = 0;
+ for (; idx < affix.length(); idx++) {
+ if (isBidiMark(affix.charAt(idx))) {
+ hasBidiMark = true;
+ break;
+ }
+ }
+ if (!hasBidiMark) {
+ return affix;
+ }
+
+ StringBuilder buf = new StringBuilder();
+ buf.append(affix, 0, idx);
+ idx++; // skip the first Bidi mark
+ for (; idx < affix.length(); idx++) {
+ char c = affix.charAt(idx);
+ if (!isBidiMark(c)) {
+ buf.append(c);
+ }
+ }
+
+ return buf.toString();
+ }
+
/**
* Return the length matched by the given affix, or -1 if none. Runs of white space in
* the affix, match runs of white space in the input. Pattern white space and input
*/
private static int compareSimpleAffix(String affix, String input, int pos) {
int start = pos;
- for (int i = 0; i < affix.length();) {
- int c = UTF16.charAt(affix, i);
+ // Affixes here might consist of sign, currency symbol and related spacing, etc.
+ // For more efficiency we should keep lazily-created trimmed affixes around in
+ // instance variables instead of trimming each time they are used (the next step).
+ String trimmedAffix = (affix.length() > 1)? trimMarksFromAffix(affix): affix;
+ for (int i = 0; i < trimmedAffix.length();) {
+ int c = UTF16.charAt(trimmedAffix, i);
int len = UTF16.getCharCount(c);
if (PatternProps.isWhiteSpace(c)) {
// We may have a pattern like: \u200F and input text like: \u200F Note
// UWhiteSpace. So we have to first do a direct match of the run of RULE
// whitespace in the pattern, then match any extra characters.
boolean literalMatch = false;
- while (pos < input.length() && UTF16.charAt(input, pos) == c) {
- literalMatch = true;
- i += len;
- pos += len;
- if (i == affix.length()) {
- break;
- }
- c = UTF16.charAt(affix, i);
- len = UTF16.getCharCount(c);
- if (!PatternProps.isWhiteSpace(c)) {
+ while (pos < input.length()) {
+ int ic = UTF16.charAt(input, pos);
+ if (ic == c) {
+ literalMatch = true;
+ i += len;
+ pos += len;
+ if (i == trimmedAffix.length()) {
+ break;
+ }
+ c = UTF16.charAt(trimmedAffix, i);
+ len = UTF16.getCharCount(c);
+ if (!PatternProps.isWhiteSpace(c)) {
+ break;
+ }
+ } else if (isBidiMark(ic)) {
+ pos++; // just skip over this input text
+ } else {
break;
}
}
- // Advance over run in affix
- i = skipPatternWhiteSpace(affix, i);
+ // Advance over run in trimmedAffix
+ i = skipPatternWhiteSpace(trimmedAffix, i);
// Advance over run in input text. Must see at least one white space char
// in input, unless we've already matched some characters literally.
}
// If we skip UWhiteSpace in the input text, we need to skip it in the
// pattern. Otherwise, the previous lines may have skipped over text
- // (such as U+00A0) that is also in the affix.
- i = skipUWhiteSpace(affix, i);
+ // (such as U+00A0) that is also in the trimmedAffix.
+ i = skipUWhiteSpace(trimmedAffix, i);
} else {
- if (pos < input.length() && UTF16.charAt(input, pos) == c) {
- i += len;
- pos += len;
- } else {
+ boolean match = false;
+ while (pos < input.length()) {
+ int ic = UTF16.charAt(input, pos);
+ if (!match && equalWithSignCompatibility(ic, c)) {
+ i += len;
+ pos += len;
+ match = true;
+ } else if (isBidiMark(ic)) {
+ pos++; // just skip over this input text
+ } else {
+ break;
+ }
+ }
+ if (!match) {
return -1;
}
}
return pos - start;
}
+ private static boolean equalWithSignCompatibility(int lhs, int rhs) {
+ return lhs == rhs
+ || (minusSigns.contains(lhs) && minusSigns.contains(rhs))
+ || (plusSigns.contains(lhs) && plusSigns.contains(rhs));
+ }
+
/**
* Skips over a run of zero or more Pattern_White_Space characters at pos in text.
*/
return pos;
}
- /**
+ /**
+ * Skips over a run of zero or more bidi marks at pos in text.
+ */
+ private static int skipBidiMarks(String text, int pos) {
+ while (pos < text.length()) {
+ int c = UTF16.charAt(text, pos);
+ if (!isBidiMark(c)) {
+ break;
+ }
+ pos += UTF16.getCharCount(c);
+ }
+ return pos;
+ }
+
+ /**
* Returns the length matched by the given affix, or -1 if none.
*
* @param affixPat pattern string
* white space in text.
*/
static final int match(String text, int pos, int ch) {
- if (pos >= text.length()) {
+ if (pos < 0 || pos >= text.length()) {
return -1;
}
+ pos = skipBidiMarks(text, pos);
if (PatternProps.isWhiteSpace(ch)) {
// Advance over run of white space in input text
// Must see at least one white space char in input
}
return pos;
}
- return (pos >= 0 && UTF16.charAt(text, pos) == ch) ? (pos + UTF16.getCharCount(ch)) : -1;
+ if (pos >= text.length() || UTF16.charAt(text, pos) != ch) {
+ return -1;
+ }
+ pos = skipBidiMarks(text, pos + UTF16.getCharCount(ch));
+ return pos;
}
/**
/**
* {@icu} Returns the rounding increment.
*
- * @return A positive rounding increment, or <code>null</code> if rounding is not in
- * effect.
+ * @return A positive rounding increment, or <code>null</code> if a custom rounding
+ * increment is not in effect.
* @see #setRoundingIncrement
* @see #getRoundingMode
* @see #setRoundingMode
* @stable ICU 2.0
*/
-//#if defined(ECLIPSE)
-//## public BigDecimal getRoundingIncrement() {
-//## return roundingIncrementICU;
-//## }
-//#else
public java.math.BigDecimal getRoundingIncrement() {
if (roundingIncrementICU == null)
return null;
return roundingIncrementICU.toBigDecimal();
}
-//#endif
/**
- * {@icu} Sets the rounding increment. This method also controls whether rounding is
- * enabled.
+ * {@icu} Sets the rounding increment. In the absence of a rounding increment, numbers
+ * will be rounded to the number of digits displayed.
*
* @param newValue A positive rounding increment, or <code>null</code> or
- * <code>BigDecimal(0.0)</code> to disable rounding.
+ * <code>BigDecimal(0.0)</code> to use the default rounding increment.
* @throws IllegalArgumentException if <code>newValue</code> is < 0.0
* @see #getRoundingIncrement
* @see #getRoundingMode
}
/**
- * {@icu} Sets the rounding increment. This method also controls whether rounding is
- * enabled.
+ * {@icu} Sets the rounding increment. In the absence of a rounding increment, numbers
+ * will be rounded to the number of digits displayed.
*
* @param newValue A positive rounding increment, or <code>null</code> or
- * <code>BigDecimal(0.0)</code> to disable rounding.
+ * <code>BigDecimal(0.0)</code> to use the default rounding increment.
* @throws IllegalArgumentException if <code>newValue</code> is < 0.0
* @see #getRoundingIncrement
* @see #getRoundingMode
} else {
setInternalRoundingIncrement(newValue);
}
- setRoundingDouble();
+ resetActualRounding();
}
/**
- * {@icu} Sets the rounding increment. This method also controls whether rounding is
- * enabled.
+ * {@icu} Sets the rounding increment. In the absence of a rounding increment, numbers
+ * will be rounded to the number of digits displayed.
*
- * @param newValue A positive rounding increment, or 0.0 to disable rounding.
+ * @param newValue A positive rounding increment, or 0.0 to use the default
+ * rounding increment.
* @throws IllegalArgumentException if <code>newValue</code> is < 0.0
* @see #getRoundingIncrement
* @see #getRoundingMode
if (newValue < 0.0) {
throw new IllegalArgumentException("Illegal rounding increment");
}
- roundingDouble = newValue;
- roundingDoubleReciprocal = 0.0d;
if (newValue == 0.0d) {
- setRoundingIncrement((BigDecimal) null);
+ setInternalRoundingIncrement((BigDecimal) null);
} else {
- roundingDouble = newValue;
- if (roundingDouble < 1.0d) {
- double rawRoundedReciprocal = 1.0d / roundingDouble;
- setRoundingDoubleReciprocal(rawRoundedReciprocal);
- }
- setInternalRoundingIncrement(new BigDecimal(newValue));
+ // Should use BigDecimal#valueOf(double) instead of constructor
+ // to avoid the double precision problem.
+ setInternalRoundingIncrement(BigDecimal.valueOf(newValue));
}
+ resetActualRounding();
}
- private void setRoundingDoubleReciprocal(double rawRoundedReciprocal) {
- roundingDoubleReciprocal = Math.rint(rawRoundedReciprocal);
- if (Math.abs(rawRoundedReciprocal - roundingDoubleReciprocal) > roundingIncrementEpsilon) {
- roundingDoubleReciprocal = 0.0d;
- }
- }
-
- static final double roundingIncrementEpsilon = 0.000000001;
-
/**
* Returns the rounding mode.
*
* @see java.math.BigDecimal
* @stable ICU 2.0
*/
+ @Override
public int getRoundingMode() {
return roundingMode;
}
* @see java.math.BigDecimal
* @stable ICU 2.0
*/
+ @Override
public void setRoundingMode(int roundingMode) {
if (roundingMode < BigDecimal.ROUND_UP || roundingMode > BigDecimal.ROUND_UNNECESSARY) {
throw new IllegalArgumentException("Invalid rounding mode: " + roundingMode);
}
this.roundingMode = roundingMode;
-
- if (getRoundingIncrement() == null) {
- setRoundingIncrement(Math.pow(10.0, (double) -getMaximumFractionDigits()));
- }
+ resetActualRounding();
}
/**
* Overrides clone.
* @stable ICU 2.0
*/
+ @Override
public Object clone() {
try {
DecimalFormat other = (DecimalFormat) super.clone();
if (currencyPluralInfo != null) {
other.currencyPluralInfo = (CurrencyPluralInfo) currencyPluralInfo.clone();
}
+ other.attributes = new ArrayList<FieldPosition>(); // #9240
// TODO: We need to figure out whether we share a single copy of DigitList by
// multiple cloned copies. format/subformat are designed to use a single
* Overrides equals.
* @stable ICU 2.0
*/
+ @Override
public boolean equals(Object obj) {
if (obj == null)
return false;
* Overrides hashCode.
* @stable ICU 2.0
*/
+ @Override
public int hashCode() {
return super.hashCode() * 37 + positivePrefix.hashCode();
// just enough fields for a reasonable distribution
c = symbols.getPerMill();
break;
case PATTERN_MINUS:
- c = symbols.getMinusSign();
- break;
+ String minusString = symbols.getMinusString();
+ buffer.append(minusString);
+ continue;
}
buffer.append(c);
}
*
* @stable ICU 3.6
*/
+ @Override
public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
+ return formatToCharacterIterator(obj, NULL_UNIT);
+ }
+
+ AttributedCharacterIterator formatToCharacterIterator(Object obj, Unit unit) {
if (!(obj instanceof Number))
throw new IllegalArgumentException();
Number number = (Number) obj;
- StringBuffer text = null;
+ StringBuffer text = new StringBuffer();
+ unit.writePrefix(text);
attributes.clear();
if (obj instanceof BigInteger) {
- text = format((BigInteger) number, new StringBuffer(), new FieldPosition(0), true);
+ format((BigInteger) number, text, new FieldPosition(0), true);
} else if (obj instanceof java.math.BigDecimal) {
- text = format((java.math.BigDecimal) number, new StringBuffer(), new FieldPosition(0)
+ format((java.math.BigDecimal) number, text, new FieldPosition(0)
, true);
} else if (obj instanceof Double) {
- text = format(number.doubleValue(), new StringBuffer(), new FieldPosition(0), true);
+ format(number.doubleValue(), text, new FieldPosition(0), true);
} else if (obj instanceof Integer || obj instanceof Long) {
- text = format(number.longValue(), new StringBuffer(), new FieldPosition(0), true);
+ format(number.longValue(), text, new FieldPosition(0), true);
+ } else {
+ throw new IllegalArgumentException();
}
-
+ unit.writeSuffix(text);
AttributedString as = new AttributedString(text.toString());
// add NumberFormat field attributes to the AttributedString
// [Richard/GCL]
setMaximumIntegerDigits(useExponentialNotation ? digitLeftCount + minInt :
DOUBLE_INTEGER_DIGITS);
- setMaximumFractionDigits(decimalPos >= 0 ?
+ _setMaximumFractionDigits(decimalPos >= 0 ?
(digitTotalCount - decimalPos) : 0);
setMinimumFractionDigits(decimalPos >= 0 ?
(digitLeftCount + zeroDigitCount - decimalPos) : 0);
if (scale < 0) {
roundingIncrementICU = roundingIncrementICU.movePointRight(-scale);
}
- setRoundingDouble();
roundingMode = BigDecimal.ROUND_HALF_EVEN;
} else {
setRoundingIncrement((BigDecimal) null);
setMinimumIntegerDigits(0);
setMaximumIntegerDigits(DOUBLE_INTEGER_DIGITS);
setMinimumFractionDigits(0);
- setMaximumFractionDigits(DOUBLE_FRACTION_DIGITS);
+ _setMaximumFractionDigits(DOUBLE_FRACTION_DIGITS);
}
// If there was no negative pattern, or if the negative pattern is identical to
formatPattern = pattern;
// special handlings for currency instance
- if (currencySignCount > 0) {
+ if (currencySignCount != CURRENCY_SIGN_COUNT_ZERO) {
// reset rounding increment and max/min fractional digits
// by the currency
Currency theCurrency = getCurrency();
setRoundingIncrement(theCurrency.getRoundingIncrement());
int d = theCurrency.getDefaultFractionDigits();
setMinimumFractionDigits(d);
- setMaximumFractionDigits(d);
+ _setMaximumFractionDigits(d);
}
// initialize currencyPluralInfo if needed
currencyPluralInfo = new CurrencyPluralInfo(symbols.getULocale());
}
}
+ resetActualRounding();
}
- /**
- * Centralizes the setting of the roundingDouble and roundingDoubleReciprocal.
- */
- private void setRoundingDouble() {
- if (roundingIncrementICU == null) {
- roundingDouble = 0.0d;
- roundingDoubleReciprocal = 0.0d;
- } else {
- roundingDouble = roundingIncrementICU.doubleValue();
- setRoundingDoubleReciprocal(
- BigDecimal.ONE.divide(roundingIncrementICU, BigDecimal.ROUND_HALF_EVEN)
- .doubleValue());
- }
- }
private void patternError(String msg, String pattern) {
throw new IllegalArgumentException(msg + " in pattern \"" + pattern + '"');
* @see NumberFormat#setMaximumIntegerDigits
* @stable ICU 2.0
*/
+ @Override
public void setMaximumIntegerDigits(int newValue) {
super.setMaximumIntegerDigits(Math.min(newValue, DOUBLE_INTEGER_DIGITS));
}
* @see NumberFormat#setMinimumIntegerDigits
* @stable ICU 2.0
*/
+ @Override
public void setMinimumIntegerDigits(int newValue) {
super.setMinimumIntegerDigits(Math.min(newValue, DOUBLE_INTEGER_DIGITS));
}
/**
* {@icu} Sets the minimum number of significant digits that will be displayed. If
* <code>min</code> is less than one then it is set to one. If the maximum significant
- * digits count is less than <code>min</code>, then it is set to
- * <code>min</code>. This value has no effect unless {@link #areSignificantDigitsUsed()}
- * returns true.
+ * digits count is less than <code>min</code>, then it is set to <code>min</code>.
+ * This function also enables the use of significant digits by this formatter -
+ * {@link #areSignificantDigitsUsed()} will return true.
*
* @param min the fewest significant digits to be shown
* @stable ICU 3.0
int max = Math.max(maxSignificantDigits, min);
minSignificantDigits = min;
maxSignificantDigits = max;
+ setSignificantDigitsUsed(true);
}
/**
* {@icu} Sets the maximum number of significant digits that will be displayed. If
* <code>max</code> is less than one then it is set to one. If the minimum significant
- * digits count is greater than <code>max</code>, then it is set to
- * <code>max</code>. This value has no effect unless {@link #areSignificantDigitsUsed()}
- * returns true.
+ * digits count is greater than <code>max</code>, then it is set to <code>max</code>.
+ * This function also enables the use of significant digits by this formatter -
+ * {@link #areSignificantDigitsUsed()} will return true.
*
* @param max the most significant digits to be shown
* @stable ICU 3.0
int min = Math.min(minSignificantDigits, max);
minSignificantDigits = min;
maxSignificantDigits = max;
+ setSignificantDigitsUsed(true);
}
/**
* @param theCurrency new currency object to use. Must not be null.
* @stable ICU 2.2
*/
+ @Override
public void setCurrency(Currency theCurrency) {
// If we are a currency format, then modify our affixes to
// encode the currency symbol for the given currency in our
symbols.setCurrencySymbol(s);
}
- if (currencySignCount > 0) {
+ if (currencySignCount != CURRENCY_SIGN_COUNT_ZERO) {
if (theCurrency != null) {
setRoundingIncrement(theCurrency.getRoundingIncrement());
int d = theCurrency.getDefaultFractionDigits();
* @internal
* @deprecated This API is ICU internal only.
*/
+ @Deprecated
+ @Override
protected Currency getEffectiveCurrency() {
Currency c = getCurrency();
if (c == null) {
* @see NumberFormat#setMaximumFractionDigits
* @stable ICU 2.0
*/
+ @Override
public void setMaximumFractionDigits(int newValue) {
+ _setMaximumFractionDigits(newValue);
+ resetActualRounding();
+ }
+
+ /*
+ * Internal method for DecimalFormat, setting maximum fractional digits
+ * without triggering actual rounding recalculated.
+ */
+ private void _setMaximumFractionDigits(int newValue) {
super.setMaximumFractionDigits(Math.min(newValue, DOUBLE_FRACTION_DIGITS));
}
* @see NumberFormat#setMinimumFractionDigits
* @stable ICU 2.0
*/
+ @Override
public void setMinimumFractionDigits(int newValue) {
super.setMinimumFractionDigits(Math.min(newValue, DOUBLE_FRACTION_DIGITS));
}
public boolean isParseBigDecimal() {
return parseBigDecimal;
}
+
+ /**
+ * Set the maximum number of exponent digits when parsing a number.
+ * If the limit is set too high, an OutOfMemoryException may be triggered.
+ * The default value is 1000.
+ * @param newValue the new limit
+ * @draft ICU 51
+ * @provisional This API might change or be removed in a future release.
+ */
+ public void setParseMaxDigits(int newValue) {
+ if (newValue > 0) {
+ PARSE_MAX_EXPONENT = newValue;
+ }
+ }
+
+ /**
+ * Get the current maximum number of exponent digits when parsing a
+ * number.
+ *
+ * @draft ICU 51
+ * @provisional This API might change or be removed in a future release.
+ */
+ public int getParseMaxDigits() {
+ return PARSE_MAX_EXPONENT;
+ }
private void writeObject(ObjectOutputStream stream) throws IOException {
// Ticket#6449 Format.Field instances are not serializable. When
setMaximumIntegerDigits(DOUBLE_INTEGER_DIGITS);
}
if (getMaximumFractionDigits() > DOUBLE_FRACTION_DIGITS) {
- setMaximumFractionDigits(DOUBLE_FRACTION_DIGITS);
+ _setMaximumFractionDigits(DOUBLE_FRACTION_DIGITS);
}
if (serialVersionOnStream < 2) {
exponentSignAlwaysShown = false;
setInternalRoundingIncrement(null);
- setRoundingDouble();
roundingMode = BigDecimal.ROUND_HALF_EVEN;
formatWidth = 0;
pad = ' ';
if (roundingIncrement != null) {
setInternalRoundingIncrement(new BigDecimal(roundingIncrement));
- setRoundingDouble();
}
+ resetActualRounding();
}
private void setInternalRoundingIncrement(BigDecimal value) {
*/
private transient BigDecimal roundingIncrementICU = null;
- /**
- * The rounding increment as a double. If this value is <= 0, then no rounding is
- * done. This value is <code>roundingIncrementICU.doubleValue()</code>. Default value
- * 0.0.
- */
- private transient double roundingDouble = 0.0;
-
- /**
- * If the roundingDouble is the reciprocal of an integer (the most common case!), this
- * is set to be that integer. Otherwise it is 0.0.
- */
- private transient double roundingDoubleReciprocal = 0.0;
-
/**
* The rounding mode. This value controls any rounding operations which occur when
* applying a rounding increment or when reducing the number of fraction digits to
* -- plural long name, such as "US Dollar" for "1.00 US Dollar", or "US Dollars" for
* "3.00 US Dollars".
*/
- private int currencySignCount = 0;
+ private int currencySignCount = CURRENCY_SIGN_COUNT_ZERO;
/**
* For parsing purposes, we need to remember all prefix patterns and suffix patterns
private String posPrefixPatternForCurrency = null;
// positive suffix pattern
private String posSuffixPatternForCurrency = null;
- private int patternType;
+ private final int patternType;
public AffixForCurrency(String negPrefix, String negSuffix, String posPrefix,
String posSuffix, int type) {
// Information needed for DecimalFormat to format/parse currency plural.
private CurrencyPluralInfo currencyPluralInfo = null;
+
+ /**
+ * Unit is an immutable class for the textual representation of a unit, in
+ * particular its prefix and suffix.
+ *
+ * @author rocketman
+ *
+ */
+ static class Unit {
+ private final String prefix;
+ private final String suffix;
+
+ public Unit(String prefix, String suffix) {
+ this.prefix = prefix;
+ this.suffix = suffix;
+ }
+
+ public void writeSuffix(StringBuffer toAppendTo) {
+ toAppendTo.append(suffix);
+ }
+
+ public void writePrefix(StringBuffer toAppendTo) {
+ toAppendTo.append(prefix);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof Unit)) {
+ return false;
+ }
+ Unit other = (Unit) obj;
+ return prefix.equals(other.prefix) && suffix.equals(other.suffix);
+ }
+ @Override
+ public String toString() {
+ return prefix + "/" + suffix;
+ }
+ }
+
+ static final Unit NULL_UNIT = new Unit("", "");
+
+ // Note about rounding implementation
+ //
+ // The original design intended to skip rounding operation when roundingIncrement is not
+ // set. However, rounding may need to occur when fractional digits exceed the width of
+ // fractional part of pattern.
+ //
+ // DigitList class has built-in rounding mechanism, using ROUND_HALF_EVEN. This implementation
+ // forces non-null roundingIncrement if the setting is other than ROUND_HALF_EVEN, otherwise,
+ // when rounding occurs in DigitList by pattern's fractional digits' width, the result
+ // does not match the rounding mode.
+ //
+ // Ideally, all rounding operation should be done in one place like ICU4C trunk does
+ // (ICU4C rounding implementation was rewritten recently). This is intrim implemetation
+ // to fix various issues. In the future, we should entire implementation of rounding
+ // in this class, like ICU4C did.
+ //
+ // Once we fully implement rounding logic in DigitList, then following fields and methods
+ // should be gone.
+
+ private transient BigDecimal actualRoundingIncrementICU = null;
+ private transient java.math.BigDecimal actualRoundingIncrement = null;
+
+ /*
+ * The actual rounding increment as a double.
+ */
+ private transient double roundingDouble = 0.0;
+
+ /*
+ * If the roundingDouble is the reciprocal of an integer (the most common case!), this
+ * is set to be that integer. Otherwise it is 0.0.
+ */
+ private transient double roundingDoubleReciprocal = 0.0;
+
+ /*
+ * Set roundingDouble, roundingDoubleReciprocal and actualRoundingIncrement
+ * based on rounding mode and width of fractional digits. Whenever setting affecting
+ * rounding mode, rounding increment and maximum width of fractional digits, then
+ * this method must be called.
+ *
+ * roundingIncrementICU is the field storing the custom rounding increment value,
+ * while actual rounding increment could be larger.
+ */
+ private void resetActualRounding() {
+ if (roundingIncrementICU != null) {
+ BigDecimal byWidth = getMaximumFractionDigits() > 0 ?
+ BigDecimal.ONE.movePointLeft(getMaximumFractionDigits()) : BigDecimal.ONE;
+ if (roundingIncrementICU.compareTo(byWidth) >= 0) {
+ actualRoundingIncrementICU = roundingIncrementICU;
+ } else {
+ actualRoundingIncrementICU = byWidth.equals(BigDecimal.ONE) ? null : byWidth;
+ }
+ } else {
+ if (roundingMode == BigDecimal.ROUND_HALF_EVEN) {
+ actualRoundingIncrementICU = null;
+ } else {
+ if (getMaximumFractionDigits() > 0) {
+ actualRoundingIncrementICU = BigDecimal.ONE.movePointLeft(getMaximumFractionDigits());
+ }
+ }
+ }
+
+ if (actualRoundingIncrementICU == null) {
+ setRoundingDouble(0.0d);
+ actualRoundingIncrement = null;
+ } else {
+ setRoundingDouble(actualRoundingIncrementICU.doubleValue());
+ actualRoundingIncrement = actualRoundingIncrementICU.toBigDecimal();
+ }
+ }
+
+ static final double roundingIncrementEpsilon = 0.000000001;
+
+ private void setRoundingDouble(double newValue) {
+ roundingDouble = newValue;
+ if (roundingDouble > 0.0d) {
+ double rawRoundedReciprocal = 1.0d / roundingDouble;
+ roundingDoubleReciprocal = Math.rint(rawRoundedReciprocal);
+ if (Math.abs(rawRoundedReciprocal - roundingDoubleReciprocal) > roundingIncrementEpsilon) {
+ roundingDoubleReciprocal = 0.0d;
+ }
+ } else {
+ roundingDoubleReciprocal = 0.0d;
+ }
+ }
}
// eof