From 86571337e9bcddf2726bcbdb305f9d60a7d5e460 Mon Sep 17 00:00:00 2001 From: Travis Keep Date: Tue, 9 Oct 2012 22:30:13 +0000 Subject: [PATCH] ICU-9597 Be sure that FieldPosition adjusted correctly in DecimalFormat.java X-SVN-Rev: 32581 --- .../src/com/ibm/icu/text/DecimalFormat.java | 663 +++++++++--------- .../icu/dev/test/format/NumberFormatTest.java | 262 ++++--- 2 files changed, 512 insertions(+), 413 deletions(-) diff --git a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java index f2ef8b58b12..fc293afffd5 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/text/DecimalFormat.java @@ -1285,16 +1285,7 @@ public class DecimalFormat extends NumberFormat { // 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 @@ -1307,362 +1298,398 @@ public class DecimalFormat extends NumberFormat { 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 > 0 ? symbols.getMonetaryGroupingSeparator() : + symbols.getGroupingSeparator(); + char decimal = currencySignCount > 0 ? symbols.getMonetaryDecimalSeparator() : + symbols.getDecimalSeparator(); + 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()); + } + + 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()); + } + + // 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(); - 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]); + 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) { + result.append(digits[digitList.getDigitValue(digitIndex++)]); + } else { + 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()); + // 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 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 > 0 ? symbols.getMonetaryDecimalSeparator() : + symbols.getDecimalSeparator(); + 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; } + } - // [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()); } } + 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)) { + 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()); + } - // [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, diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java index cf927fd9f35..32e1ea02519 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/format/NumberFormatTest.java @@ -222,7 +222,7 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { logln("Pattern \"" + fmt.toPattern() + "\""); logln(" Format " + 1234.56 + " . " + s); assertEquals("symbol, pos", "$1,234.56", s); - + s = ((NumberFormat) fmt).format(-1234.56); logln(" Format " + Double.toString(-1234.56) + " . " + s); assertEquals("symbol, neg", "-$1,234.56", s); @@ -235,7 +235,7 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { logln("Pattern \"" + fmt.toPattern() + "\""); logln(" Format " + Double.toString(1234.56) + " . " + s); assertEquals("name, pos", "USD 1,234.56", s); - + s = ((NumberFormat) fmt).format(-1234.56); logln(" Format " + Double.toString(-1234.56) + " . " + s); assertEquals("name, neg", "USD -1,234.56", s); @@ -253,7 +253,7 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { {" $ 124 ", "0", "0"}, // TODO: need to handle space correctly {"124$", "0", "3"}, // TODO: need to handle space correctly // {"124 $", "5", "-1"}, TODO: OK or NOT? - {"124 $", "0", "3"}, + {"124 $", "0", "3"}, }; NumberFormat foo = NumberFormat.getCurrencyInstance(); for (int i = 0; i < DATA.length; ++i) { @@ -281,16 +281,16 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { public void TestMultiCurrencySign() { String[][] DATA = { // the fields in the following test are: - // locale, - // currency pattern (with negative pattern), + // locale, + // currency pattern (with negative pattern), // currency number to be formatted, // currency format using currency symbol name, such as "$" for USD, // currency format using currency ISO name, such as "USD", // currency format using plural name, such as "US dollars". // for US locale - {"en_US", "\u00A4#,##0.00;-\u00A4#,##0.00", "1234.56", "$1,234.56", "USD1,234.56", "US dollars1,234.56"}, - {"en_US", "\u00A4#,##0.00;-\u00A4#,##0.00", "-1234.56", "-$1,234.56", "-USD1,234.56", "-US dollars1,234.56"}, - {"en_US", "\u00A4#,##0.00;-\u00A4#,##0.00", "1", "$1.00", "USD1.00", "US dollar1.00"}, + {"en_US", "\u00A4#,##0.00;-\u00A4#,##0.00", "1234.56", "$1,234.56", "USD1,234.56", "US dollars1,234.56"}, + {"en_US", "\u00A4#,##0.00;-\u00A4#,##0.00", "-1234.56", "-$1,234.56", "-USD1,234.56", "-US dollars1,234.56"}, + {"en_US", "\u00A4#,##0.00;-\u00A4#,##0.00", "1", "$1.00", "USD1.00", "US dollar1.00"}, // for CHINA locale {"zh_CN", "\u00A4#,##0.00;(\u00A4#,##0.00)", "1234.56", "\uFFE51,234.56", "CNY1,234.56", "\u4EBA\u6C11\u5E011,234.56"}, {"zh_CN", "\u00A4#,##0.00;(\u00A4#,##0.00)", "-1234.56", "(\uFFE51,234.56)", "(CNY1,234.56)", "(\u4EBA\u6C11\u5E011,234.56)"}, @@ -336,7 +336,7 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { // DATA[i][5] is the currency format result using // triple currency sign. String oneCurrencyFormat = DATA[i][k]; - if (fmt.parse(oneCurrencyFormat).doubleValue() != + if (fmt.parse(oneCurrencyFormat).doubleValue() != numberToBeFormat.doubleValue()) { errln("FAILED parse " + oneCurrencyFormat); } @@ -384,12 +384,12 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { String[][] DATA = { // the data are: // string to be parsed, the parsed result (number) - {"$1.00", "1"}, - {"USD1.00", "1"}, - {"1.00 US dollar", "1"}, - {"$1,234.56", "1234.56"}, - {"USD1,234.56", "1234.56"}, - {"1,234.56 US dollar", "1234.56"}, + {"$1.00", "1"}, + {"USD1.00", "1"}, + {"1.00 US dollar", "1"}, + {"$1,234.56", "1234.56"}, + {"USD1,234.56", "1234.56"}, + {"1,234.56 US dollar", "1234.56"}, }; try { for (int i = 0; i < DATA.length; ++i) { @@ -403,7 +403,7 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { } catch (ParseException e) { errln("FAILED, DecimalFormat parse currency: " + e.toString()); } - } + } /** * Test localized currency patterns. @@ -473,7 +473,7 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { public void TestCurrencyIsoPluralFormat() { String[][] DATA = { // the data are: - // locale, + // locale, // currency amount to be formatted, // currency ISO code to be formatted, // format result using CURRENCYSTYLE, @@ -482,10 +482,10 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { {"en_US", "1", "USD", "$1.00", "USD1.00", "1.00 US dollar"}, {"en_US", "1234.56", "USD", "$1,234.56", "USD1,234.56", "1,234.56 US dollars"}, {"en_US", "-1234.56", "USD", "($1,234.56)", "(USD1,234.56)", "-1,234.56 US dollars"}, - {"zh_CN", "1", "USD", "US$1.00", "USD1.00", "1.00\u7F8E\u5143"}, + {"zh_CN", "1", "USD", "US$1.00", "USD1.00", "1.00\u7F8E\u5143"}, {"zh_CN", "1234.56", "USD", "US$1,234.56", "USD1,234.56", "1,234.56\u7F8E\u5143"}, {"zh_CN", "1", "CNY", "\uFFE51.00", "CNY1.00", "1.00\u4EBA\u6C11\u5E01"}, - {"zh_CN", "1234.56", "CNY", "\uFFE51,234.56", "CNY1,234.56", "1,234.56\u4EBA\u6C11\u5E01"}, + {"zh_CN", "1234.56", "CNY", "\uFFE51,234.56", "CNY1,234.56", "1,234.56\u4EBA\u6C11\u5E01"}, {"ru_RU", "1", "RUB", "1,00\u00A0\u0440\u0443\u0431.", "1,00\u00A0RUB", "1,00 \u0440\u043E\u0441\u0441\u0438\u0439\u0441\u043A\u0438\u0439 \u0440\u0443\u0431\u043B\u044C"}, {"ru_RU", "2", "RUB", "2,00\u00A0\u0440\u0443\u0431.", "2,00\u00A0RUB", "2,00 \u0440\u043E\u0441\u0441\u0438\u0439\u0441\u043A\u0438\u0445 \u0440\u0443\u0431\u043B\u044F"}, {"ru_RU", "5", "RUB", "5,00\u00A0\u0440\u0443\u0431.", "5,00\u00A0RUB", "5,00 \u0440\u043E\u0441\u0441\u0438\u0439\u0441\u043A\u0438\u0445 \u0440\u0443\u0431\u043B\u0435\u0439"}, @@ -497,7 +497,7 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { {"es_AR", "1", "INR", "\u20B91,00", "INR1,00", "1,00 rupia india"}, {"ar_EG", "1", "USD", "US$\u00A0\u0661\u066B\u0660\u0660", "USD\u00a0\u0661\u066b\u0660\u0660", "\u0661\u066b\u0660\u0660 \u062f\u0648\u0644\u0627\u0631 \u0623\u0645\u0631\u064a\u0643\u064a"}, }; - + for (int i=0; i getSupportedLocaleNames() { return null; } + @Override public NumberFormat createFormat(ULocale loc, int formatType) { return null; } @@ -2590,10 +2648,12 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { * tested. */ class TestFactory1 extends NumberFormatFactory { + @Override public Set getSupportedLocaleNames() { return null; } + @Override public NumberFormat createFormat(Locale loc, int formatType) { return null; } @@ -2648,26 +2708,32 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { // Tests when "if (shim == null)" is true @SuppressWarnings("serial") class TestGetAvailableLocales extends NumberFormat { + @Override public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) { return null; } + @Override public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) { return null; } + @Override public StringBuffer format(BigInteger number, StringBuffer toAppendTo, FieldPosition pos) { return null; } + @Override public StringBuffer format(java.math.BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) { return null; } + @Override public StringBuffer format(BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) { return null; } + @Override public Number parse(String text, ParsePosition parsePosition) { return null; } @@ -2720,26 +2786,32 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { public void TestRoundingMode() { @SuppressWarnings("serial") class TestRoundingMode extends NumberFormat { + @Override public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) { return null; } + @Override public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) { return null; } + @Override public StringBuffer format(BigInteger number, StringBuffer toAppendTo, FieldPosition pos) { return null; } + @Override public StringBuffer format(java.math.BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) { return null; } + @Override public StringBuffer format(BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) { return null; } + @Override public Number parse(String text, ParsePosition parsePosition) { return null; } @@ -2842,33 +2914,33 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { + text1 + " text2=" + text2); } } - + /* * Testing rounding to negative zero problem * reported by ticket#7609 */ - public void TestNegZeroRounding() { - - DecimalFormat df = (DecimalFormat) NumberFormat.getInstance(); - df.setRoundingMode(MathContext.ROUND_HALF_UP); - df.setMinimumFractionDigits(1); - df.setMaximumFractionDigits(1); + public void TestNegZeroRounding() { + + DecimalFormat df = (DecimalFormat) NumberFormat.getInstance(); + df.setRoundingMode(MathContext.ROUND_HALF_UP); + df.setMinimumFractionDigits(1); + df.setMaximumFractionDigits(1); String text1 = df.format(-0.01); - - df.setRoundingIncrement(0.1); + + df.setRoundingIncrement(0.1); String text2 = df.format(-0.01); - + // output1 and output2 must be identical if (!text1.equals(text2)) { errln("NumberFormat.format() should return the same result - text1=" + text1 + " text2=" + text2); } - + } - + public void TestCurrencyAmountCoverage() { CurrencyAmount ca, cb; - + try { ca = new CurrencyAmount(null, null); errln("NullPointerException should have been thrown."); @@ -2879,7 +2951,7 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { errln("NullPointerException should have been thrown."); } catch (NullPointerException ex) { } - + ca = new CurrencyAmount(new Integer(0), Currency.getInstance(new ULocale("ja_JP"))); cb = new CurrencyAmount(new Integer(1), Currency.getInstance(new ULocale("ja_JP"))); if (ca.equals(null)) { @@ -2973,9 +3045,9 @@ public class NumberFormatTest extends com.ibm.icu.dev.test.TestFmwk { } private static class FormatCharItrTestThread implements Runnable { - private NumberFormat fmt; - private int num; - private int[] result; + private final NumberFormat fmt; + private final int num; + private final int[] result; FormatCharItrTestThread(NumberFormat fmt, int num, int[] result) { this.fmt = fmt; -- 2.40.0