com.ibm.icu.text.DecimalFormat Maven / Gradle / Ivy
Show all versions of icu4j Show documentation
/*
*******************************************************************************
* Copyright (C) 1996-2014, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.text;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.math.BigInteger;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.ChoiceFormat;
import java.text.FieldPosition;
import java.text.Format;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import com.ibm.icu.impl.ICUConfig;
import com.ibm.icu.impl.PatternProps;
import com.ibm.icu.impl.Utility;
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;
import com.ibm.icu.util.ULocale.Category;
/**
* {@icuenhanced java.text.DecimalFormat}.{@icu _usage_}
*
* DecimalFormat
is a concrete subclass of {@link NumberFormat} that formats
* decimal numbers. It has a variety of features designed to make it possible to parse and
* format numbers in any locale, including support for Western, Arabic, or Indic digits.
* It also supports different flavors of numbers, including integers ("123"), fixed-point
* numbers ("123.4"), scientific notation ("1.23E4"), percentages ("12%"), and currency
* amounts ("$123.00", "USD123.00", "123.00 US dollars"). All of these flavors can be
* easily localized.
*
* To obtain a {@link NumberFormat} for a specific locale (including the default
* locale) call one of NumberFormat
's factory methods such as {@link
* NumberFormat#getInstance}. Do not call the DecimalFormat
constructors
* directly, unless you know what you are doing, since the {@link NumberFormat} factory
* methods may return subclasses other than DecimalFormat
. If you need to
* customize the format object, do something like this:
*
*
* NumberFormat f = NumberFormat.getInstance(loc);
* if (f instanceof DecimalFormat) {
* ((DecimalFormat) f).setDecimalSeparatorAlwaysShown(true);
* }
*
* Example Usage
*
* Print out a number using the localized number, currency, and percent
* format for each locale.
*
*
* Locale[] locales = NumberFormat.getAvailableLocales();
* double myNumber = -1234.56;
* NumberFormat format;
* for (int j=0; j<3; ++j) {
* System.out.println("FORMAT");
* for (int i = 0; i < locales.length; ++i) {
* if (locales[i].getCountry().length() == 0) {
* // Skip language-only locales
* continue;
* }
* System.out.print(locales[i].getDisplayName());
* switch (j) {
* case 0:
* format = NumberFormat.getInstance(locales[i]); break;
* case 1:
* format = NumberFormat.getCurrencyInstance(locales[i]); break;
* default:
* format = NumberFormat.getPercentInstance(locales[i]); break;
* }
* try {
* // Assume format is a DecimalFormat
* System.out.print(": " + ((DecimalFormat) format).toPattern()
* + " -> " + form.format(myNumber));
* } catch (Exception e) {}
* try {
* System.out.println(" -> " + format.parse(form.format(myNumber)));
* } catch (ParseException e) {}
* }
* }
*
* Another example use getInstance(style).
* Print out a number using the localized number, currency, percent,
* scientific, integer, iso currency, and plural currency format for each locale.
*
*
* ULocale locale = new ULocale("en_US");
* double myNumber = 1234.56;
* for (int j=NumberFormat.NUMBERSTYLE; j<=NumberFormat.PLURALCURRENCYSTYLE; ++j) {
* NumberFormat format = NumberFormat.getInstance(locale, j);
* try {
* // Assume format is a DecimalFormat
* System.out.print(": " + ((DecimalFormat) format).toPattern()
* + " -> " + form.format(myNumber));
* } catch (Exception e) {}
* try {
* System.out.println(" -> " + format.parse(form.format(myNumber)));
* } catch (ParseException e) {}
* }
*
* Patterns
*
* A DecimalFormat
consists of a pattern and a set of
* symbols. The pattern may be set directly using {@link #applyPattern}, or
* indirectly using other API methods which manipulate aspects of the pattern, such as the
* minimum number of integer digits. The symbols are stored in a {@link
* DecimalFormatSymbols} object. When using the {@link NumberFormat} factory methods, the
* pattern and symbols are read from ICU's locale data.
*
*
Special Pattern Characters
*
* Many characters in a pattern are taken literally; they are matched during parsing
* and output unchanged during formatting. Special characters, on the other hand, stand
* for other characters, strings, or classes of characters. For example, the '#'
* character is replaced by a localized digit. Often the replacement character is the
* same as the pattern character; in the U.S. locale, the ',' grouping character is
* replaced by ','. However, the replacement is still happening, and if the symbols are
* modified, the grouping character changes. Some special characters affect the behavior
* of the formatter by their presence; for example, if the percent character is seen, then
* the value is multiplied by 100 before being displayed.
*
*
To insert a special character in a pattern as a literal, that is, without any
* special meaning, the character must be quoted. There are some exceptions to this which
* are noted below.
*
*
The characters listed here are used in non-localized patterns. Localized patterns
* use the corresponding characters taken from this formatter's {@link
* DecimalFormatSymbols} object instead, and these characters lose their special status.
* Two exceptions are the currency sign and quote, which are not localized.
*
*
*
*
* Symbol
* Location
* Localized?
* Meaning
*
* 0
* Number
* Yes
* Digit
*
* 1-9
* Number
* Yes
* '1' through '9' indicate rounding.
*
* @
* Number
* No
* Significant digit
*
* #
* Number
* Yes
* Digit, zero shows as absent
*
* .
* Number
* Yes
* Decimal separator or monetary decimal separator
*
* -
* Number
* Yes
* Minus sign
*
* ,
* Number
* Yes
* Grouping separator
*
* E
* Number
* Yes
* Separates mantissa and exponent in scientific notation.
* Need not be quoted in prefix or suffix.
*
* +
* Exponent
* Yes
* Prefix positive exponents with localized plus sign.
* Need not be quoted in prefix or suffix.
*
* ;
* Subpattern boundary
* Yes
* Separates positive and negative subpatterns
*
* %
* Prefix or suffix
* Yes
* Multiply by 100 and show as percentage
*
* \u2030
* Prefix or suffix
* Yes
* Multiply by 1000 and show as per mille
*
* ¤
(\u00A4
)
* Prefix or suffix
* No
* Currency sign, replaced by currency symbol. If
* doubled, replaced by international currency symbol.
* If tripled, replaced by currency plural names, for example,
* "US dollar" or "US dollars" for America.
* If present in a pattern, the monetary decimal separator
* is used instead of the decimal separator.
*
* '
* Prefix or suffix
* No
* Used to quote special characters in a prefix or suffix,
* for example, "'#'#"
formats 123 to
* "#123"
. To create a single quote
* itself, use two in a row: "# o''clock"
.
*
* *
* Prefix or suffix boundary
* Yes
* Pad escape, precedes pad character
*
*
*
* A DecimalFormat
pattern contains a postive and negative subpattern, for
* example, "#,##0.00;(#,##0.00)". Each subpattern has a prefix, a numeric part, and a
* suffix. If there is no explicit negative subpattern, the negative subpattern is the
* localized minus sign prefixed to the positive subpattern. That is, "0.00" alone is
* equivalent to "0.00;-0.00". If there is an explicit negative subpattern, it serves
* only to specify the negative prefix and suffix; the number of digits, minimal digits,
* and other characteristics are ignored in the negative subpattern. That means that
* "#,##0.0#;(#)" has precisely the same result as "#,##0.0#;(#,##0.0#)".
*
*
The prefixes, suffixes, and various symbols used for infinity, digits, thousands
* separators, decimal separators, etc. may be set to arbitrary values, and they will
* appear properly during formatting. However, care must be taken that the symbols and
* strings do not conflict, or parsing will be unreliable. For example, either the
* positive and negative prefixes or the suffixes must be distinct for {@link #parse} to
* be able to distinguish positive from negative values. Another example is that the
* decimal separator and thousands separator should be distinct characters, or parsing
* will be impossible.
*
*
The grouping separator is a character that separates clusters of integer
* digits to make large numbers more legible. It commonly used for thousands, but in some
* locales it separates ten-thousands. The grouping size is the number of digits
* between the grouping separators, such as 3 for "100,000,000" or 4 for "1 0000
* 0000". There are actually two different grouping sizes: One used for the least
* significant integer digits, the primary grouping size, and one used for all
* others, the secondary grouping size. In most locales these are the same, but
* sometimes they are different. For example, if the primary grouping interval is 3, and
* the secondary is 2, then this corresponds to the pattern "#,##,##0", and the number
* 123456789 is formatted as "12,34,56,789". If a pattern contains multiple grouping
* separators, the interval between the last one and the end of the integer defines the
* primary grouping size, and the interval between the last two defines the secondary
* grouping size. All others are ignored, so "#,##,###,####" == "###,###,####" ==
* "##,#,###,####".
*
*
Illegal patterns, such as "#.#.#" or "#.###,###", will cause
* DecimalFormat
to throw an {@link IllegalArgumentException} with a message
* that describes the problem.
*
*
Pattern BNF
*
*
* pattern := subpattern (';' subpattern)?
* subpattern := prefix? number exponent? suffix?
* number := (integer ('.' fraction)?) | sigDigits
* prefix := '\u0000'..'\uFFFD' - specialCharacters
* suffix := '\u0000'..'\uFFFD' - specialCharacters
* integer := '#'* '0'* '0'
* fraction := '0'* '#'*
* sigDigits := '#'* '@' '@'* '#'*
* exponent := 'E' '+'? '0'* '0'
* padSpec := '*' padChar
* padChar := '\u0000'..'\uFFFD' - quote
*
* Notation:
* X* 0 or more instances of X
* X? 0 or 1 instances of X
* X|Y either X or Y
* C..D any character from C up to D, inclusive
* S-T characters in S, except those in T
*
* The first subpattern is for positive numbers. The second (optional)
* subpattern is for negative numbers.
*
* Not indicated in the BNF syntax above:
*
*
*
* - The grouping separator ',' can occur inside the integer and sigDigits
* elements, between any two pattern characters of that element, as long as the integer or
* sigDigits element is not followed by the exponent element.
*
*
- Two grouping intervals are recognized: That between the decimal point and the first
* grouping symbol, and that between the first and second grouping symbols. These
* intervals are identical in most locales, but in some locales they differ. For example,
* the pattern "#,##,###" formats the number 123456789 as
* "12,34,56,789".
*
*
- The pad specifier
padSpec
may appear before the prefix, after the
* prefix, before the suffix, after the suffix, or not at all.
*
* - In place of '0', the digits '1' through '9' may be used to indicate a rounding
* increment.
*
*
*
* Parsing
*
* DecimalFormat
parses all Unicode characters that represent decimal
* digits, as defined by {@link UCharacter#digit}. In addition,
* DecimalFormat
also recognizes as digits the ten consecutive characters
* starting with the localized zero digit defined in the {@link DecimalFormatSymbols}
* object. During formatting, the {@link DecimalFormatSymbols}-based digits are output.
*
*
During parsing, grouping separators are ignored.
*
*
For currency parsing, the formatter is able to parse every currency style formats no
* matter which style the formatter is constructed with. For example, a formatter
* instance gotten from NumberFormat.getInstance(ULocale, NumberFormat.CURRENCYSTYLE) can
* parse formats such as "USD1.00" and "3.00 US dollars".
*
*
If {@link #parse(String, ParsePosition)} fails to parse a string, it returns
* null
and leaves the parse position unchanged. The convenience method
* {@link #parse(String)} indicates parse failure by throwing a {@link
* java.text.ParseException}.
*
*
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,
* DecimalFormat
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).
*
*
Formatting
*
* Formatting is guided by several parameters, all of which can be specified either
* using a pattern or using the API. The following description applies to formats that do
* not use scientific notation or significant
* digits.
*
*
- If the number of actual integer digits exceeds the maximum integer
* digits, then only the least significant digits are shown. For example, 1997 is
* formatted as "97" if the maximum integer digits is set to 2.
*
*
- If the number of actual integer digits is less than the minimum integer
* digits, then leading zeros are added. For example, 1997 is formatted as "01997"
* if the minimum integer digits is set to 5.
*
*
- If the number of actual fraction digits exceeds the maximum fraction
* digits, then half-even rounding it performed to the maximum fraction digits. For
* example, 0.125 is formatted as "0.12" if the maximum fraction digits is 2. This
* behavior can be changed by specifying a rounding increment and a rounding mode.
*
*
- If the number of actual fraction digits is less than the minimum fraction
* digits, then trailing zeros are added. For example, 0.125 is formatted as
* "0.1250" if the mimimum fraction digits is set to 4.
*
*
- Trailing fractional zeros are not displayed if they occur j positions
* after the decimal, where j is less than the maximum fraction digits. For
* example, 0.10004 is formatted as "0.1" if the maximum fraction digits is four or less.
*
*
* Special Values
*
*
NaN
is represented as a single character, typically
* \uFFFD
. This character is determined by the {@link
* DecimalFormatSymbols} object. This is the only value for which the prefixes and
* suffixes are not used.
*
*
Infinity is represented as a single character, typically \u221E
,
* with the positive or negative prefixes and suffixes applied. The infinity character is
* determined by the {@link DecimalFormatSymbols} object.
*
* Scientific Notation
*
*
Numbers in scientific notation are expressed as the product of a mantissa and a
* power of ten, for example, 1234 can be expressed as 1.234 x 103. The
* mantissa is typically in the half-open interval [1.0, 10.0) or sometimes [0.0, 1.0),
* but it need not be. DecimalFormat
supports arbitrary mantissas.
* DecimalFormat
can be instructed to use scientific notation through the API
* or through the pattern. In a pattern, the exponent character immediately followed by
* one or more digit characters indicates scientific notation. Example: "0.###E0" formats
* the number 1234 as "1.234E3".
*
*
*
* - The number of digit characters after the exponent character gives the minimum
* exponent digit count. There is no maximum. Negative exponents are formatted using the
* localized minus sign, not the prefix and suffix from the pattern. This allows
* patterns such as "0.###E0 m/s". To prefix positive exponents with a localized plus
* sign, specify '+' between the exponent and the digits: "0.###E+0" will produce formats
* "1E+1", "1E+0", "1E-1", etc. (In localized patterns, use the localized plus sign
* rather than '+'.)
*
*
- The minimum number of integer digits is achieved by adjusting the exponent.
* Example: 0.00123 formatted with "00.###E0" yields "12.3E-4". This only happens if
* there is no maximum number of integer digits. If there is a maximum, then the minimum
* number of integer digits is fixed at one.
*
*
- The maximum number of integer digits, if present, specifies the exponent grouping.
* The most common use of this is to generate engineering notation, in which the
* exponent is a multiple of three, e.g., "##0.###E0". The number 12345 is formatted
* using "##0.####E0" as "12.345E3".
*
*
- When using scientific notation, the formatter controls the digit counts using
* significant digits logic. The maximum number of significant digits limits the total
* number of integer and fraction digits that will be shown in the mantissa; it does not
* affect parsing. For example, 12345 formatted with "##0.##E0" is "12.3E3". See the
* section on significant digits for more details.
*
*
- The number of significant digits shown is determined as follows: If
* areSignificantDigitsUsed() returns false, then the minimum number of significant digits
* shown is one, and the maximum number of significant digits shown is the sum of the
* minimum integer and maximum fraction digits, and is unaffected by the
* maximum integer digits. If this sum is zero, then all significant digits are shown.
* If areSignificantDigitsUsed() returns true, then the significant digit counts are
* specified by getMinimumSignificantDigits() and getMaximumSignificantDigits(). In this
* case, the number of integer digits is fixed at one, and there is no exponent grouping.
*
*
- Exponential patterns may not contain grouping separators.
*
*
*
* Significant Digits
*
* DecimalFormat
has two ways of controlling how many digits are shows: (a)
* significant digits counts, or (b) integer and fraction digit counts. Integer and
* fraction digit counts are described above. When a formatter is using significant
* digits counts, the number of integer and fraction digits is not specified directly, and
* the formatter settings for these counts are ignored. Instead, the formatter uses
* however many integer and fraction digits are required to display the specified number
* of significant digits. Examples:
*
*
*
*
* Pattern
* Minimum significant digits
* Maximum significant digits
* Number
* Output of format()
*
* @@@
* 3
* 3
* 12345
* 12300
*
* @@@
* 3
* 3
* 0.12345
* 0.123
*
* @@##
* 2
* 4
* 3.14159
* 3.142
*
* @@##
* 2
* 4
* 1.23004
* 1.23
*
*
*
*
*
* - Significant digit counts may be expressed using patterns that specify a minimum and
* maximum number of significant digits. These are indicated by the
'@'
and
* '#'
characters. The minimum number of significant digits is the number of
* '@'
characters. The maximum number of significant digits is the number of
* '@'
characters plus the number of '#'
characters following on
* the right. For example, the pattern "@@@"
indicates exactly 3 significant
* digits. The pattern "@##"
indicates from 1 to 3 significant digits.
* Trailing zero digits to the right of the decimal separator are suppressed after the
* minimum number of significant digits have been shown. For example, the pattern
* "@##"
formats the number 0.1203 as "0.12"
.
*
* - If a pattern uses significant digits, it may not contain a decimal separator, nor
* the
'0'
pattern character. Patterns such as "@00"
or
* "@.###"
are disallowed.
*
* - Any number of
'#'
characters may be prepended to the left of the
* leftmost '@'
character. These have no effect on the minimum and maximum
* significant digits counts, but may be used to position grouping separators. For
* example, "#,#@#"
indicates a minimum of one significant digits, a maximum
* of two significant digits, and a grouping size of three.
*
* - In order to enable significant digits formatting, use a pattern containing the
*
'@'
pattern character. Alternatively, call {@link
* #setSignificantDigitsUsed setSignificantDigitsUsed(true)}.
*
* - In order to disable significant digits formatting, use a pattern that does not
* contain the
'@'
pattern character. Alternatively, call {@link
* #setSignificantDigitsUsed setSignificantDigitsUsed(false)}.
*
* - The number of significant digits has no effect on parsing.
*
*
- Significant digits may be used together with exponential notation. Such patterns
* are equivalent to a normal exponential pattern with a minimum and maximum integer digit
* count of one, a minimum fraction digit count of
getMinimumSignificantDigits() -
* 1
, and a maximum fraction digit count of getMaximumSignificantDigits() -
* 1
. For example, the pattern "@@###E0"
is equivalent to
* "0.0###E0"
.
*
* - If signficant digits are in use, then the integer and fraction digit counts, as set
* via the API, are ignored. If significant digits are not in use, then the signficant
* digit counts, as set via the API, are ignored.
*
*
*
* Padding
*
* DecimalFormat
supports padding the result of {@link #format} to a
* specific width. Padding may be specified either through the API or through the pattern
* syntax. In a pattern the pad escape character, followed by a single pad character,
* causes padding to be parsed and formatted. The pad escape character is '*' in
* unlocalized patterns, and can be localized using {@link
* DecimalFormatSymbols#setPadEscape}. For example, "$*x#,##0.00"
formats
* 123 to "$xx123.00"
, and 1234 to "$1,234.00"
.
*
*
*
* - When padding is in effect, the width of the positive subpattern, including prefix
* and suffix, determines the format width. For example, in the pattern
"* #0
* o''clock"
, the format width is 10.
*
* - The width is counted in 16-bit code units (Java
char
s).
*
* - Some parameters which usually do not matter have meaning when padding is used,
* because the pattern width is significant with padding. In the pattern "*
* ##,##,#,##0.##", the format width is 14. The initial characters "##,##," do not affect
* the grouping size or maximum integer digits, but they do affect the format width.
*
*
- Padding may be inserted at one of four locations: before the prefix, after the
* prefix, before the suffix, or after the suffix. If padding is specified in any other
* location, {@link #applyPattern} throws an {@link IllegalArgumentException}. If there
* is no prefix, before the prefix and after the prefix are equivalent, likewise for the
* suffix.
*
*
- When specified in a pattern, the 16-bit
char
immediately following the
* pad escape is the pad character. This may be any character, including a special pattern
* character. That is, the pad escape escapes the following character. If there
* is no character after the pad escape, then the pattern is illegal.
*
*
*
*
* Rounding
*
*
DecimalFormat
supports rounding to a specific increment. For example,
* 1230 rounded to the nearest 50 is 1250. 1.234 rounded to the nearest 0.65 is 1.3. The
* rounding increment may be specified through the API or in a pattern. To specify a
* rounding increment in a pattern, include the increment in the pattern itself. "#,#50"
* specifies a rounding increment of 50. "#,##0.05" specifies a rounding increment of
* 0.05.
*
*
*
* - Rounding only affects the string produced by formatting. It does not affect
* parsing or change any numerical values.
*
*
- A rounding mode determines how values are rounded; see the {@link
* com.ibm.icu.math.BigDecimal} documentation for a description of the modes. Rounding
* increments specified in patterns use the default mode, {@link
* com.ibm.icu.math.BigDecimal#ROUND_HALF_EVEN}.
*
*
- Some locales use rounding in their currency formats to reflect the smallest
* currency denomination.
*
*
- In a pattern, digits '1' through '9' specify rounding, but otherwise behave
* identically to digit '0'.
*
*
*
* Synchronization
*
* DecimalFormat
objects are not synchronized. Multiple threads should
* not access one formatter concurrently.
*
* @see java.text.Format
* @see NumberFormat
* @author Mark Davis
* @author Alan Liu
* @stable ICU 2.0
*/
public class DecimalFormat extends NumberFormat {
/**
* Creates a DecimalFormat using the default pattern and symbols for the default
* FORMAT
locale. This is a convenient way to obtain a DecimalFormat when
* internationalization is not the main concern.
*
*
To obtain standard formats for a given locale, use the factory methods on
* NumberFormat such as getNumberInstance. These factories will return the most
* appropriate sub-class of NumberFormat for a given locale.
*
* @see NumberFormat#getInstance
* @see NumberFormat#getNumberInstance
* @see NumberFormat#getCurrencyInstance
* @see NumberFormat#getPercentInstance
* @see Category#FORMAT
* @stable ICU 2.0
*/
public DecimalFormat() {
ULocale def = ULocale.getDefault(Category.FORMAT);
String pattern = getPattern(def, 0);
// Always applyPattern after the symbols are set
this.symbols = new DecimalFormatSymbols(def);
setCurrency(Currency.getInstance(def));
applyPatternWithoutExpandAffix(pattern, false);
if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
currencyPluralInfo = new CurrencyPluralInfo(def);
// the exact pattern is not known until the plural count is known.
// so, no need to expand affix now.
} else {
expandAffixAdjustWidth(null);
}
}
/**
* Creates a DecimalFormat from the given pattern and the symbols for the default
* FORMAT
locale. This is a convenient way to obtain a DecimalFormat when
* internationalization is not the main concern.
*
*
To obtain standard formats for a given locale, use the factory methods on
* NumberFormat such as getNumberInstance. These factories will return the most
* appropriate sub-class of NumberFormat for a given locale.
*
* @param pattern A non-localized pattern string.
* @throws IllegalArgumentException if the given pattern is invalid.
* @see NumberFormat#getInstance
* @see NumberFormat#getNumberInstance
* @see NumberFormat#getCurrencyInstance
* @see NumberFormat#getPercentInstance
* @see Category#FORMAT
* @stable ICU 2.0
*/
public DecimalFormat(String pattern) {
// Always applyPattern after the symbols are set
ULocale def = ULocale.getDefault(Category.FORMAT);
this.symbols = new DecimalFormatSymbols(def);
setCurrency(Currency.getInstance(def));
applyPatternWithoutExpandAffix(pattern, false);
if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
currencyPluralInfo = new CurrencyPluralInfo(def);
} else {
expandAffixAdjustWidth(null);
}
}
/**
* Creates a DecimalFormat from the given pattern and symbols. Use this constructor
* when you need to completely customize the behavior of the format.
*
*
To obtain standard formats for a given locale, use the factory methods on
* NumberFormat such as getInstance or getCurrencyInstance. If you need only minor
* adjustments to a standard format, you can modify the format returned by a
* NumberFormat factory method.
*
* @param pattern a non-localized pattern string
* @param symbols the set of symbols to be used
* @exception IllegalArgumentException if the given pattern is invalid
* @see NumberFormat#getInstance
* @see NumberFormat#getNumberInstance
* @see NumberFormat#getCurrencyInstance
* @see NumberFormat#getPercentInstance
* @see DecimalFormatSymbols
* @stable ICU 2.0
*/
public DecimalFormat(String pattern, DecimalFormatSymbols symbols) {
createFromPatternAndSymbols(pattern, symbols);
}
private void createFromPatternAndSymbols(String pattern, DecimalFormatSymbols inputSymbols) {
// Always applyPattern after the symbols are set
symbols = (DecimalFormatSymbols) inputSymbols.clone();
setCurrencyForSymbols();
applyPatternWithoutExpandAffix(pattern, false);
if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
currencyPluralInfo = new CurrencyPluralInfo(symbols.getULocale());
} else {
expandAffixAdjustWidth(null);
}
}
/**
* Creates a DecimalFormat from the given pattern, symbols, information used for
* currency plural format, and format style. Use this constructor when you need to
* completely customize the behavior of the format.
*
*
To obtain standard formats for a given locale, use the factory methods on
* NumberFormat such as getInstance or getCurrencyInstance.
*
*
If you need only minor adjustments to a standard format, you can modify the
* format returned by a NumberFormat factory method using the setters.
*
*
If you want to completely customize a decimal format, using your own
* DecimalFormatSymbols (such as group separators) and your own information for
* currency plural formatting (such as plural rule and currency plural patterns), you
* can use this constructor.
*
* @param pattern a non-localized pattern string
* @param symbols the set of symbols to be used
* @param infoInput the information used for currency plural format, including
* currency plural patterns and plural rules.
* @param style the decimal formatting style, it is one of the following values:
* NumberFormat.NUMBERSTYLE; NumberFormat.CURRENCYSTYLE; NumberFormat.PERCENTSTYLE;
* NumberFormat.SCIENTIFICSTYLE; NumberFormat.INTEGERSTYLE;
* NumberFormat.ISOCURRENCYSTYLE; NumberFormat.PLURALCURRENCYSTYLE;
* @stable ICU 4.2
*/
public DecimalFormat(String pattern, DecimalFormatSymbols symbols, CurrencyPluralInfo infoInput,
int style) {
CurrencyPluralInfo info = infoInput;
if (style == NumberFormat.PLURALCURRENCYSTYLE) {
info = (CurrencyPluralInfo) infoInput.clone();
}
create(pattern, symbols, info, style);
}
private void create(String pattern, DecimalFormatSymbols inputSymbols, CurrencyPluralInfo info,
int inputStyle) {
if (inputStyle != NumberFormat.PLURALCURRENCYSTYLE) {
createFromPatternAndSymbols(pattern, inputSymbols);
} else {
// Always applyPattern after the symbols are set
symbols = (DecimalFormatSymbols) inputSymbols.clone();
currencyPluralInfo = info;
// the pattern used in format is not fixed until formatting, in which, the
// number is known and will be used to pick the right pattern based on plural
// count. Here, set the pattern as the pattern of plural count == "other".
// For most locale, the patterns are probably the same for all plural
// count. If not, the right pattern need to be re-applied during format.
String currencyPluralPatternForOther =
currencyPluralInfo.getCurrencyPluralPattern("other");
applyPatternWithoutExpandAffix(currencyPluralPatternForOther, false);
setCurrencyForSymbols();
}
style = inputStyle;
}
/**
* Creates a DecimalFormat for currency plural format from the given pattern, symbols,
* and style.
*/
DecimalFormat(String pattern, DecimalFormatSymbols inputSymbols, int style) {
CurrencyPluralInfo info = null;
if (style == NumberFormat.PLURALCURRENCYSTYLE) {
info = new CurrencyPluralInfo(inputSymbols.getULocale());
}
create(pattern, inputSymbols, info, style);
}
/**
* {@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,
boolean parseAttr) {
fieldPosition.setBeginIndex(0);
fieldPosition.setEndIndex(0);
if (Double.isNaN(number)) {
if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) {
fieldPosition.setBeginIndex(result.length());
} else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
fieldPosition.setBeginIndex(result.length());
}
result.append(symbols.getNaN());
// [Spark/CDL] Add attribute for NaN here.
// result.append(symbols.getNaN());
if (parseAttr) {
addAttribute(Field.INTEGER, result.length() - symbols.getNaN().length(),
result.length());
}
if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) {
fieldPosition.setEndIndex(result.length());
} else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
fieldPosition.setEndIndex(result.length());
}
addPadding(result, fieldPosition, 0, 0);
return result;
}
// 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);
if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) {
fieldPosition.setBeginIndex(result.length());
} else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
fieldPosition.setBeginIndex(result.length());
}
// [Spark/CDL] Add attribute for infinity here.
result.append(symbols.getInfinity());
if (parseAttr) {
addAttribute(Field.INTEGER, result.length() - symbols.getInfinity().length(),
result.length());
}
if (fieldPosition.getField() == NumberFormat.INTEGER_FIELD) {
fieldPosition.setEndIndex(result.length());
} else if (fieldPosition.getFieldAttribute() == NumberFormat.Field.INTEGER) {
fieldPosition.setEndIndex(result.length());
}
int suffixLen = appendAffix(result, isNegative, false, parseAttr);
addPadding(result, fieldPosition, prefixLen, suffixLen);
return result;
}
int precision = precision(false);
// This is to fix rounding for scientific notation. See ticket:10542.
// This code should go away when a permanent fix is done for ticket:9931.
//
// This block of code only executes for scientific notation so it will not interfere with the
// previous fix in {@link #resetActualRounding} for fixed decimal numbers.
// Moreover this code only runs when there is rounding to be done (precision > 0) and when the
// rounding mode is something other than ROUND_HALF_EVEN.
// This block of code does the correct rounding of number in advance so that it will fit into
// the number of digits indicated by precision. In this way, we avoid using the default
// ROUND_HALF_EVEN behavior of DigitList. For example, if number = 0.003016 and roundingMode =
// ROUND_DOWN and precision = 3 then after this code executes, number = 0.00301 (3 significant digits)
if (useExponentialNotation && precision > 0 && number != 0.0 && roundingMode != BigDecimal.ROUND_HALF_EVEN) {
int log10RoundingIncr = 1 - precision + (int) Math.floor(Math.log10(Math.abs(number)));
double roundingIncReciprocal = 0.0;
double roundingInc = 0.0;
if (log10RoundingIncr < 0) {
roundingIncReciprocal =
BigDecimal.ONE.movePointRight(-log10RoundingIncr).doubleValue();
} else {
roundingInc =
BigDecimal.ONE.movePointRight(log10RoundingIncr).doubleValue();
}
number = DecimalFormat.round(number, roundingInc, roundingIncReciprocal, roundingMode, isNegative);
}
// End fix for ticket:10542
// At this point we are guaranteed a nonnegative finite
// number.
synchronized (digitList) {
digitList.set(number, precision, !useExponentialNotation &&
!areSignificantDigitsUsed());
return subformat(number, result, fieldPosition, isNegative, false, 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 This API is ICU internal only.
*/
@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 This API is ICU internal only.
*/
@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
* the nearest integer, according to the given mode, and returning that integer *
* roundingInc. Note this is changed from the version in 2.4, since division of
* doubles have inaccuracies. jitterbug 1871.
*
* @param number
* the absolute value of the number to be rounded
* @param roundingInc
* the rounding increment
* @param roundingIncReciprocal
* if non-zero, is the reciprocal of rounding inc.
* @param mode
* a BigDecimal rounding mode
* @param isNegative
* true if the number to be rounded is negative
* @return the absolute value of the rounded result
*/
private static double round(double number, double roundingInc, double roundingIncReciprocal,
int mode, boolean isNegative) {
double div = roundingIncReciprocal == 0.0 ? number / roundingInc : number *
roundingIncReciprocal;
// do the absolute cases first
switch (mode) {
case BigDecimal.ROUND_CEILING:
div = (isNegative ? Math.floor(div + epsilon) : Math.ceil(div - epsilon));
break;
case BigDecimal.ROUND_FLOOR:
div = (isNegative ? Math.ceil(div - epsilon) : Math.floor(div + epsilon));
break;
case BigDecimal.ROUND_DOWN:
div = (Math.floor(div + epsilon));
break;
case BigDecimal.ROUND_UP:
div = (Math.ceil(div - epsilon));
break;
case BigDecimal.ROUND_UNNECESSARY:
if (div != Math.floor(div)) {
throw new ArithmeticException("Rounding necessary");
}
return number;
default:
// Handle complex cases, where the choice depends on the closer value.
// We figure out the distances to the two possible values, ceiling and floor.
// We then go for the diff that is smaller. Only if they are equal does the
// mode matter.
double ceil = Math.ceil(div);
double ceildiff = ceil - div; // (ceil * roundingInc) - number;
double floor = Math.floor(div);
double floordiff = div - floor; // number - (floor * roundingInc);
// Note that the diff values were those mapped back to the "normal" space by
// using the roundingInc. I don't have access to the original author of the
// code but suspect that that was to produce better result in edge cases
// because of machine precision, rather than simply using the difference
// between, say, ceil and div. However, it didn't work in all cases. Am
// trying instead using an epsilon value.
switch (mode) {
case BigDecimal.ROUND_HALF_EVEN:
// We should be able to just return Math.rint(a), but this
// doesn't work in some VMs.
// if one is smaller than the other, take the corresponding side
if (floordiff + epsilon < ceildiff) {
div = floor;
} else if (ceildiff + epsilon < floordiff) {
div = ceil;
} else { // they are equal, so we want to round to whichever is even
double testFloor = floor / 2;
div = (testFloor == Math.floor(testFloor)) ? floor : ceil;
}
break;
case BigDecimal.ROUND_HALF_DOWN:
div = ((floordiff <= ceildiff + epsilon) ? floor : ceil);
break;
case BigDecimal.ROUND_HALF_UP:
div = ((ceildiff <= floordiff + epsilon) ? ceil : floor);
break;
default:
throw new IllegalArgumentException("Invalid rounding mode: " + mode);
}
}
number = roundingIncReciprocal == 0.0 ? div * roundingInc : div / roundingIncReciprocal;
return number;
}
private static double epsilon = 0.00000000001;
/**
* @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);
}
private StringBuffer format(long number, StringBuffer result, FieldPosition fieldPosition,
boolean parseAttr) {
fieldPosition.setBeginIndex(0);
fieldPosition.setEndIndex(0);
// If we are to do rounding, we need to move into the BigDecimal
// domain in order to do divide/multiply correctly.
if (actualRoundingIncrementICU != null) {
return format(BigDecimal.valueOf(number), result, fieldPosition);
}
boolean isNegative = (number < 0);
if (isNegative)
number = -number;
// In general, long values always represent real finite numbers, so we don't have
// to check for +/- Infinity or NaN. However, there is one case we have to be
// careful of: The multiplier can push a number near MIN_VALUE or MAX_VALUE
// outside the legal range. We check for this before multiplying, and if it
// happens we use BigInteger instead.
if (multiplier != 1) {
boolean tooBig = false;
if (number < 0) { // This can only happen if number == Long.MIN_VALUE
long cutoff = Long.MIN_VALUE / multiplier;
tooBig = (number <= cutoff); // number == cutoff can only happen if multiplier == -1
} else {
long cutoff = Long.MAX_VALUE / multiplier;
tooBig = (number > cutoff);
}
if (tooBig) {
// [Spark/CDL] Use
// format_BigInteger_StringBuffer_FieldPosition_boolean instead
// parseAttr is used to judge whether to synthesize attributes.
return format(BigInteger.valueOf(isNegative ? -number : number), result,
fieldPosition, parseAttr);
}
}
number *= multiplier;
synchronized (digitList) {
digitList.set(number, precision(true));
return subformat(number, result, fieldPosition, isNegative, true, parseAttr);
}
}
/**
* Formats a BigInteger number.
*
* @stable ICU 2.0
*/
@Override
public StringBuffer format(BigInteger number, StringBuffer result,
FieldPosition fieldPosition) {
return format(number, result, fieldPosition, false);
}
private StringBuffer format(BigInteger number, StringBuffer result, FieldPosition fieldPosition,
boolean parseAttr) {
// If we are to do rounding, we need to move into the BigDecimal
// domain in order to do divide/multiply correctly.
if (actualRoundingIncrementICU != null) {
return format(new BigDecimal(number), result, fieldPosition);
}
if (multiplier != 1) {
number = number.multiply(BigInteger.valueOf(multiplier));
}
// At this point we are guaranteed a nonnegative finite
// number.
synchronized (digitList) {
digitList.set(number, precision(true));
return subformat(number.intValue(), result, fieldPosition, number.signum() < 0, true,
parseAttr);
}
}
/**
* Formats a BigDecimal number.
*
* @stable ICU 2.0
*/
@Override
public StringBuffer format(java.math.BigDecimal number, StringBuffer result,
FieldPosition fieldPosition) {
return format(number, result, fieldPosition, false);
}
private StringBuffer format(java.math.BigDecimal number, StringBuffer result,
FieldPosition fieldPosition,
boolean parseAttr) {
if (multiplier != 1) {
number = number.multiply(java.math.BigDecimal.valueOf(multiplier));
}
if (actualRoundingIncrement != null) {
number = number.divide(actualRoundingIncrement, 0, roundingMode).multiply(actualRoundingIncrement);
}
synchronized (digitList) {
digitList.set(number, precision(false), !useExponentialNotation &&
!areSignificantDigitsUsed());
return subformat(number.doubleValue(), result, fieldPosition, number.signum() < 0,
false, parseAttr);
}
}
/**
* Formats a BigDecimal number.
*
* @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
// for now. It isn't very efficient since it must create a conversion object to
// do math on the rounding increment. In the future we may try to clean this up,
// or even better, limit our support to just one flavor of BigDecimal.
if (multiplier != 1) {
number = number.multiply(BigDecimal.valueOf(multiplier), mathContext);
}
if (actualRoundingIncrementICU != null) {
number = number.divide(actualRoundingIncrementICU, 0, roundingMode)
.multiply(actualRoundingIncrementICU, mathContext);
}
synchronized (digitList) {
digitList.set(number, precision(false), !useExponentialNotation &&
!areSignificantDigitsUsed());
return subformat(number.doubleValue(), result, fieldPosition, number.signum() < 0,
false, false);
}
}
/**
* Returns true if a grouping separator belongs at the given position, based on whether
* grouping is in use and the values of the primary and secondary grouping interval.
*
* @param pos the number of integer digits to the right of the current position. Zero
* indicates the position after the rightmost integer digit.
* @return true if a grouping character belongs at the current position.
*/
private boolean isGroupingPosition(int pos) {
boolean result = false;
if (isGroupingUsed() && (pos > 0) && (groupingSize > 0)) {
if ((groupingSize2 > 0) && (pos > groupingSize)) {
result = ((pos - groupingSize) % groupingSize2) == 0;
} else {
result = pos % groupingSize == 0;
}
}
return result;
}
/**
* Return the number of fraction digits to display, or the total
* number of digits for significant digit formats and exponential
* formats.
*/
private int precision(boolean isIntegral) {
if (areSignificantDigitsUsed()) {
return getMaximumSignificantDigits();
} else if (useExponentialNotation) {
return getMinimumIntegerDigits() + getMaximumFractionDigits();
} else {
return isIntegral ? 0 : getMaximumFractionDigits();
}
}
private StringBuffer subformat(int number, StringBuffer result, FieldPosition fieldPosition,
boolean isNegative, boolean isInteger, boolean parseAttr) {
if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
// 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) {
// 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);
}
}
private StringBuffer subformat(String pluralCount, StringBuffer result, FieldPosition fieldPosition,
boolean isNegative, boolean isInteger, boolean parseAttr) {
// There are 2 ways to activate currency plural format: by applying a pattern with
// 3 currency sign directly, or by instantiate a decimal formatter using
// PLURALCURRENCYSTYLE. For both cases, the number of currency sign in the
// pattern is 3. Even if the number of currency sign in the pattern is 3, it does
// not mean we need to reset the pattern. For 1st case, we do not need to reset
// pattern. For 2nd case, we might need to reset pattern, if the default pattern
// (corresponding to plural count 'other') we use is different from the pattern
// based on 'pluralCount'.
//
// style is only valid when decimal formatter is constructed through
// DecimalFormat(pattern, symbol, style)
if (style == NumberFormat.PLURALCURRENCYSTYLE) {
// May need to reset pattern if the style is PLURALCURRENCYSTYLE.
String currencyPluralPattern = currencyPluralInfo.getCurrencyPluralPattern(pluralCount);
if (formatPattern.equals(currencyPluralPattern) == false) {
applyPatternWithoutExpandAffix(currencyPluralPattern, false);
}
}
// Expand the affix to the right name according to the plural rule. This is only
// used for currency plural formatting. Currency plural name is not a fixed
// static one, it is a dynamic name based on the currency plural count. So, the
// affixes need to be expanded here. For other cases, the affix is a static one
// based on pattern alone, and it is already expanded during applying pattern, or
// setDecimalFormatSymbols, or setCurrency.
expandAffixAdjustWidth(pluralCount);
return subformat(result, fieldPosition, isNegative, isInteger, parseAttr);
}
/**
* Complete the formatting of a finite number. On entry, the
* digitList must be filled in with the correct digits.
*/
private StringBuffer subformat(StringBuffer result, FieldPosition fieldPosition,
boolean isNegative, boolean isInteger, boolean parseAttr) {
// NOTE: This isn't required anymore because DigitList takes care of this.
//
// // The negative of the exponent represents the number of leading // zeros
// between the decimal and the first non-zero digit, for // a value < 0.1 (e.g.,
// for 0.00123, -fExponent == 2). If this // is more than the maximum fraction
// digits, then we have an underflow // for the printed representation. We
// recognize this here and set // the DigitList representation to zero in this
// situation.
//
// if (-digitList.decimalAt >= getMaximumFractionDigits())
// {
// digitList.count = 0;
// }
// Per bug 4147706, DecimalFormat must respect the sign of numbers which format as
// zero. This allows sensible computations and preserves relations such as
// signum(1/x) = signum(x), where x is +Infinity or -Infinity. Prior to this fix,
// we always formatted zero values as if they were positive. Liu 7/6/98.
if (digitList.isZero()) {
digitList.decimalAt = 0; // Normalize
}
int prefixLen = appendAffix(result, isNegative, true, parseAttr);
if (useExponentialNotation) {
subformatExponential(result, fieldPosition, parseAttr);
} else {
subformatFixed(result, fieldPosition, isInteger, parseAttr);
}
int suffixLen = appendAffix(result, isNegative, false, parseAttr);
addPadding(result, fieldPosition, prefixLen, suffixLen);
return result;
}
private void subformatFixed(StringBuffer result,
FieldPosition fieldPosition,
boolean isInteger,
boolean parseAttr) {
char [] digits = symbols.getDigitsLocal();
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 {
// Output a zero (leading or trailing)
result.append(digits[0]);
if (sigCount > 0) {
++sigCount;
}
}
// 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());
}
}
}
// 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());
}
result.append(decimal);
if (fieldPosition.getFieldAttribute() == Field.DECIMAL_SEPARATOR) {
fieldPosition.setEndIndex(result.length());
}
// [Spark/CDL] Add attribute for decimal separator
if (parseAttr) {
addAttribute(Field.DECIMAL_SEPARATOR, result.length() - 1, 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());
}
// [Spark/CDL] Record the begin index of fraction part.
int fracBegin = result.length();
recordFractionDigits = fieldPosition instanceof UFieldPosition;
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;
}
// 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 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;
}
}
// 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;
}
}
// 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);
}
// [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;
}
}
if (maxIntDig > minIntDig) {
minIntDig = 1;
}
}
long fractionalDigits = 0;
int fractionalDigitsCount = 0;
boolean recordFractionDigits = false;
// 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".
// 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.
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());
}
// [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());
}
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
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());
}
if (recordFractionDigits) {
((UFieldPosition) fieldPosition).setFractionDigits(fractionalDigitsCount, fractionalDigits);
}
// [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());
}
}
// 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,
int suffixLen) {
if (formatWidth > 0) {
int len = formatWidth - result.length();
if (len > 0) {
char[] padding = new char[len];
for (int i = 0; i < len; ++i) {
padding[i] = pad;
}
switch (padPosition) {
case PAD_AFTER_PREFIX:
result.insert(prefixLen, padding);
break;
case PAD_BEFORE_PREFIX:
result.insert(0, padding);
break;
case PAD_BEFORE_SUFFIX:
result.insert(result.length() - suffixLen, padding);
break;
case PAD_AFTER_SUFFIX:
result.append(padding);
break;
}
if (padPosition == PAD_BEFORE_PREFIX || padPosition == PAD_AFTER_PREFIX) {
fieldPosition.setBeginIndex(fieldPosition.getBeginIndex() + len);
fieldPosition.setEndIndex(fieldPosition.getEndIndex() + len);
}
}
}
}
/**
* Parses the given string, returning a Number
object to represent the
* parsed value. Double
objects are returned to represent non-integral
* values which cannot be stored in a BigDecimal
. These are
* NaN
, infinity, -infinity, and -0.0. If {@link #isParseBigDecimal()} is
* false (the default), all other values are returned as Long
,
* BigInteger
, or BigDecimal
values, in that order of
* preference. If {@link #isParseBigDecimal()} is true, all other values are returned
* as BigDecimal
valuse. If the parse fails, null is returned.
*
* @param text the string to be parsed
* @param parsePosition defines the position where parsing is to begin, and upon
* return, the position where parsing left off. If the position has not changed upon
* return, then parsing failed.
* @return a Number
object with the parsed value or
* null
if the parse failed
* @stable ICU 2.0
*/
@Override
public Number parse(String text, ParsePosition parsePosition) {
return (Number) parse(text, parsePosition, null);
}
/**
* Parses text from the given string as a CurrencyAmount. Unlike the parse() method,
* this method will attempt to parse a generic currency name, searching for a match of
* this object's locale's currency display names, or for a 3-letter ISO currency
* 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 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
* @stable ICU 49
*/
@Override
public CurrencyAmount parseCurrency(CharSequence text, ParsePosition pos) {
Currency[] currency = new Currency[1];
return (CurrencyAmount) parse(text.toString(), pos, currency);
}
/**
* Parses the given text as either a Number or a CurrencyAmount.
*
* @param text the string to parse
* @param parsePosition 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.
* @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, Currency[] currency) {
int backup;
int i = backup = parsePosition.getIndex();
// Handle NaN as a special case:
// Skip padding characters, if around prefix
if (formatWidth > 0 &&
(padPosition == PAD_BEFORE_PREFIX || padPosition == PAD_AFTER_PREFIX)) {
i = skipPadding(text, i);
}
if (text.regionMatches(i, symbols.getNaN(), 0, symbols.getNaN().length())) {
i += symbols.getNaN().length();
// Skip padding characters, if around suffix
if (formatWidth > 0 && (padPosition == PAD_BEFORE_SUFFIX ||
padPosition == PAD_AFTER_SUFFIX)) {
i = skipPadding(text, i);
}
parsePosition.setIndex(i);
return new Double(Double.NaN);
}
// NaN parse failed; start over
i = backup;
boolean[] status = new boolean[STATUS_LENGTH];
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,
false, Currency.SYMBOL_NAME)) {
parsePosition.setIndex(backup);
return null;
}
}
Number n = null;
// Handle infinity
if (status[STATUS_INFINITE]) {
n = new Double(status[STATUS_POSITIVE] ? Double.POSITIVE_INFINITY :
Double.NEGATIVE_INFINITY);
}
// Handle underflow
else if (status[STATUS_UNDERFLOW]) {
n = status[STATUS_POSITIVE] ? new Double("0.0") : new Double("-0.0");
}
// Handle -0.0
else if (!status[STATUS_POSITIVE] && digitList.isZero()) {
n = new Double("-0.0");
}
else {
// Do as much of the multiplier conversion as possible without
// losing accuracy.
int mult = multiplier; // Don't modify this.multiplier
while (mult % 10 == 0) {
--digitList.decimalAt;
mult /= 10;
}
// Handle integral values
if (!parseBigDecimal && mult == 1 && digitList.isIntegral()) {
// hack quick long
if (digitList.decimalAt < 12) { // quick check for long
long l = 0;
if (digitList.count > 0) {
int nx = 0;
while (nx < digitList.count) {
l = l * 10 + (char) digitList.digits[nx++] - '0';
}
while (nx++ < digitList.decimalAt) {
l *= 10;
}
if (!status[STATUS_POSITIVE]) {
l = -l;
}
}
n = Long.valueOf(l);
} else {
BigInteger big = digitList.getBigInteger(status[STATUS_POSITIVE]);
n = (big.bitLength() < 64) ? (Number) Long.valueOf(big.longValue()) : (Number) big;
}
}
// Handle non-integral values or the case where parseBigDecimal is set
else {
BigDecimal big = digitList.getBigDecimalICU(status[STATUS_POSITIVE]);
n = big;
if (mult != 1) {
n = big.divide(BigDecimal.valueOf(mult), mathContext);
}
}
}
// Assemble into CurrencyAmount if necessary
return (currency != null) ? (Object) new CurrencyAmount(n, currency[0]) : (Object) n;
}
private boolean parseForCurrency(String text, ParsePosition parsePosition,
Currency[] currency, boolean[] status) {
int origPos = parsePosition.getIndex();
if (!isReadyForParsing) {
int savedCurrencySignCount = currencySignCount;
setupCurrencyAffixForAllPatterns();
// reset pattern back
if (savedCurrencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
applyPatternWithoutExpandAffix(formatPattern, false);
} else {
applyPattern(formatPattern, false);
}
isReadyForParsing = true;
}
int maxPosIndex = origPos;
int maxErrorPos = -1;
boolean[] savedStatus = null;
// First, parse against current pattern.
// Since current pattern could be set by applyPattern(),
// it could be an arbitrary pattern, and it may not be the one
// defined in current locale.
boolean[] tmpStatus = new boolean[STATUS_LENGTH];
ParsePosition tmpPos = new ParsePosition(origPos);
DigitList tmpDigitList = new DigitList();
boolean found;
if (style == NumberFormat.PLURALCURRENCYSTYLE) {
found = subparse(text, tmpPos, tmpDigitList, tmpStatus, currency,
negPrefixPattern, negSuffixPattern, posPrefixPattern, posSuffixPattern,
true, Currency.LONG_NAME);
} else {
found = subparse(text, tmpPos, tmpDigitList, tmpStatus, currency,
negPrefixPattern, negSuffixPattern, posPrefixPattern, posSuffixPattern,
true, Currency.SYMBOL_NAME);
}
if (found) {
if (tmpPos.getIndex() > maxPosIndex) {
maxPosIndex = tmpPos.getIndex();
savedStatus = tmpStatus;
digitList = tmpDigitList;
}
} else {
maxErrorPos = tmpPos.getErrorIndex();
}
// Then, parse against affix patterns. Those are currency patterns and currency
// plural patterns defined in the locale.
for (AffixForCurrency affix : affixPatternsForCurrency) {
tmpStatus = new boolean[STATUS_LENGTH];
tmpPos = new ParsePosition(origPos);
tmpDigitList = new DigitList();
boolean result = subparse(text, tmpPos, tmpDigitList, tmpStatus, currency,
affix.getNegPrefix(), affix.getNegSuffix(),
affix.getPosPrefix(), affix.getPosSuffix(),
true, affix.getPatternType());
if (result) {
found = true;
if (tmpPos.getIndex() > maxPosIndex) {
maxPosIndex = tmpPos.getIndex();
savedStatus = tmpStatus;
digitList = tmpDigitList;
}
} else {
maxErrorPos = (tmpPos.getErrorIndex() > maxErrorPos) ? tmpPos.getErrorIndex()
: maxErrorPos;
}
}
// Finally, parse against simple affix to find the match. For example, in
// TestMonster suite, if the to-be-parsed text is "-\u00A40,00".
// 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();
// Disable complex currency parsing and try it again.
boolean result = subparse(text, tmpPos, tmpDigitList, tmpStatus, currency,
negativePrefix, negativeSuffix, positivePrefix, positiveSuffix,
false /* disable complex currency parsing */, Currency.SYMBOL_NAME);
if (result) {
if (tmpPos.getIndex() > maxPosIndex) {
maxPosIndex = tmpPos.getIndex();
savedStatus = tmpStatus;
digitList = tmpDigitList;
}
found = true;
} else {
maxErrorPos = (tmpPos.getErrorIndex() > maxErrorPos) ? tmpPos.getErrorIndex() :
maxErrorPos;
}
if (!found) {
// parsePosition.setIndex(origPos);
parsePosition.setErrorIndex(maxErrorPos);
} else {
parsePosition.setIndex(maxPosIndex);
parsePosition.setErrorIndex(-1);
for (int index = 0; index < STATUS_LENGTH; ++index) {
status[index] = savedStatus[index];
}
}
return found;
}
// Get affix patterns used in locale's currency pattern (NumberPatterns[1]) and
// currency plural pattern (CurrencyUnitPatterns).
private void setupCurrencyAffixForAllPatterns() {
if (currencyPluralInfo == null) {
currencyPluralInfo = new CurrencyPluralInfo(symbols.getULocale());
}
affixPatternsForCurrency = new HashSet();
// save the current pattern, since it will be changed by
// applyPatternWithoutExpandAffix
String savedFormatPattern = formatPattern;
// CURRENCYSTYLE and ISOCURRENCYSTYLE should have the same prefix and suffix, so,
// only need to save one of them. Here, chose onlyApplyPatternWithoutExpandAffix
// without saving the actualy pattern in 'pattern' data member. TODO: is it uloc?
applyPatternWithoutExpandAffix(getPattern(symbols.getULocale(), NumberFormat.CURRENCYSTYLE),
false);
AffixForCurrency affixes = new AffixForCurrency(
negPrefixPattern, negSuffixPattern, posPrefixPattern, posSuffixPattern,
Currency.SYMBOL_NAME);
affixPatternsForCurrency.add(affixes);
// add plural pattern
Iterator iter = currencyPluralInfo.pluralPatternIterator();
Set currencyUnitPatternSet = new HashSet();
while (iter.hasNext()) {
String pluralCount = iter.next();
String currencyPattern = currencyPluralInfo.getCurrencyPluralPattern(pluralCount);
if (currencyPattern != null &&
currencyUnitPatternSet.contains(currencyPattern) == false) {
currencyUnitPatternSet.add(currencyPattern);
applyPatternWithoutExpandAffix(currencyPattern, false);
affixes = new AffixForCurrency(negPrefixPattern, negSuffixPattern, posPrefixPattern,
posSuffixPattern, Currency.LONG_NAME);
affixPatternsForCurrency.add(affixes);
}
}
// reset pattern back
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;
private static final int STATUS_INFINITE = 0;
private static final int STATUS_POSITIVE = 1;
private static final int STATUS_UNDERFLOW = 2;
private static final int STATUS_LENGTH = 3;
private static final UnicodeSet dotEquivalents = new UnicodeSet(
//"[.\u2024\u3002\uFE12\uFE52\uFF0E\uFF61]"
0x002E, 0x002E,
0x2024, 0x2024,
0x3002, 0x3002,
0xFE12, 0xFE12,
0xFE52, 0xFE52,
0xFF0E, 0xFF0E,
0xFF61, 0xFF61).freeze();
private static final UnicodeSet commaEquivalents = new UnicodeSet(
//"[,\u060C\u066B\u3001\uFE10\uFE11\uFE50\uFE51\uFF0C\uFF64]"
0x002C, 0x002C,
0x060C, 0x060C,
0x066B, 0x066B,
0x3001, 0x3001,
0xFE10, 0xFE11,
0xFE50, 0xFE51,
0xFF0C, 0xFF0C,
0xFF64, 0xFF64).freeze();
// private static final UnicodeSet otherGroupingSeparators = new UnicodeSet(
// //"[\\ '\u00A0\u066C\u2000-\u200A\u2018\u2019\u202F\u205F\u3000\uFF07]"
// 0x0020, 0x0020,
// 0x0027, 0x0027,
// 0x00A0, 0x00A0,
// 0x066C, 0x066C,
// 0x2000, 0x200A,
// 0x2018, 0x2019,
// 0x202F, 0x202F,
// 0x205F, 0x205F,
// 0x3000, 0x3000,
// 0xFF07, 0xFF07).freeze();
private static final UnicodeSet strictDotEquivalents = new UnicodeSet(
//"[.\u2024\uFE52\uFF0E\uFF61]"
0x002E, 0x002E,
0x2024, 0x2024,
0xFE52, 0xFE52,
0xFF0E, 0xFF0E,
0xFF61, 0xFF61).freeze();
private static final UnicodeSet strictCommaEquivalents = new UnicodeSet(
//"[,\u066B\uFE10\uFE50\uFF0C]"
0x002C, 0x002C,
0x066B, 0x066B,
0xFE10, 0xFE10,
0xFE50, 0xFE50,
0xFF0C, 0xFF0C).freeze();
// private static final UnicodeSet strictOtherGroupingSeparators = new UnicodeSet(
// //"[\\ '\u00A0\u066C\u2000-\u200A\u2018\u2019\u202F\u205F\u3000\uFF07]"
// 0x0020, 0x0020,
// 0x0027, 0x0027,
// 0x00A0, 0x00A0,
// 0x066C, 0x066C,
// 0x2000, 0x200A,
// 0x2018, 0x2019,
// 0x202F, 0x202F,
// 0x205F, 0x205F,
// 0x3000, 0x3000,
// 0xFF07, 0xFF07).freeze();
private static final UnicodeSet defaultGroupingSeparators =
// new UnicodeSet(dotEquivalents).addAll(commaEquivalents)
// .addAll(otherGroupingSeparators).freeze();
new UnicodeSet(
0x0020, 0x0020,
0x0027, 0x0027,
0x002C, 0x002C,
0x002E, 0x002E,
0x00A0, 0x00A0,
0x060C, 0x060C,
0x066B, 0x066C,
0x2000, 0x200A,
0x2018, 0x2019,
0x2024, 0x2024,
0x202F, 0x202F,
0x205F, 0x205F,
0x3000, 0x3002,
0xFE10, 0xFE12,
0xFE50, 0xFE52,
0xFF07, 0xFF07,
0xFF0C, 0xFF0C,
0xFF0E, 0xFF0E,
0xFF61, 0xFF61,
0xFF64, 0xFF64).freeze();
private static final UnicodeSet strictDefaultGroupingSeparators =
// new UnicodeSet(strictDotEquivalents).addAll(strictCommaEquivalents)
// .addAll(strictOtherGroupingSeparators).freeze();
new UnicodeSet(
0x0020, 0x0020,
0x0027, 0x0027,
0x002C, 0x002C,
0x002E, 0x002E,
0x00A0, 0x00A0,
0x066B, 0x066C,
0x2000, 0x200A,
0x2018, 0x2019,
0x2024, 0x2024,
0x202F, 0x202F,
0x205F, 0x205F,
0x3000, 0x3000,
0xFE10, 0xFE10,
0xFE50, 0xFE50,
0xFE52, 0xFE52,
0xFF07, 0xFF07,
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();
// equivalent grouping and decimal support
static final boolean skipExtendedSeparatorParsing = ICUConfig.get(
"com.ibm.icu.text.DecimalFormat.SkipExtendedSeparatorParsing", "false")
.equals("true");
// 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 (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,
* until an unparseable character is seen.
*
* @param text the string to parse.
* @param parsePosition the position at which to being parsing. Upon return, the first
* unparseable character.
* @param digits the DigitList to set to the parsed value.
* @param status Upon return contains boolean status flags indicating whether the
* value was infinite and whether it was positive.
* @param currency return value for parsed currency, for generic currency parsing
* mode, or null for normal parsing. In generic currency parsing mode, any currency is
* parsed, not just the currency that this formatter is set to.
* @param negPrefix negative prefix pattern
* @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, boolean parseComplexCurrency, int type) {
int position = parsePosition.getIndex();
int oldStart = parsePosition.getIndex();
// Match padding before prefix
if (formatWidth > 0 && padPosition == PAD_BEFORE_PREFIX) {
position = skipPadding(text, position);
}
// Match positive and negative prefixes; prefer longest match.
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;
} else if (negMatch > posMatch) {
posMatch = -1;
}
}
if (posMatch >= 0) {
position += posMatch;
} else if (negMatch >= 0) {
position += negMatch;
} else {
parsePosition.setErrorIndex(position);
return false;
}
// Match padding after prefix
if (formatWidth > 0 && padPosition == PAD_AFTER_PREFIX) {
position = skipPadding(text, position);
}
// process digits or Inf, find decimal position
status[STATUS_INFINITE] = false;
if (text.regionMatches(position, symbols.getInfinity(), 0,
symbols.getInfinity().length())) {
position += symbols.getInfinity().length();
status[STATUS_INFINITE] = true;
} else {
// We now have a string of digits, possibly with grouping symbols, and decimal
// points. We want to process these into a DigitList. We don't want to put a
// bunch of leading zeros into the DigitList though, so we keep track of the
// location of the decimal point, put only significant digits into the
// DigitList, and adjust the exponent as needed.
digits.decimalAt = digits.count = 0;
char [] digitSymbols = symbols.getDigitsLocal();
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 sawGrouping = false;
boolean sawExponent = false;
boolean sawDigit = false;
long exponent = 0; // Set to the exponent value, if any
int digit = 0;
// strict parsing
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;
UnicodeSet decimalEquiv = skipExtendedSeparatorParsing ? UnicodeSet.EMPTY :
getEquivalentDecimals(decimal, strictParse);
UnicodeSet groupEquiv = skipExtendedSeparatorParsing ? UnicodeSet.EMPTY :
(strictParse ? strictDefaultGroupingSeparators : defaultGroupingSeparators);
// We have to track digitCount ourselves, because digits.count will pin when
// the maximum allowable digits is reached.
int digitCount = 0;
int backup = -1;
int ch;
for (; position < text.length(); position += UTF16.getCharCount(ch)) {
ch = UTF16.charAt(text,position);
// We recognize all digit ranges, not only the Latin digit range
// '0'..'9'. We do so by using the UCharacter.digit() method, which
// converts a valid Unicode digit to the range 0..9.
//
// The character 'ch' may be a digit. If so, place its value from 0 to 9
// in 'digit'. First try using the locale digit, which may or MAY NOT be a
// standard Unicode digit range. If this fails, try using the standard
// Unicode digit ranges by calling UCharacter.digit(). If this also fails,
// digit will have a value outside the range 0..9.
digit = ch - digitSymbols[0];
if (digit < 0 || digit > 9)
digit = UCharacter.digit(ch, 10);
if (digit < 0 || digit > 9) {
for ( digit = 0 ; digit < 10 ; digit++) {
if ( ch == digitSymbols[digit] )
break;
}
}
if (digit == 0) {
// Cancel out backup setting (see grouping handler below)
if (strictParse && backup != -1) {
// comma followed by digit, so group before comma is a secondary
// 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, digitStart, position) - 1 > gs2)) {
strictFail = true;
break;
}
lastGroup = backup;
}
backup = -1; // Do this BEFORE continue statement below!!!
sawDigit = true;
// Handle leading zeros
if (digits.count == 0) {
if (!sawDecimal) {
// Ignore leading zeros in integer part of number.
continue;
}
// If we have seen the decimal, but no significant digits yet,
// then we account for leading zeros by decrementing the
// digits.decimalAt into negative values.
--digits.decimalAt;
} else {
++digitCount;
digits.append((char) (digit + '0'));
}
} else if (digit > 0 && digit <= 9) // [sic] digit==0 handled above
{
if (strictParse) {
if (backup != -1) {
if ((lastGroup != -1 && countCodePoints(text, lastGroup, backup) - 1 != gs2)
|| (lastGroup == -1 && countCodePoints(text, digitStart, position) - 1 > gs2)) {
strictFail = true;
break;
}
lastGroup = backup;
}
}
sawDigit = true;
++digitCount;
digits.append((char) (digit + '0'));
// Cancel out backup setting (see grouping handler below)
backup = -1;
} else if (ch == decimal) {
if (strictParse) {
if (backup != -1 ||
(lastGroup != -1 && countCodePoints(text,lastGroup,position) != groupingSize + 1)) {
strictFail = true;
break;
}
}
// If we're only parsing integers, or if we ALREADY saw the decimal,
// then don't parse this one.
if (isParseIntegerOnly() || sawDecimal) {
break;
}
digits.decimalAt = digitCount; // Not digits.count!
sawDecimal = true;
} else if (isGroupingUsed() && ch == grouping) {
if (sawDecimal) {
break;
}
if (strictParse) {
if ((!sawDigit || backup != -1)) {
// leading group, or two group separators in a row
strictFail = true;
break;
}
}
// Ignore grouping characters, if we are using them, but require that
// they be followed by a digit. Otherwise we backup and reprocess
// them.
backup = position;
sawGrouping = true;
} else if (!sawDecimal && decimalEquiv.contains(ch)) {
if (strictParse) {
if (backup != -1 ||
(lastGroup != -1 && countCodePoints(text,lastGroup,position) != groupingSize + 1)) {
strictFail = true;
break;
}
}
// If we're only parsing integers, then don't parse this one.
if (isParseIntegerOnly())
break;
digits.decimalAt = digitCount; // Not digits.count!
// Once we see a decimal separator character, we only accept that
// decimal separator character from then on.
decimal = (char) ch;
sawDecimal = true;
} else if (isGroupingUsed() && !sawGrouping && groupEquiv.contains(ch)) {
if (sawDecimal) {
break;
}
if (strictParse) {
if ((!sawDigit || backup != -1)) {
// leading group, or two group separators in a row
strictFail = true;
break;
}
}
// Once we see a grouping character, we only accept that grouping
// character from then on.
grouping = (char) ch;
// Ignore grouping characters, if we are using them, but require that
// they be followed by a digit. Otherwise we backup and reprocess
// them.
backup = position;
sawGrouping = true;
} else if (!sawExponent && text.regionMatches(true, position, exponentSep, 0, exponentSep.length())) {
// Parse sign, if present
boolean negExp = false;
int pos = position + exponentSep.length();
if (pos < text.length()) {
ch = UTF16.charAt(text,pos);
if (ch == symbols.getPlusSign()) {
++pos;
} else if (ch == symbols.getMinusSign()) {
++pos;
negExp = true;
}
}
DigitList exponentDigits = new DigitList();
exponentDigits.count = 0;
while (pos < text.length()) {
digit = UTF16.charAt(text,pos) - digitSymbols[0];
if (digit < 0 || digit > 9) {
// Can't parse "[1E0]" when pattern is "0.###E0;[0.###E0]"
// Should update reassign the value of 'ch' in the code: digit
// = Character.digit(ch, 10); [Richard/GCL]
digit = UCharacter.digit(UTF16.charAt(text,pos), 10);
}
if (digit >= 0 && digit <= 9) {
exponentDigits.append((char) (digit + '0'));
pos += UTF16.getCharCount(UTF16.charAt(text,pos));
} else {
break;
}
}
if (exponentDigits.count > 0) {
// defer strict parse until we know we have a bona-fide exponent
if (strictParse) {
if (backup != -1 || lastGroup != -1) {
strictFail = true;
break;
}
}
// Quick overflow check for exponential part. Actual limit check
// will be done later in this code.
if (exponentDigits.count > 10 /* maximum decimal digits for int */) {
if (negExp) {
// set underflow flag
status[STATUS_UNDERFLOW] = true;
} else {
// set infinite flag
status[STATUS_INFINITE] = true;
}
} else {
exponentDigits.decimalAt = exponentDigits.count;
exponent = exponentDigits.getLong();
if (negExp) {
exponent = -exponent;
}
}
position = pos; // Advance past the exponent
sawExponent = true;
}
break; // Whether we fail or succeed, we exit this loop
} else {
break;
}
}
if (backup != -1)
position = backup;
// If there was no decimal point we have an integer
if (!sawDecimal)
digits.decimalAt = digitCount; // Not digits.count!
// check for strict parse errors
if (strictParse && !sawDecimal) {
if (lastGroup != -1 && countCodePoints(text,lastGroup,position) != groupingSize + 1) {
strictFail = true;
}
}
if (strictFail) {
// only set with strictParse and a leading zero error leading zeros are an
// error with strict parsing except immediately before nondigit (except
// group separator followed by digit), or end of text.
parsePosition.setIndex(oldStart);
parsePosition.setErrorIndex(position);
return false;
}
// Adjust for exponent, if any
exponent += digits.decimalAt;
if (exponent < -getParseMaxDigits()) {
status[STATUS_UNDERFLOW] = true;
} else if (exponent > getParseMaxDigits()) {
status[STATUS_INFINITE] = true;
} else {
digits.decimalAt = (int) exponent;
}
// If none of the text string was recognized. For example, parse "x" with
// pattern "#0.00" (return index and error index both 0) parse "$" with
// pattern "$#0.00". (return index 0 and error index 1).
if (!sawDigit && digitCount == 0) {
parsePosition.setIndex(oldStart);
parsePosition.setErrorIndex(oldStart);
return false;
}
}
// Match padding before suffix
if (formatWidth > 0 && padPosition == PAD_BEFORE_SUFFIX) {
position = skipPadding(text, position);
}
// Match positive and negative suffixes; prefer longest match.
if (posMatch >= 0) {
posMatch = compareAffix(text, position, false, false, posSuffix, parseComplexCurrency, type, currency);
}
if (negMatch >= 0) {
negMatch = compareAffix(text, position, true, false, negSuffix, parseComplexCurrency, type, currency);
}
if (posMatch >= 0 && negMatch >= 0) {
if (posMatch > negMatch) {
negMatch = -1;
} else if (negMatch > posMatch) {
posMatch = -1;
}
}
// Fail if neither or both
if ((posMatch >= 0) == (negMatch >= 0)) {
parsePosition.setErrorIndex(position);
return false;
}
position += (posMatch >= 0 ? posMatch : negMatch);
// Match padding after suffix
if (formatWidth > 0 && padPosition == PAD_AFTER_SUFFIX) {
position = skipPadding(text, position);
}
parsePosition.setIndex(position);
status[STATUS_POSITIVE] = (posMatch >= 0);
if (parsePosition.getIndex() == oldStart) {
parsePosition.setErrorIndex(position);
return false;
}
return true;
}
// Utility method used to count the number of codepoints
private int countCodePoints(String str,int start, int end) {
int count = 0;
int index = start;
while ( index < end ) {
count++;
index += UTF16.getCharCount(UTF16.charAt(str, index));
}
return count;
}
/**
* Returns a set of characters equivalent to the given desimal separator used for
* parsing number. This method may return an empty set.
*/
private UnicodeSet getEquivalentDecimals(char decimal, boolean strictParse) {
UnicodeSet equivSet = UnicodeSet.EMPTY;
if (strictParse) {
if (strictDotEquivalents.contains(decimal)) {
equivSet = strictDotEquivalents;
} else if (strictCommaEquivalents.contains(decimal)) {
equivSet = strictCommaEquivalents;
}
} else {
if (dotEquivalents.contains(decimal)) {
equivSet = dotEquivalents;
} else if (commaEquivalents.contains(decimal)) {
equivSet = commaEquivalents;
}
}
return equivSet;
}
/**
* Starting at position, advance past a run of pad characters, if any. Return the
* index of the first character after position that is not a pad character. Result is
* >= position.
*/
private final int skipPadding(String text, int position) {
while (position < text.length() && text.charAt(position) == pad) {
++position;
}
return position;
}
/**
* Returns 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
* white space are determined differently; see code.
*
* @param text input text
* @param pos offset into input at which to begin matching
* @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
* is parsed, not just the currency that this formatter is set to.
* @return length of input that matches, or -1 if match failure
*/
private int compareAffix(String text, int pos, boolean isNegative, boolean isPrefix,
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) {
return compareSimpleAffix(isNegative ? negativePrefix : positivePrefix, text, pos);
} else {
return compareSimpleAffix(isNegative ? negativeSuffix : positiveSuffix, text, pos);
}
}
/**
* 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 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
* white space are determined differently; see code.
*
* @param affix pattern string, taken as a literal
* @param input input text
* @param pos offset into input at which to begin matching
* @return length of input that matches, or -1 if match failure
*/
private static int compareSimpleAffix(String affix, String input, int pos) {
int start = pos;
// 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
// that U+200F and U+0020 are Pattern_White_Space but only U+0020 is
// 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()) {
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 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.
int s = pos;
pos = skipUWhiteSpace(input, pos);
if (pos == s && !literalMatch) {
return -1;
}
// 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 trimmedAffix.
i = skipUWhiteSpace(trimmedAffix, i);
} 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.
*/
private static int skipPatternWhiteSpace(String text, int pos) {
while (pos < text.length()) {
int c = UTF16.charAt(text, pos);
if (!PatternProps.isWhiteSpace(c)) {
break;
}
pos += UTF16.getCharCount(c);
}
return pos;
}
/**
* Skips over a run of zero or more isUWhiteSpace() characters at pos in text.
*/
private static int skipUWhiteSpace(String text, int pos) {
while (pos < text.length()) {
int c = UTF16.charAt(text, pos);
if (!UCharacter.isUWhiteSpace(c)) {
break;
}
pos += UTF16.getCharCount(c);
}
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
* @param text input text
* @param pos offset into input at which to begin matching
* @param type parse 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 is parsed, not just the
* currency that this formatter is set to.
* @return position after the matched text, or -1 if match failure
*/
private int compareComplexAffix(String affixPat, String text, int pos, int type,
Currency[] currency) {
int start = pos;
for (int i = 0; i < affixPat.length() && pos >= 0;) {
char c = affixPat.charAt(i++);
if (c == QUOTE) {
for (;;) {
int j = affixPat.indexOf(QUOTE, i);
if (j == i) {
pos = match(text, pos, QUOTE);
i = j + 1;
break;
} else if (j > i) {
pos = match(text, pos, affixPat.substring(i, j));
i = j + 1;
if (i < affixPat.length() && affixPat.charAt(i) == QUOTE) {
pos = match(text, pos, QUOTE);
++i;
// loop again
} else {
break;
}
} else {
// Unterminated quote; should be caught by apply
// pattern.
throw new RuntimeException();
}
}
continue;
}
switch (c) {
case CURRENCY_SIGN:
// since the currency names in choice format is saved the same way as
// other currency names, do not need to do currency choice parsing here.
// the general currency parsing parse against all names, including names
// in choice format. assert(currency != null || (getCurrency() != null &&
// currencyChoice != null));
boolean intl = i < affixPat.length() && affixPat.charAt(i) == CURRENCY_SIGN;
if (intl) {
++i;
}
boolean plural = i < affixPat.length() && affixPat.charAt(i) == CURRENCY_SIGN;
if (plural) {
++i;
intl = false;
}
// Parse generic currency -- anything for which we have a display name, or
// any 3-letter ISO code. Try to parse display name for our locale; first
// determine our locale. TODO: use locale in CurrencyPluralInfo
ULocale uloc = getLocale(ULocale.VALID_LOCALE);
if (uloc == null) {
// applyPattern has been called; use the symbols
uloc = symbols.getLocale(ULocale.VALID_LOCALE);
}
// Delegate parse of display name => ISO code to Currency
ParsePosition ppos = new ParsePosition(pos);
// using Currency.parse to handle mixed style parsing.
String iso = Currency.parse(uloc, text, type, ppos);
// If parse succeeds, populate currency[0]
if (iso != null) {
if (currency != null) {
currency[0] = Currency.getInstance(iso);
} else {
// The formatter is currency-style but the client has not requested
// the value of the parsed currency. In this case, if that value does
// not match the formatter's current value, then the parse fails.
Currency effectiveCurr = getEffectiveCurrency();
if (iso.compareTo(effectiveCurr.getCurrencyCode()) != 0) {
pos = -1;
continue;
}
}
pos = ppos.getIndex();
} else {
pos = -1;
}
continue;
case PATTERN_PERCENT:
c = symbols.getPercent();
break;
case PATTERN_PER_MILLE:
c = symbols.getPerMill();
break;
case PATTERN_MINUS:
c = symbols.getMinusSign();
break;
}
pos = match(text, pos, c);
if (PatternProps.isWhiteSpace(c)) {
i = skipPatternWhiteSpace(affixPat, i);
}
}
return pos - start;
}
/**
* Matches a single character at text[pos] and return the index of the next character
* upon success. Return -1 on failure. If ch is a Pattern_White_Space then match a run of
* white space in text.
*/
static final int match(String text, int pos, int ch) {
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
int s = pos;
pos = skipPatternWhiteSpace(text, pos);
if (pos == s) {
return -1;
}
return pos;
}
if (pos >= text.length() || UTF16.charAt(text, pos) != ch) {
return -1;
}
pos = skipBidiMarks(text, pos + UTF16.getCharCount(ch));
return pos;
}
/**
* Matches a string at text[pos] and return the index of the next character upon
* success. Return -1 on failure. Match a run of white space in str with a run of
* white space in text.
*/
static final int match(String text, int pos, String str) {
for (int i = 0; i < str.length() && pos >= 0;) {
int ch = UTF16.charAt(str, i);
i += UTF16.getCharCount(ch);
pos = match(text, pos, ch);
if (PatternProps.isWhiteSpace(ch)) {
i = skipPatternWhiteSpace(str, i);
}
}
return pos;
}
/**
* Returns a copy of the decimal format symbols used by this format.
*
* @return desired DecimalFormatSymbols
* @see DecimalFormatSymbols
* @stable ICU 2.0
*/
public DecimalFormatSymbols getDecimalFormatSymbols() {
try {
// don't allow multiple references
return (DecimalFormatSymbols) symbols.clone();
} catch (Exception foo) {
return null; // should never happen
}
}
/**
* Sets the decimal format symbols used by this format. The format uses a copy of the
* provided symbols.
*
* @param newSymbols desired DecimalFormatSymbols
* @see DecimalFormatSymbols
* @stable ICU 2.0
*/
public void setDecimalFormatSymbols(DecimalFormatSymbols newSymbols) {
symbols = (DecimalFormatSymbols) newSymbols.clone();
setCurrencyForSymbols();
expandAffixes(null);
}
/**
* Update the currency object to match the symbols. This method is used only when the
* caller has passed in a symbols object that may not be the default object for its
* locale.
*/
private void setCurrencyForSymbols() {
// Bug 4212072 Update the affix strings according to symbols in order to keep the
// affix strings up to date. [Richard/GCL]
// With the introduction of the Currency object, the currency symbols in the DFS
// object are ignored. For backward compatibility, we check any explicitly set DFS
// object. If it is a default symbols object for its locale, we change the
// currency object to one for that locale. If it is custom, we set the currency to
// null.
DecimalFormatSymbols def = new DecimalFormatSymbols(symbols.getULocale());
if (symbols.getCurrencySymbol().equals(def.getCurrencySymbol())
&& symbols.getInternationalCurrencySymbol()
.equals(def.getInternationalCurrencySymbol())) {
setCurrency(Currency.getInstance(symbols.getULocale()));
} else {
setCurrency(null);
}
}
/**
* Returns the positive prefix.
*
* Examples: +123, $123, sFr123
* @return the prefix
* @stable ICU 2.0
*/
public String getPositivePrefix() {
return positivePrefix;
}
/**
* Sets the positive prefix.
*
*
Examples: +123, $123, sFr123
* @param newValue the prefix
* @stable ICU 2.0
*/
public void setPositivePrefix(String newValue) {
positivePrefix = newValue;
posPrefixPattern = null;
}
/**
* Returns the negative prefix.
*
*
Examples: -123, ($123) (with negative suffix), sFr-123
*
* @return the prefix
* @stable ICU 2.0
*/
public String getNegativePrefix() {
return negativePrefix;
}
/**
* Sets the negative prefix.
*
*
Examples: -123, ($123) (with negative suffix), sFr-123
* @param newValue the prefix
* @stable ICU 2.0
*/
public void setNegativePrefix(String newValue) {
negativePrefix = newValue;
negPrefixPattern = null;
}
/**
* Returns the positive suffix.
*
*
Example: 123%
*
* @return the suffix
* @stable ICU 2.0
*/
public String getPositiveSuffix() {
return positiveSuffix;
}
/**
* Sets the positive suffix.
*
*
Example: 123%
* @param newValue the suffix
* @stable ICU 2.0
*/
public void setPositiveSuffix(String newValue) {
positiveSuffix = newValue;
posSuffixPattern = null;
}
/**
* Returns the negative suffix.
*
*
Examples: -123%, ($123) (with positive suffixes)
*
* @return the suffix
* @stable ICU 2.0
*/
public String getNegativeSuffix() {
return negativeSuffix;
}
/**
* Sets the positive suffix.
*
*
Examples: 123%
* @param newValue the suffix
* @stable ICU 2.0
*/
public void setNegativeSuffix(String newValue) {
negativeSuffix = newValue;
negSuffixPattern = null;
}
/**
* Returns the multiplier for use in percent, permill, etc. For a percentage, set the
* suffixes to have "%" and the multiplier to be 100. (For Arabic, use arabic percent
* symbol). For a permill, set the suffixes to have "\u2031" and the multiplier to be
* 1000.
*
*
Examples: with 100, 1.23 -> "123", and "123" -> 1.23
*
* @return the multiplier
* @stable ICU 2.0
*/
public int getMultiplier() {
return multiplier;
}
/**
* Sets the multiplier for use in percent, permill, etc. For a percentage, set the
* suffixes to have "%" and the multiplier to be 100. (For Arabic, use arabic percent
* symbol). For a permill, set the suffixes to have "\u2031" and the multiplier to be
* 1000.
*
*
Examples: with 100, 1.23 -> "123", and "123" -> 1.23
*
* @param newValue the multiplier
* @stable ICU 2.0
*/
public void setMultiplier(int newValue) {
if (newValue == 0) {
throw new IllegalArgumentException("Bad multiplier: " + newValue);
}
multiplier = newValue;
}
/**
* {@icu} Returns the rounding increment.
*
* @return A positive rounding increment, or null
if a custom rounding
* increment is not in effect.
* @see #setRoundingIncrement
* @see #getRoundingMode
* @see #setRoundingMode
* @stable ICU 2.0
*/
public java.math.BigDecimal getRoundingIncrement() {
if (roundingIncrementICU == null)
return null;
return roundingIncrementICU.toBigDecimal();
}
/**
* {@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 null
or
* BigDecimal(0.0)
to use the default rounding increment.
* @throws IllegalArgumentException if newValue
is < 0.0
* @see #getRoundingIncrement
* @see #getRoundingMode
* @see #setRoundingMode
* @stable ICU 2.0
*/
public void setRoundingIncrement(java.math.BigDecimal newValue) {
if (newValue == null) {
setRoundingIncrement((BigDecimal) null);
} else {
setRoundingIncrement(new BigDecimal(newValue));
}
}
/**
* {@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 null
or
* BigDecimal(0.0)
to use the default rounding increment.
* @throws IllegalArgumentException if newValue
is < 0.0
* @see #getRoundingIncrement
* @see #getRoundingMode
* @see #setRoundingMode
* @stable ICU 3.6
*/
public void setRoundingIncrement(BigDecimal newValue) {
int i = newValue == null ? 0 : newValue.compareTo(BigDecimal.ZERO);
if (i < 0) {
throw new IllegalArgumentException("Illegal rounding increment");
}
if (i == 0) {
setInternalRoundingIncrement(null);
} else {
setInternalRoundingIncrement(newValue);
}
resetActualRounding();
}
/**
* {@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 use the default
* rounding increment.
* @throws IllegalArgumentException if newValue
is < 0.0
* @see #getRoundingIncrement
* @see #getRoundingMode
* @see #setRoundingMode
* @stable ICU 2.0
*/
public void setRoundingIncrement(double newValue) {
if (newValue < 0.0) {
throw new IllegalArgumentException("Illegal rounding increment");
}
if (newValue == 0.0d) {
setInternalRoundingIncrement((BigDecimal) null);
} else {
// Should use BigDecimal#valueOf(double) instead of constructor
// to avoid the double precision problem.
setInternalRoundingIncrement(BigDecimal.valueOf(newValue));
}
resetActualRounding();
}
/**
* Returns the rounding mode.
*
* @return A rounding mode, between BigDecimal.ROUND_UP
and
* BigDecimal.ROUND_UNNECESSARY
.
* @see #setRoundingIncrement
* @see #getRoundingIncrement
* @see #setRoundingMode
* @see java.math.BigDecimal
* @stable ICU 2.0
*/
@Override
public int getRoundingMode() {
return roundingMode;
}
/**
* Sets the rounding mode. This has no effect unless the rounding increment is greater
* than zero.
*
* @param roundingMode A rounding mode, between BigDecimal.ROUND_UP
and
* BigDecimal.ROUND_UNNECESSARY
.
* @exception IllegalArgumentException if roundingMode
is unrecognized.
* @see #setRoundingIncrement
* @see #getRoundingIncrement
* @see #getRoundingMode
* @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;
resetActualRounding();
}
/**
* Returns the width to which the output of format()
is padded. The width is
* counted in 16-bit code units.
*
* @return the format width, or zero if no padding is in effect
* @see #setFormatWidth
* @see #getPadCharacter
* @see #setPadCharacter
* @see #getPadPosition
* @see #setPadPosition
* @stable ICU 2.0
*/
public int getFormatWidth() {
return formatWidth;
}
/**
* Sets the width to which the output of format()
is
* padded. The width is counted in 16-bit code units. This method
* also controls whether padding is enabled.
*
* @param width the width to which to pad the result of
* format()
, or zero to disable padding
* @exception IllegalArgumentException if width
is < 0
* @see #getFormatWidth
* @see #getPadCharacter
* @see #setPadCharacter
* @see #getPadPosition
* @see #setPadPosition
* @stable ICU 2.0
*/
public void setFormatWidth(int width) {
if (width < 0) {
throw new IllegalArgumentException("Illegal format width");
}
formatWidth = width;
}
/**
* {@icu} Returns the character used to pad to the format width. The default is ' '.
*
* @return the pad character
* @see #setFormatWidth
* @see #getFormatWidth
* @see #setPadCharacter
* @see #getPadPosition
* @see #setPadPosition
* @stable ICU 2.0
*/
public char getPadCharacter() {
return pad;
}
/**
* {@icu} Sets the character used to pad to the format width. If padding is not
* enabled, then this will take effect if padding is later enabled.
*
* @param padChar the pad character
* @see #setFormatWidth
* @see #getFormatWidth
* @see #getPadCharacter
* @see #getPadPosition
* @see #setPadPosition
* @stable ICU 2.0
*/
public void setPadCharacter(char padChar) {
pad = padChar;
}
/**
* {@icu} Returns the position at which padding will take place. This is the location at
* which padding will be inserted if the result of format()
is shorter
* than the format width.
*
* @return the pad position, one of PAD_BEFORE_PREFIX
,
* PAD_AFTER_PREFIX
, PAD_BEFORE_SUFFIX
, or
* PAD_AFTER_SUFFIX
.
* @see #setFormatWidth
* @see #getFormatWidth
* @see #setPadCharacter
* @see #getPadCharacter
* @see #setPadPosition
* @see #PAD_BEFORE_PREFIX
* @see #PAD_AFTER_PREFIX
* @see #PAD_BEFORE_SUFFIX
* @see #PAD_AFTER_SUFFIX
* @stable ICU 2.0
*/
public int getPadPosition() {
return padPosition;
}
/**
* {@icu} Sets the position at which padding will take place. This is the location at
* which padding will be inserted if the result of format()
is shorter
* than the format width. This has no effect unless padding is enabled.
*
* @param padPos the pad position, one of PAD_BEFORE_PREFIX
,
* PAD_AFTER_PREFIX
, PAD_BEFORE_SUFFIX
, or
* PAD_AFTER_SUFFIX
.
* @exception IllegalArgumentException if the pad position in unrecognized
* @see #setFormatWidth
* @see #getFormatWidth
* @see #setPadCharacter
* @see #getPadCharacter
* @see #getPadPosition
* @see #PAD_BEFORE_PREFIX
* @see #PAD_AFTER_PREFIX
* @see #PAD_BEFORE_SUFFIX
* @see #PAD_AFTER_SUFFIX
* @stable ICU 2.0
*/
public void setPadPosition(int padPos) {
if (padPos < PAD_BEFORE_PREFIX || padPos > PAD_AFTER_SUFFIX) {
throw new IllegalArgumentException("Illegal pad position");
}
padPosition = padPos;
}
/**
* {@icu} Returns whether or not scientific notation is used.
*
* @return true if this object formats and parses scientific notation
* @see #setScientificNotation
* @see #getMinimumExponentDigits
* @see #setMinimumExponentDigits
* @see #isExponentSignAlwaysShown
* @see #setExponentSignAlwaysShown
* @stable ICU 2.0
*/
public boolean isScientificNotation() {
return useExponentialNotation;
}
/**
* {@icu} Sets whether or not scientific notation is used. When scientific notation is
* used, the effective maximum number of integer digits is <= 8. If the maximum number
* of integer digits is set to more than 8, the effective maximum will be 1. This
* allows this call to generate a 'default' scientific number format without
* additional changes.
*
* @param useScientific true if this object formats and parses scientific notation
* @see #isScientificNotation
* @see #getMinimumExponentDigits
* @see #setMinimumExponentDigits
* @see #isExponentSignAlwaysShown
* @see #setExponentSignAlwaysShown
* @stable ICU 2.0
*/
public void setScientificNotation(boolean useScientific) {
useExponentialNotation = useScientific;
}
/**
* {@icu} Returns the minimum exponent digits that will be shown.
*
* @return the minimum exponent digits that will be shown
* @see #setScientificNotation
* @see #isScientificNotation
* @see #setMinimumExponentDigits
* @see #isExponentSignAlwaysShown
* @see #setExponentSignAlwaysShown
* @stable ICU 2.0
*/
public byte getMinimumExponentDigits() {
return minExponentDigits;
}
/**
* {@icu} Sets the minimum exponent digits that will be shown. This has no effect
* unless scientific notation is in use.
*
* @param minExpDig a value >= 1 indicating the fewest exponent
* digits that will be shown
* @exception IllegalArgumentException if minExpDig
< 1
* @see #setScientificNotation
* @see #isScientificNotation
* @see #getMinimumExponentDigits
* @see #isExponentSignAlwaysShown
* @see #setExponentSignAlwaysShown
* @stable ICU 2.0
*/
public void setMinimumExponentDigits(byte minExpDig) {
if (minExpDig < 1) {
throw new IllegalArgumentException("Exponent digits must be >= 1");
}
minExponentDigits = minExpDig;
}
/**
* {@icu} Returns whether the exponent sign is always shown.
*
* @return true if the exponent is always prefixed with either the localized minus
* sign or the localized plus sign, false if only negative exponents are prefixed with
* the localized minus sign.
* @see #setScientificNotation
* @see #isScientificNotation
* @see #setMinimumExponentDigits
* @see #getMinimumExponentDigits
* @see #setExponentSignAlwaysShown
* @stable ICU 2.0
*/
public boolean isExponentSignAlwaysShown() {
return exponentSignAlwaysShown;
}
/**
* {@icu} Sets whether the exponent sign is always shown. This has no effect unless
* scientific notation is in use.
*
* @param expSignAlways true if the exponent is always prefixed with either the
* localized minus sign or the localized plus sign, false if only negative exponents
* are prefixed with the localized minus sign.
* @see #setScientificNotation
* @see #isScientificNotation
* @see #setMinimumExponentDigits
* @see #getMinimumExponentDigits
* @see #isExponentSignAlwaysShown
* @stable ICU 2.0
*/
public void setExponentSignAlwaysShown(boolean expSignAlways) {
exponentSignAlwaysShown = expSignAlways;
}
/**
* Returns the grouping size. Grouping size is the number of digits between grouping
* separators in the integer portion of a number. For example, in the number
* "123,456.78", the grouping size is 3.
*
* @see #setGroupingSize
* @see NumberFormat#isGroupingUsed
* @see DecimalFormatSymbols#getGroupingSeparator
* @stable ICU 2.0
*/
public int getGroupingSize() {
return groupingSize;
}
/**
* Sets the grouping size. Grouping size is the number of digits between grouping
* separators in the integer portion of a number. For example, in the number
* "123,456.78", the grouping size is 3.
*
* @see #getGroupingSize
* @see NumberFormat#setGroupingUsed
* @see DecimalFormatSymbols#setGroupingSeparator
* @stable ICU 2.0
*/
public void setGroupingSize(int newValue) {
groupingSize = (byte) newValue;
}
/**
* {@icu} Returns the secondary grouping size. In some locales one grouping interval
* is used for the least significant integer digits (the primary grouping size), and
* another is used for all others (the secondary grouping size). A formatter
* supporting a secondary grouping size will return a positive integer unequal to the
* primary grouping size returned by getGroupingSize()
. For example, if
* the primary grouping size is 4, and the secondary grouping size is 2, then the
* number 123456789 formats as "1,23,45,6789", and the pattern appears as "#,##,###0".
*
* @return the secondary grouping size, or a value less than one if there is none
* @see #setSecondaryGroupingSize
* @see NumberFormat#isGroupingUsed
* @see DecimalFormatSymbols#getGroupingSeparator
* @stable ICU 2.0
*/
public int getSecondaryGroupingSize() {
return groupingSize2;
}
/**
* {@icu} Sets the secondary grouping size. If set to a value less than 1, then
* secondary grouping is turned off, and the primary grouping size is used for all
* intervals, not just the least significant.
*
* @see #getSecondaryGroupingSize
* @see NumberFormat#setGroupingUsed
* @see DecimalFormatSymbols#setGroupingSeparator
* @stable ICU 2.0
*/
public void setSecondaryGroupingSize(int newValue) {
groupingSize2 = (byte) newValue;
}
/**
* {@icu} Returns the MathContext used by this format.
*
* @return desired MathContext
* @see #getMathContext
* @stable ICU 4.2
*/
public MathContext getMathContextICU() {
return mathContext;
}
/**
* {@icu} Returns the MathContext used by this format.
*
* @return desired MathContext
* @see #getMathContext
* @stable ICU 4.2
*/
public java.math.MathContext getMathContext() {
try {
// don't allow multiple references
return mathContext == null ? null : new java.math.MathContext(mathContext.getDigits(),
java.math.RoundingMode.valueOf(mathContext.getRoundingMode()));
} catch (Exception foo) {
return null; // should never happen
}
}
/**
* {@icu} Sets the MathContext used by this format.
*
* @param newValue desired MathContext
* @see #getMathContext
* @stable ICU 4.2
*/
public void setMathContextICU(MathContext newValue) {
mathContext = newValue;
}
/**
* {@icu} Sets the MathContext used by this format.
*
* @param newValue desired MathContext
* @see #getMathContext
* @stable ICU 4.2
*/
public void setMathContext(java.math.MathContext newValue) {
mathContext = new MathContext(newValue.getPrecision(), MathContext.SCIENTIFIC, false,
(newValue.getRoundingMode()).ordinal());
}
/**
* Returns the behavior of the decimal separator with integers. (The decimal
* separator will always appear with decimals.)
Example: Decimal ON: 12345 ->
* 12345.; OFF: 12345 -> 12345
*
* @stable ICU 2.0
*/
public boolean isDecimalSeparatorAlwaysShown() {
return decimalSeparatorAlwaysShown;
}
/**
* Sets the behavior of the decimal separator with integers. (The decimal separator
* will always appear with decimals.)
*
*
This only affects formatting, and only where there might be no digits after the
* decimal point, e.g., if true, 3456.00 -> "3,456." if false, 3456.00 -> "3456" This
* is independent of parsing. If you want parsing to stop at the decimal point, use
* setParseIntegerOnly.
*
*
* Example: Decimal ON: 12345 -> 12345.; OFF: 12345 -> 12345
*
* @stable ICU 2.0
*/
public void setDecimalSeparatorAlwaysShown(boolean newValue) {
decimalSeparatorAlwaysShown = newValue;
}
/**
* {@icu} Returns a copy of the CurrencyPluralInfo used by this format. It might
* return null if the decimal format is not a plural type currency decimal
* format. Plural type currency decimal format means either the pattern in the decimal
* format contains 3 currency signs, or the decimal format is initialized with
* PLURALCURRENCYSTYLE.
*
* @return desired CurrencyPluralInfo
* @see CurrencyPluralInfo
* @stable ICU 4.2
*/
public CurrencyPluralInfo getCurrencyPluralInfo() {
try {
// don't allow multiple references
return currencyPluralInfo == null ? null :
(CurrencyPluralInfo) currencyPluralInfo.clone();
} catch (Exception foo) {
return null; // should never happen
}
}
/**
* {@icu} Sets the CurrencyPluralInfo used by this format. The format uses a copy of
* the provided information.
*
* @param newInfo desired CurrencyPluralInfo
* @see CurrencyPluralInfo
* @stable ICU 4.2
*/
public void setCurrencyPluralInfo(CurrencyPluralInfo newInfo) {
currencyPluralInfo = (CurrencyPluralInfo) newInfo.clone();
isReadyForParsing = false;
}
/**
* Overrides clone.
* @stable ICU 2.0
*/
@Override
public Object clone() {
try {
DecimalFormat other = (DecimalFormat) super.clone();
other.symbols = (DecimalFormatSymbols) symbols.clone();
other.digitList = new DigitList(); // fix for JB#5358
if (currencyPluralInfo != null) {
other.currencyPluralInfo = (CurrencyPluralInfo) currencyPluralInfo.clone();
}
other.attributes = new ArrayList(); // #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
// instance, but parse/subparse implementation is not.
return other;
} catch (Exception e) {
throw new IllegalStateException();
}
}
/**
* Overrides equals.
* @stable ICU 2.0
*/
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (!super.equals(obj))
return false; // super does class check
DecimalFormat other = (DecimalFormat) obj;
// Add the comparison of the four new added fields ,they are posPrefixPattern,
// posSuffixPattern, negPrefixPattern, negSuffixPattern. [Richard/GCL]
// following are added to accomodate changes for currency plural format.
return currencySignCount == other.currencySignCount
&& (style != NumberFormat.PLURALCURRENCYSTYLE ||
equals(posPrefixPattern, other.posPrefixPattern)
&& equals(posSuffixPattern, other.posSuffixPattern)
&& equals(negPrefixPattern, other.negPrefixPattern)
&& equals(negSuffixPattern, other.negSuffixPattern))
&& multiplier == other.multiplier
&& groupingSize == other.groupingSize
&& groupingSize2 == other.groupingSize2
&& decimalSeparatorAlwaysShown == other.decimalSeparatorAlwaysShown
&& useExponentialNotation == other.useExponentialNotation
&& (!useExponentialNotation || minExponentDigits == other.minExponentDigits)
&& useSignificantDigits == other.useSignificantDigits
&& (!useSignificantDigits || minSignificantDigits == other.minSignificantDigits
&& maxSignificantDigits == other.maxSignificantDigits)
&& symbols.equals(other.symbols)
&& Utility.objectEquals(currencyPluralInfo, other.currencyPluralInfo);
}
// method to unquote the strings and compare
private boolean equals(String pat1, String pat2) {
if (pat1 == null || pat2 == null) {
return (pat1 == null && pat2 == null);
}
// fast path
if (pat1.equals(pat2)) {
return true;
}
return unquote(pat1).equals(unquote(pat2));
}
private String unquote(String pat) {
StringBuilder buf = new StringBuilder(pat.length());
int i = 0;
while (i < pat.length()) {
char ch = pat.charAt(i++);
if (ch != QUOTE) {
buf.append(ch);
}
}
return buf.toString();
}
// protected void handleToString(StringBuffer buf) {
// buf.append("\nposPrefixPattern: '" + posPrefixPattern + "'\n");
// buf.append("positivePrefix: '" + positivePrefix + "'\n");
// buf.append("posSuffixPattern: '" + posSuffixPattern + "'\n");
// buf.append("positiveSuffix: '" + positiveSuffix + "'\n");
// buf.append("negPrefixPattern: '" +
// com.ibm.icu.impl.Utility.format1ForSource(negPrefixPattern) + "'\n");
// buf.append("negativePrefix: '" +
// com.ibm.icu.impl.Utility.format1ForSource(negativePrefix) + "'\n");
// buf.append("negSuffixPattern: '" + negSuffixPattern + "'\n");
// buf.append("negativeSuffix: '" + negativeSuffix + "'\n");
// buf.append("multiplier: '" + multiplier + "'\n");
// buf.append("groupingSize: '" + groupingSize + "'\n");
// buf.append("groupingSize2: '" + groupingSize2 + "'\n");
// buf.append("decimalSeparatorAlwaysShown: '" + decimalSeparatorAlwaysShown + "'\n");
// buf.append("useExponentialNotation: '" + useExponentialNotation + "'\n");
// buf.append("minExponentDigits: '" + minExponentDigits + "'\n");
// buf.append("useSignificantDigits: '" + useSignificantDigits + "'\n");
// buf.append("minSignificantDigits: '" + minSignificantDigits + "'\n");
// buf.append("maxSignificantDigits: '" + maxSignificantDigits + "'\n");
// buf.append("symbols: '" + symbols + "'");
// }
/**
* Overrides hashCode.
* @stable ICU 2.0
*/
@Override
public int hashCode() {
return super.hashCode() * 37 + positivePrefix.hashCode();
// just enough fields for a reasonable distribution
}
/**
* Synthesizes a pattern string that represents the current state of this Format
* object.
*
* @see #applyPattern
* @stable ICU 2.0
*/
public String toPattern() {
if (style == NumberFormat.PLURALCURRENCYSTYLE) {
// the prefix or suffix pattern might not be defined yet, so they can not be
// synthesized, instead, get them directly. but it might not be the actual
// pattern used in formatting. the actual pattern used in formatting depends
// on the formatted number's plural count.
return formatPattern;
}
return toPattern(false);
}
/**
* Synthesizes a localized pattern string that represents the current state of this
* Format object.
*
* @see #applyPattern
* @stable ICU 2.0
*/
public String toLocalizedPattern() {
if (style == NumberFormat.PLURALCURRENCYSTYLE) {
return formatPattern;
}
return toPattern(true);
}
/**
* Expands the affix pattern strings into the expanded affix strings. If any affix
* pattern string is null, do not expand it. This method should be called any time the
* symbols or the affix patterns change in order to keep the expanded affix strings up
* to date. This method also will be called before formatting if format currency
* plural names, since the plural name is not a static one, it is based on the
* currency plural count, the affix will be known only after the currency plural count
* is know. In which case, the parameter 'pluralCount' will be a non-null currency
* plural count. In all other cases, the 'pluralCount' is null, which means it is not
* needed.
*/
// Bug 4212072 [Richard/GCL]
private void expandAffixes(String pluralCount) {
// expandAffix() will set currencyChoice to a non-null value if
// appropriate AND if it is null.
currencyChoice = null;
// Reuse one StringBuffer for better performance
StringBuffer buffer = new StringBuffer();
if (posPrefixPattern != null) {
expandAffix(posPrefixPattern, pluralCount, buffer, false);
positivePrefix = buffer.toString();
}
if (posSuffixPattern != null) {
expandAffix(posSuffixPattern, pluralCount, buffer, false);
positiveSuffix = buffer.toString();
}
if (negPrefixPattern != null) {
expandAffix(negPrefixPattern, pluralCount, buffer, false);
negativePrefix = buffer.toString();
}
if (negSuffixPattern != null) {
expandAffix(negSuffixPattern, pluralCount, buffer, false);
negativeSuffix = buffer.toString();
}
}
/**
* Expands an affix pattern into an affix string. All characters in the pattern are
* literal unless bracketed by QUOTEs. The following characters outside QUOTE are
* recognized: PATTERN_PERCENT, PATTERN_PER_MILLE, PATTERN_MINUS, and
* CURRENCY_SIGN. If CURRENCY_SIGN is doubled, it is interpreted as an international
* currency sign. If CURRENCY_SIGN is tripled, it is interpreted as currency plural
* long names, such as "US Dollars". Any other character outside QUOTE represents
* itself. Quoted text must be well-formed.
*
* This method is used in two distinct ways. First, it is used to expand the stored
* affix patterns into actual affixes. For this usage, doFormat must be false. Second,
* it is used to expand the stored affix patterns given a specific number (doFormat ==
* true), for those rare cases in which a currency format references a ChoiceFormat
* (e.g., en_IN display name for INR). The number itself is taken from digitList.
*
* When used in the first way, this method has a side effect: It sets currencyChoice
* to a ChoiceFormat object, if the currency's display name in this locale is a
* ChoiceFormat pattern (very rare). It only does this if currencyChoice is null to
* start with.
*
* @param pattern the non-null, possibly empty pattern
* @param pluralCount the plural count. It is only used for currency plural format. In
* which case, it is the plural count of the currency amount. For example, in en_US,
* it is the singular "one", or the plural "other". For all other cases, it is null,
* and is not being used.
* @param buffer a scratch StringBuffer; its contents will be lost
* @param doFormat if false, then the pattern will be expanded, and if a currency
* symbol is encountered that expands to a ChoiceFormat, the currencyChoice member
* variable will be initialized if it is null. If doFormat is true, then it is assumed
* that the currencyChoice has been created, and it will be used to format the value
* in digitList.
*/
// Bug 4212072 [Richard/GCL]
private void expandAffix(String pattern, String pluralCount, StringBuffer buffer,
boolean doFormat) {
buffer.setLength(0);
for (int i = 0; i < pattern.length();) {
char c = pattern.charAt(i++);
if (c == QUOTE) {
for (;;) {
int j = pattern.indexOf(QUOTE, i);
if (j == i) {
buffer.append(QUOTE);
i = j + 1;
break;
} else if (j > i) {
buffer.append(pattern.substring(i, j));
i = j + 1;
if (i < pattern.length() && pattern.charAt(i) == QUOTE) {
buffer.append(QUOTE);
++i;
// loop again
} else {
break;
}
} else {
// Unterminated quote; should be caught by apply
// pattern.
throw new RuntimeException();
}
}
continue;
}
switch (c) {
case CURRENCY_SIGN:
// As of ICU 2.2 we use the currency object, and ignore the currency
// symbols in the DFS, unless we have a null currency object. This occurs
// if resurrecting a pre-2.2 object or if the user sets a custom DFS.
boolean intl = i < pattern.length() && pattern.charAt(i) == CURRENCY_SIGN;
boolean plural = false;
if (intl) {
++i;
if (i < pattern.length() && pattern.charAt(i) == CURRENCY_SIGN) {
plural = true;
intl = false;
++i;
}
}
String s = null;
Currency currency = getCurrency();
if (currency != null) {
// plural name is only needed when pluralCount != null, which means
// when formatting currency plural names. For other cases,
// pluralCount == null, and plural names are not needed.
if (plural && pluralCount != null) {
boolean isChoiceFormat[] = new boolean[1];
s = currency.getName(symbols.getULocale(), Currency.PLURAL_LONG_NAME,
pluralCount, isChoiceFormat);
} else if (!intl) {
boolean isChoiceFormat[] = new boolean[1];
s = currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME,
isChoiceFormat);
if (isChoiceFormat[0]) {
// Two modes here: If doFormat is false, we set up
// currencyChoice. If doFormat is true, we use the previously
// created currencyChoice to format the value in digitList.
if (!doFormat) {
// If the currency is handled by a ChoiceFormat, then
// we're not going to use the expanded
// patterns. Instantiate the ChoiceFormat and return.
if (currencyChoice == null) {
currencyChoice = new ChoiceFormat(s);
}
// We could almost return null or "" here, since the
// expanded affixes are almost not used at all in this
// situation. However, one method -- toPattern() -- still
// does use the expanded affixes, in order to set up a
// padding pattern. We use the CURRENCY_SIGN as a
// placeholder.
s = String.valueOf(CURRENCY_SIGN);
} else {
FieldPosition pos = new FieldPosition(0); // ignored
currencyChoice.format(digitList.getDouble(), buffer, pos);
continue;
}
}
} else {
s = currency.getCurrencyCode();
}
} else {
s = intl ? symbols.getInternationalCurrencySymbol() :
symbols.getCurrencySymbol();
}
buffer.append(s);
continue;
case PATTERN_PERCENT:
c = symbols.getPercent();
break;
case PATTERN_PER_MILLE:
c = symbols.getPerMill();
break;
case PATTERN_MINUS:
String minusString = symbols.getMinusString();
buffer.append(minusString);
continue;
}
buffer.append(c);
}
}
/**
* Append an affix to the given StringBuffer.
*
* @param buf
* buffer to append to
* @param isNegative
* @param isPrefix
*/
private int appendAffix(StringBuffer buf, boolean isNegative, boolean isPrefix,
boolean parseAttr) {
if (currencyChoice != null) {
String affixPat = null;
if (isPrefix) {
affixPat = isNegative ? negPrefixPattern : posPrefixPattern;
} else {
affixPat = isNegative ? negSuffixPattern : posSuffixPattern;
}
StringBuffer affixBuf = new StringBuffer();
expandAffix(affixPat, null, affixBuf, true);
buf.append(affixBuf);
return affixBuf.length();
}
String affix = null;
if (isPrefix) {
affix = isNegative ? negativePrefix : positivePrefix;
} else {
affix = isNegative ? negativeSuffix : positiveSuffix;
}
// [Spark/CDL] Invoke formatAffix2Attribute to add attributes for affix
if (parseAttr) {
int offset = affix.indexOf(symbols.getCurrencySymbol());
if (-1 == offset) {
offset = affix.indexOf(symbols.getPercent());
if (-1 == offset) {
offset = 0;
}
}
formatAffix2Attribute(affix, buf.length() + offset, buf.length() + affix.length());
}
buf.append(affix);
return affix.length();
}
/**
* [Spark/CDL] This is a newly added method, used to add attributes for prefix and
* suffix.
*/
private void formatAffix2Attribute(String affix, int begin, int end) {
// [Spark/CDL] It is the invoker's responsibility to ensure that, before the
// invocation of this method, attributes is not null. if( attributes == null )
// return;
if (affix.indexOf(symbols.getCurrencySymbol()) > -1) {
addAttribute(Field.CURRENCY, begin, end);
} else if (affix.indexOf(symbols.getMinusSign()) > -1) {
addAttribute(Field.SIGN, begin, end);
} else if (affix.indexOf(symbols.getPercent()) > -1) {
addAttribute(Field.PERCENT, begin, end);
} else if (affix.indexOf(symbols.getPerMill()) > -1) {
addAttribute(Field.PERMILLE, begin, end);
}
}
/**
* [Spark/CDL] Use this method to add attribute.
*/
private void addAttribute(Field field, int begin, int end) {
FieldPosition pos = new FieldPosition(field);
pos.setBeginIndex(begin);
pos.setEndIndex(end);
attributes.add(pos);
}
/**
* Formats the object to an attributed string, and return the corresponding iterator.
*
* @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 = new StringBuffer();
unit.writePrefix(text);
attributes.clear();
if (obj instanceof BigInteger) {
format((BigInteger) number, text, new FieldPosition(0), true);
} else if (obj instanceof java.math.BigDecimal) {
format((java.math.BigDecimal) number, text, new FieldPosition(0)
, true);
} else if (obj instanceof Double) {
format(number.doubleValue(), text, new FieldPosition(0), true);
} else if (obj instanceof Integer || obj instanceof Long) {
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
for (int i = 0; i < attributes.size(); i++) {
FieldPosition pos = attributes.get(i);
Format.Field attribute = pos.getFieldAttribute();
as.addAttribute(attribute, attribute, pos.getBeginIndex(), pos.getEndIndex());
}
// return the CharacterIterator from AttributedString
return as.getIterator();
}
/**
* Appends an affix pattern to the given StringBuffer. Localize unquoted specials.
*/
private void appendAffixPattern(StringBuffer buffer, boolean isNegative, boolean isPrefix,
boolean localized) {
String affixPat = null;
if (isPrefix) {
affixPat = isNegative ? negPrefixPattern : posPrefixPattern;
} else {
affixPat = isNegative ? negSuffixPattern : posSuffixPattern;
}
// When there is a null affix pattern, we use the affix itself.
if (affixPat == null) {
String affix = null;
if (isPrefix) {
affix = isNegative ? negativePrefix : positivePrefix;
} else {
affix = isNegative ? negativeSuffix : positiveSuffix;
}
// Do this crudely for now: Wrap everything in quotes.
buffer.append(QUOTE);
for (int i = 0; i < affix.length(); ++i) {
char ch = affix.charAt(i);
if (ch == QUOTE) {
buffer.append(ch);
}
buffer.append(ch);
}
buffer.append(QUOTE);
return;
}
if (!localized) {
buffer.append(affixPat);
} else {
int i, j;
for (i = 0; i < affixPat.length(); ++i) {
char ch = affixPat.charAt(i);
switch (ch) {
case QUOTE:
j = affixPat.indexOf(QUOTE, i + 1);
if (j < 0) {
throw new IllegalArgumentException("Malformed affix pattern: " + affixPat);
}
buffer.append(affixPat.substring(i, j + 1));
i = j;
continue;
case PATTERN_PER_MILLE:
ch = symbols.getPerMill();
break;
case PATTERN_PERCENT:
ch = symbols.getPercent();
break;
case PATTERN_MINUS:
ch = symbols.getMinusSign();
break;
}
// check if char is same as any other symbol
if (ch == symbols.getDecimalSeparator() || ch == symbols.getGroupingSeparator()) {
buffer.append(QUOTE);
buffer.append(ch);
buffer.append(QUOTE);
} else {
buffer.append(ch);
}
}
}
}
/**
* Does the real work of generating a pattern.
*/
private String toPattern(boolean localized) {
StringBuffer result = new StringBuffer();
char zero = localized ? symbols.getZeroDigit() : PATTERN_ZERO_DIGIT;
char digit = localized ? symbols.getDigit() : PATTERN_DIGIT;
char sigDigit = 0;
boolean useSigDig = areSignificantDigitsUsed();
if (useSigDig) {
sigDigit = localized ? symbols.getSignificantDigit() : PATTERN_SIGNIFICANT_DIGIT;
}
char group = localized ? symbols.getGroupingSeparator() : PATTERN_GROUPING_SEPARATOR;
int i;
int roundingDecimalPos = 0; // Pos of decimal in roundingDigits
String roundingDigits = null;
int padPos = (formatWidth > 0) ? padPosition : -1;
String padSpec = (formatWidth > 0)
? new StringBuffer(2).append(localized
? symbols.getPadEscape()
: PATTERN_PAD_ESCAPE).append(pad).toString()
: null;
if (roundingIncrementICU != null) {
i = roundingIncrementICU.scale();
roundingDigits = roundingIncrementICU.movePointRight(i).toString();
roundingDecimalPos = roundingDigits.length() - i;
}
for (int part = 0; part < 2; ++part) {
// variable not used int partStart = result.length();
if (padPos == PAD_BEFORE_PREFIX) {
result.append(padSpec);
}
// Use original symbols read from resources in pattern eg. use "\u00A4"
// instead of "$" in Locale.US [Richard/GCL]
appendAffixPattern(result, part != 0, true, localized);
if (padPos == PAD_AFTER_PREFIX) {
result.append(padSpec);
}
int sub0Start = result.length();
int g = isGroupingUsed() ? Math.max(0, groupingSize) : 0;
if (g > 0 && groupingSize2 > 0 && groupingSize2 != groupingSize) {
g += groupingSize2;
}
int maxDig = 0, minDig = 0, maxSigDig = 0;
if (useSigDig) {
minDig = getMinimumSignificantDigits();
maxDig = maxSigDig = getMaximumSignificantDigits();
} else {
minDig = getMinimumIntegerDigits();
maxDig = getMaximumIntegerDigits();
}
if (useExponentialNotation) {
if (maxDig > MAX_SCIENTIFIC_INTEGER_DIGITS) {
maxDig = 1;
}
} else if (useSigDig) {
maxDig = Math.max(maxDig, g + 1);
} else {
maxDig = Math.max(Math.max(g, getMinimumIntegerDigits()), roundingDecimalPos) + 1;
}
for (i = maxDig; i > 0; --i) {
if (!useExponentialNotation && i < maxDig && isGroupingPosition(i)) {
result.append(group);
}
if (useSigDig) {
// #@,@### (maxSigDig == 5, minSigDig == 2) 65 4321 (1-based pos,
// count from the right) Use # if pos > maxSigDig or 1 <= pos <=
// (maxSigDig - minSigDig) Use @ if (maxSigDig - minSigDig) < pos <=
// maxSigDig
result.append((maxSigDig >= i && i > (maxSigDig - minDig)) ? sigDigit : digit);
} else {
if (roundingDigits != null) {
int pos = roundingDecimalPos - i;
if (pos >= 0 && pos < roundingDigits.length()) {
result.append((char) (roundingDigits.charAt(pos) - '0' + zero));
continue;
}
}
result.append(i <= minDig ? zero : digit);
}
}
if (!useSigDig) {
if (getMaximumFractionDigits() > 0 || decimalSeparatorAlwaysShown) {
result.append(localized ? symbols.getDecimalSeparator() :
PATTERN_DECIMAL_SEPARATOR);
}
int pos = roundingDecimalPos;
for (i = 0; i < getMaximumFractionDigits(); ++i) {
if (roundingDigits != null && pos < roundingDigits.length()) {
result.append(pos < 0 ? zero :
(char) (roundingDigits.charAt(pos) - '0' + zero));
++pos;
continue;
}
result.append(i < getMinimumFractionDigits() ? zero : digit);
}
}
if (useExponentialNotation) {
if (localized) {
result.append(symbols.getExponentSeparator());
} else {
result.append(PATTERN_EXPONENT);
}
if (exponentSignAlwaysShown) {
result.append(localized ? symbols.getPlusSign() : PATTERN_PLUS_SIGN);
}
for (i = 0; i < minExponentDigits; ++i) {
result.append(zero);
}
}
if (padSpec != null && !useExponentialNotation) {
int add = formatWidth
- result.length()
+ sub0Start
- ((part == 0)
? positivePrefix.length() + positiveSuffix.length()
: negativePrefix.length() + negativeSuffix.length());
while (add > 0) {
result.insert(sub0Start, digit);
++maxDig;
--add;
// Only add a grouping separator if we have at least 2 additional
// characters to be added, so we don't end up with ",###".
if (add > 1 && isGroupingPosition(maxDig)) {
result.insert(sub0Start, group);
--add;
}
}
}
if (padPos == PAD_BEFORE_SUFFIX) {
result.append(padSpec);
}
// Use original symbols read from resources in pattern eg. use "\u00A4"
// instead of "$" in Locale.US [Richard/GCL]
appendAffixPattern(result, part != 0, false, localized);
if (padPos == PAD_AFTER_SUFFIX) {
result.append(padSpec);
}
if (part == 0) {
if (negativeSuffix.equals(positiveSuffix) &&
negativePrefix.equals(PATTERN_MINUS + positivePrefix)) {
break;
} else {
result.append(localized ? symbols.getPatternSeparator() : PATTERN_SEPARATOR);
}
}
}
return result.toString();
}
/**
* Applies the given pattern to this Format object. A pattern is a short-hand
* specification for the various formatting properties. These properties can also be
* changed individually through the various setter methods.
*
* There is no limit to integer digits are set by this routine, since that is the
* typical end-user desire; use setMaximumInteger if you want to set a real value. For
* negative numbers, use a second pattern, separated by a semicolon
*
*
Example "#,#00.0#" -> 1,234.56
*
*
This means a minimum of 2 integer digits, 1 fraction digit, and a maximum of 2
* fraction digits.
*
*
Example: "#,#00.0#;(#,#00.0#)" for negatives in parentheses.
*
*
In negative patterns, the minimum and maximum counts are ignored; these are
* presumed to be set in the positive pattern.
*
* @stable ICU 2.0
*/
public void applyPattern(String pattern) {
applyPattern(pattern, false);
}
/**
* Applies the given pattern to this Format object. The pattern is assumed to be in a
* localized notation. A pattern is a short-hand specification for the various
* formatting properties. These properties can also be changed individually through
* the various setter methods.
*
*
There is no limit to integer digits are set by this routine, since that is the
* typical end-user desire; use setMaximumInteger if you want to set a real value. For
* negative numbers, use a second pattern, separated by a semicolon
*
*
Example "#,#00.0#" -> 1,234.56
*
*
This means a minimum of 2 integer digits, 1 fraction digit, and a maximum of 2
* fraction digits.
*
*
Example: "#,#00.0#;(#,#00.0#)" for negatives in parantheses.
*
*
In negative patterns, the minimum and maximum counts are ignored; these are
* presumed to be set in the positive pattern.
*
* @stable ICU 2.0
*/
public void applyLocalizedPattern(String pattern) {
applyPattern(pattern, true);
}
/**
* Does the real work of applying a pattern.
*/
private void applyPattern(String pattern, boolean localized) {
applyPatternWithoutExpandAffix(pattern, localized);
expandAffixAdjustWidth(null);
}
private void expandAffixAdjustWidth(String pluralCount) {
// Bug 4212072 Update the affix strings according to symbols in order to keep the
// affix strings up to date. [Richard/GCL]
expandAffixes(pluralCount);
// Now that we have the actual prefix and suffix, fix up formatWidth
if (formatWidth > 0) {
formatWidth += positivePrefix.length() + positiveSuffix.length();
}
}
private void applyPatternWithoutExpandAffix(String pattern, boolean localized) {
char zeroDigit = PATTERN_ZERO_DIGIT; // '0'
char sigDigit = PATTERN_SIGNIFICANT_DIGIT; // '@'
char groupingSeparator = PATTERN_GROUPING_SEPARATOR;
char decimalSeparator = PATTERN_DECIMAL_SEPARATOR;
char percent = PATTERN_PERCENT;
char perMill = PATTERN_PER_MILLE;
char digit = PATTERN_DIGIT; // '#'
char separator = PATTERN_SEPARATOR;
String exponent = String.valueOf(PATTERN_EXPONENT);
char plus = PATTERN_PLUS_SIGN;
char padEscape = PATTERN_PAD_ESCAPE;
char minus = PATTERN_MINUS; // Bug 4212072 [Richard/GCL]
if (localized) {
zeroDigit = symbols.getZeroDigit();
sigDigit = symbols.getSignificantDigit();
groupingSeparator = symbols.getGroupingSeparator();
decimalSeparator = symbols.getDecimalSeparator();
percent = symbols.getPercent();
perMill = symbols.getPerMill();
digit = symbols.getDigit();
separator = symbols.getPatternSeparator();
exponent = symbols.getExponentSeparator();
plus = symbols.getPlusSign();
padEscape = symbols.getPadEscape();
minus = symbols.getMinusSign(); // Bug 4212072 [Richard/GCL]
}
char nineDigit = (char) (zeroDigit + 9);
boolean gotNegative = false;
int pos = 0;
// Part 0 is the positive pattern. Part 1, if present, is the negative
// pattern.
for (int part = 0; part < 2 && pos < pattern.length(); ++part) {
// The subpart ranges from 0 to 4: 0=pattern proper, 1=prefix, 2=suffix,
// 3=prefix in quote, 4=suffix in quote. Subpart 0 is between the prefix and
// suffix, and consists of pattern characters. In the prefix and suffix,
// percent, permille, and currency symbols are recognized and translated.
int subpart = 1, sub0Start = 0, sub0Limit = 0, sub2Limit = 0;
// It's important that we don't change any fields of this object
// prematurely. We set the following variables for the multiplier, grouping,
// etc., and then only change the actual object fields if everything parses
// correctly. This also lets us register the data from part 0 and ignore the
// part 1, except for the prefix and suffix.
StringBuilder prefix = new StringBuilder();
StringBuilder suffix = new StringBuilder();
int decimalPos = -1;
int multpl = 1;
int digitLeftCount = 0, zeroDigitCount = 0, digitRightCount = 0, sigDigitCount = 0;
byte groupingCount = -1;
byte groupingCount2 = -1;
int padPos = -1;
char padChar = 0;
int incrementPos = -1;
long incrementVal = 0;
byte expDigits = -1;
boolean expSignAlways = false;
int currencySignCnt = 0;
// The affix is either the prefix or the suffix.
StringBuilder affix = prefix;
int start = pos;
PARTLOOP: for (; pos < pattern.length(); ++pos) {
char ch = pattern.charAt(pos);
switch (subpart) {
case 0: // Pattern proper subpart (between prefix & suffix)
// Process the digits, decimal, and grouping characters. We record
// five pieces of information. We expect the digits to occur in the
// pattern ####00.00####, and we record the number of left digits,
// zero (central) digits, and right digits. The position of the last
// grouping character is recorded (should be somewhere within the
// first two blocks of characters), as is the position of the decimal
// point, if any (should be in the zero digits). If there is no
// decimal point, then there should be no right digits.
if (ch == digit) {
if (zeroDigitCount > 0 || sigDigitCount > 0) {
++digitRightCount;
} else {
++digitLeftCount;
}
if (groupingCount >= 0 && decimalPos < 0) {
++groupingCount;
}
} else if ((ch >= zeroDigit && ch <= nineDigit) || ch == sigDigit) {
if (digitRightCount > 0) {
patternError("Unexpected '" + ch + '\'', pattern);
}
if (ch == sigDigit) {
++sigDigitCount;
} else {
++zeroDigitCount;
if (ch != zeroDigit) {
int p = digitLeftCount + zeroDigitCount + digitRightCount;
if (incrementPos >= 0) {
while (incrementPos < p) {
incrementVal *= 10;
++incrementPos;
}
} else {
incrementPos = p;
}
incrementVal += ch - zeroDigit;
}
}
if (groupingCount >= 0 && decimalPos < 0) {
++groupingCount;
}
} else if (ch == groupingSeparator) {
// Bug 4212072 process the Localized pattern like
// "'Fr. '#'##0.05;'Fr.-'#'##0.05" (Locale="CH", groupingSeparator
// == QUOTE) [Richard/GCL]
if (ch == QUOTE && (pos + 1) < pattern.length()) {
char after = pattern.charAt(pos + 1);
if (!(after == digit || (after >= zeroDigit && after <= nineDigit))) {
// A quote outside quotes indicates either the opening
// quote or two quotes, which is a quote literal. That is,
// we have the first quote in 'do' or o''clock.
if (after == QUOTE) {
++pos;
// Fall through to append(ch)
} else {
if (groupingCount < 0) {
subpart = 3; // quoted prefix subpart
} else {
// Transition to suffix subpart
subpart = 2; // suffix subpart
affix = suffix;
sub0Limit = pos--;
}
continue;
}
}
}
if (decimalPos >= 0) {
patternError("Grouping separator after decimal", pattern);
}
groupingCount2 = groupingCount;
groupingCount = 0;
} else if (ch == decimalSeparator) {
if (decimalPos >= 0) {
patternError("Multiple decimal separators", pattern);
}
// Intentionally incorporate the digitRightCount, even though it
// is illegal for this to be > 0 at this point. We check pattern
// syntax below.
decimalPos = digitLeftCount + zeroDigitCount + digitRightCount;
} else {
if (pattern.regionMatches(pos, exponent, 0, exponent.length())) {
if (expDigits >= 0) {
patternError("Multiple exponential symbols", pattern);
}
if (groupingCount >= 0) {
patternError("Grouping separator in exponential", pattern);
}
pos += exponent.length();
// Check for positive prefix
if (pos < pattern.length() && pattern.charAt(pos) == plus) {
expSignAlways = true;
++pos;
}
// Use lookahead to parse out the exponential part of the
// pattern, then jump into suffix subpart.
expDigits = 0;
while (pos < pattern.length() && pattern.charAt(pos) == zeroDigit) {
++expDigits;
++pos;
}
// 1. Require at least one mantissa pattern digit
// 2. Disallow "#+ @" in mantissa
// 3. Require at least one exponent pattern digit
if (((digitLeftCount + zeroDigitCount) < 1 &&
(sigDigitCount + digitRightCount) < 1)
|| (sigDigitCount > 0 && digitLeftCount > 0) || expDigits < 1) {
patternError("Malformed exponential", pattern);
}
}
// Transition to suffix subpart
subpart = 2; // suffix subpart
affix = suffix;
sub0Limit = pos--; // backup: for() will increment
continue;
}
break;
case 1: // Prefix subpart
case 2: // Suffix subpart
// Process the prefix / suffix characters Process unquoted characters
// seen in prefix or suffix subpart.
// Several syntax characters implicitly begins the next subpart if we
// are in the prefix; otherwise they are illegal if unquoted.
if (ch == digit || ch == groupingSeparator || ch == decimalSeparator
|| (ch >= zeroDigit && ch <= nineDigit) || ch == sigDigit) {
// Any of these characters implicitly begins the
// next subpart if we are in the prefix
if (subpart == 1) { // prefix subpart
subpart = 0; // pattern proper subpart
sub0Start = pos--; // Reprocess this character
continue;
} else if (ch == QUOTE) {
// Bug 4212072 process the Localized pattern like
// "'Fr. '#'##0.05;'Fr.-'#'##0.05" (Locale="CH",
// groupingSeparator == QUOTE) [Richard/GCL]
// A quote outside quotes indicates either the opening quote
// or two quotes, which is a quote literal. That is, we have
// the first quote in 'do' or o''clock.
if ((pos + 1) < pattern.length() && pattern.charAt(pos + 1) == QUOTE) {
++pos;
affix.append(ch);
} else {
subpart += 2; // open quote
}
continue;
}
patternError("Unquoted special character '" + ch + '\'', pattern);
} else if (ch == CURRENCY_SIGN) {
// Use lookahead to determine if the currency sign is
// doubled or not.
boolean doubled = (pos + 1) < pattern.length() &&
pattern.charAt(pos + 1) == CURRENCY_SIGN;
// Bug 4212072 To meet the need of expandAffix(String,
// StirngBuffer) [Richard/GCL]
if (doubled) {
++pos; // Skip over the doubled character
affix.append(ch); // append two: one here, one below
if ((pos + 1) < pattern.length() &&
pattern.charAt(pos + 1) == CURRENCY_SIGN) {
++pos; // Skip over the tripled character
affix.append(ch); // append again
currencySignCnt = CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT;
} else {
currencySignCnt = CURRENCY_SIGN_COUNT_IN_ISO_FORMAT;
}
} else {
currencySignCnt = CURRENCY_SIGN_COUNT_IN_SYMBOL_FORMAT;
}
// Fall through to append(ch)
} else if (ch == QUOTE) {
// A quote outside quotes indicates either the opening quote or
// two quotes, which is a quote literal. That is, we have the
// first quote in 'do' or o''clock.
if ((pos + 1) < pattern.length() && pattern.charAt(pos + 1) == QUOTE) {
++pos;
affix.append(ch); // append two: one here, one below
} else {
subpart += 2; // open quote
}
// Fall through to append(ch)
} else if (ch == separator) {
// Don't allow separators in the prefix, and don't allow
// separators in the second pattern (part == 1).
if (subpart == 1 || part == 1) {
patternError("Unquoted special character '" + ch + '\'', pattern);
}
sub2Limit = pos++;
break PARTLOOP; // Go to next part
} else if (ch == percent || ch == perMill) {
// Next handle characters which are appended directly.
if (multpl != 1) {
patternError("Too many percent/permille characters", pattern);
}
multpl = (ch == percent) ? 100 : 1000;
// Convert to non-localized pattern
ch = (ch == percent) ? PATTERN_PERCENT : PATTERN_PER_MILLE;
// Fall through to append(ch)
} else if (ch == minus) {
// Convert to non-localized pattern
ch = PATTERN_MINUS;
// Fall through to append(ch)
} else if (ch == padEscape) {
if (padPos >= 0) {
patternError("Multiple pad specifiers", pattern);
}
if ((pos + 1) == pattern.length()) {
patternError("Invalid pad specifier", pattern);
}
padPos = pos++; // Advance past pad char
padChar = pattern.charAt(pos);
continue;
}
affix.append(ch);
break;
case 3: // Prefix subpart, in quote
case 4: // Suffix subpart, in quote
// A quote within quotes indicates either the closing quote or two
// quotes, which is a quote literal. That is, we have the second quote
// in 'do' or 'don''t'.
if (ch == QUOTE) {
if ((pos + 1) < pattern.length() && pattern.charAt(pos + 1) == QUOTE) {
++pos;
affix.append(ch);
} else {
subpart -= 2; // close quote
}
// Fall through to append(ch)
}
// NOTE: In ICU 2.2 there was code here to parse quoted percent and
// permille characters _within quotes_ and give them special
// meaning. This is incorrect, since quoted characters are literals
// without special meaning.
affix.append(ch);
break;
}
}
if (subpart == 3 || subpart == 4) {
patternError("Unterminated quote", pattern);
}
if (sub0Limit == 0) {
sub0Limit = pattern.length();
}
if (sub2Limit == 0) {
sub2Limit = pattern.length();
}
// Handle patterns with no '0' pattern character. These patterns are legal,
// but must be recodified to make sense. "##.###" -> "#0.###". ".###" ->
// ".0##".
//
// We allow patterns of the form "####" to produce a zeroDigitCount of zero
// (got that?); although this seems like it might make it possible for
// format() to produce empty strings, format() checks for this condition and
// outputs a zero digit in this situation. Having a zeroDigitCount of zero
// yields a minimum integer digits of zero, which allows proper round-trip
// patterns. We don't want "#" to become "#0" when toPattern() is called (even
// though that's what it really is, semantically).
if (zeroDigitCount == 0 && sigDigitCount == 0 &&
digitLeftCount > 0 && decimalPos >= 0) {
// Handle "###.###" and "###." and ".###"
int n = decimalPos;
if (n == 0)
++n; // Handle ".###"
digitRightCount = digitLeftCount - n;
digitLeftCount = n - 1;
zeroDigitCount = 1;
}
// Do syntax checking on the digits, decimal points, and quotes.
if ((decimalPos < 0 && digitRightCount > 0 && sigDigitCount == 0)
|| (decimalPos >= 0
&& (sigDigitCount > 0
|| decimalPos < digitLeftCount
|| decimalPos > (digitLeftCount + zeroDigitCount)))
|| groupingCount == 0
|| groupingCount2 == 0
|| (sigDigitCount > 0 && zeroDigitCount > 0)
|| subpart > 2) { // subpart > 2 == unmatched quote
patternError("Malformed pattern", pattern);
}
// Make sure pad is at legal position before or after affix.
if (padPos >= 0) {
if (padPos == start) {
padPos = PAD_BEFORE_PREFIX;
} else if (padPos + 2 == sub0Start) {
padPos = PAD_AFTER_PREFIX;
} else if (padPos == sub0Limit) {
padPos = PAD_BEFORE_SUFFIX;
} else if (padPos + 2 == sub2Limit) {
padPos = PAD_AFTER_SUFFIX;
} else {
patternError("Illegal pad position", pattern);
}
}
if (part == 0) {
// Set negative affixes temporarily to match the positive
// affixes. Fix this up later after processing both parts.
// Bug 4212072 To meet the need of expandAffix(String, StirngBuffer)
// [Richard/GCL]
posPrefixPattern = negPrefixPattern = prefix.toString();
posSuffixPattern = negSuffixPattern = suffix.toString();
useExponentialNotation = (expDigits >= 0);
if (useExponentialNotation) {
minExponentDigits = expDigits;
exponentSignAlwaysShown = expSignAlways;
}
int digitTotalCount = digitLeftCount + zeroDigitCount + digitRightCount;
// The effectiveDecimalPos is the position the decimal is at or would be
// at if there is no decimal. Note that if decimalPos<0, then
// digitTotalCount == digitLeftCount + zeroDigitCount.
int effectiveDecimalPos = decimalPos >= 0 ? decimalPos : digitTotalCount;
boolean useSigDig = (sigDigitCount > 0);
setSignificantDigitsUsed(useSigDig);
if (useSigDig) {
setMinimumSignificantDigits(sigDigitCount);
setMaximumSignificantDigits(sigDigitCount + digitRightCount);
} else {
int minInt = effectiveDecimalPos - digitLeftCount;
setMinimumIntegerDigits(minInt);
// Upper limit on integer and fraction digits for a Java double
// [Richard/GCL]
setMaximumIntegerDigits(useExponentialNotation ? digitLeftCount + minInt :
DOUBLE_INTEGER_DIGITS);
_setMaximumFractionDigits(decimalPos >= 0 ?
(digitTotalCount - decimalPos) : 0);
setMinimumFractionDigits(decimalPos >= 0 ?
(digitLeftCount + zeroDigitCount - decimalPos) : 0);
}
setGroupingUsed(groupingCount > 0);
this.groupingSize = (groupingCount > 0) ? groupingCount : 0;
this.groupingSize2 = (groupingCount2 > 0 && groupingCount2 != groupingCount)
? groupingCount2 : 0;
this.multiplier = multpl;
setDecimalSeparatorAlwaysShown(decimalPos == 0 || decimalPos == digitTotalCount);
if (padPos >= 0) {
padPosition = padPos;
formatWidth = sub0Limit - sub0Start; // to be fixed up below
pad = padChar;
} else {
formatWidth = 0;
}
if (incrementVal != 0) {
// BigDecimal scale cannot be negative (even though this makes perfect
// sense), so we need to handle this.
int scale = incrementPos - effectiveDecimalPos;
roundingIncrementICU = BigDecimal.valueOf(incrementVal, scale > 0 ? scale : 0);
if (scale < 0) {
roundingIncrementICU = roundingIncrementICU.movePointRight(-scale);
}
roundingMode = BigDecimal.ROUND_HALF_EVEN;
} else {
setRoundingIncrement((BigDecimal) null);
}
// Update currency sign count for the new pattern
currencySignCount = currencySignCnt;
} else {
// Bug 4212072 To meet the need of expandAffix(String, StirngBuffer)
// [Richard/GCL]
negPrefixPattern = prefix.toString();
negSuffixPattern = suffix.toString();
gotNegative = true;
}
}
// Bug 4140009 Process the empty pattern [Richard/GCL]
if (pattern.length() == 0) {
posPrefixPattern = posSuffixPattern = "";
setMinimumIntegerDigits(0);
setMaximumIntegerDigits(DOUBLE_INTEGER_DIGITS);
setMinimumFractionDigits(0);
_setMaximumFractionDigits(DOUBLE_FRACTION_DIGITS);
}
// If there was no negative pattern, or if the negative pattern is identical to
// the positive pattern, then prepend the minus sign to the positive pattern to
// form the negative pattern.
// Bug 4212072 To meet the need of expandAffix(String, StirngBuffer) [Richard/GCL]
if (!gotNegative ||
(negPrefixPattern.equals(posPrefixPattern)
&& negSuffixPattern.equals(posSuffixPattern))) {
negSuffixPattern = posSuffixPattern;
negPrefixPattern = PATTERN_MINUS + posPrefixPattern;
}
setLocale(null, null);
// save the pattern
formatPattern = pattern;
// special handlings for currency instance
if (currencySignCount != CURRENCY_SIGN_COUNT_ZERO) {
// reset rounding increment and max/min fractional digits
// by the currency
Currency theCurrency = getCurrency();
if (theCurrency != null) {
setRoundingIncrement(theCurrency.getRoundingIncrement());
int d = theCurrency.getDefaultFractionDigits();
setMinimumFractionDigits(d);
_setMaximumFractionDigits(d);
}
// initialize currencyPluralInfo if needed
if (currencySignCount == CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT
&& currencyPluralInfo == null) {
currencyPluralInfo = new CurrencyPluralInfo(symbols.getULocale());
}
}
resetActualRounding();
}
private void patternError(String msg, String pattern) {
throw new IllegalArgumentException(msg + " in pattern \"" + pattern + '"');
}
// Rewrite the following 4 "set" methods Upper limit on integer and fraction digits
// for a Java double [Richard/GCL]
/**
* Sets the maximum number of digits allowed in the integer portion of a number. This
* override limits the integer digit count to 309.
*
* @see NumberFormat#setMaximumIntegerDigits
* @stable ICU 2.0
*/
@Override
public void setMaximumIntegerDigits(int newValue) {
super.setMaximumIntegerDigits(Math.min(newValue, DOUBLE_INTEGER_DIGITS));
}
/**
* Sets the minimum number of digits allowed in the integer portion of a number. This
* override limits the integer digit count to 309.
*
* @see NumberFormat#setMinimumIntegerDigits
* @stable ICU 2.0
*/
@Override
public void setMinimumIntegerDigits(int newValue) {
super.setMinimumIntegerDigits(Math.min(newValue, DOUBLE_INTEGER_DIGITS));
}
/**
* {@icu} Returns the minimum number of significant digits that will be
* displayed. This value has no effect unless {@link #areSignificantDigitsUsed()}
* returns true.
*
* @return the fewest significant digits that will be shown
* @stable ICU 3.0
*/
public int getMinimumSignificantDigits() {
return minSignificantDigits;
}
/**
* {@icu} Returns the maximum number of significant digits that will be
* displayed. This value has no effect unless {@link #areSignificantDigitsUsed()}
* returns true.
*
* @return the most significant digits that will be shown
* @stable ICU 3.0
*/
public int getMaximumSignificantDigits() {
return maxSignificantDigits;
}
/**
* {@icu} Sets the minimum number of significant digits that will be displayed. If
* min
is less than one then it is set to one. If the maximum significant
* digits count is less than min
, then it is set to min
.
* 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
*/
public void setMinimumSignificantDigits(int min) {
if (min < 1) {
min = 1;
}
// pin max sig dig to >= min
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
* max
is less than one then it is set to one. If the minimum significant
* digits count is greater than max
, then it is set to max
.
* 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
*/
public void setMaximumSignificantDigits(int max) {
if (max < 1) {
max = 1;
}
// pin min sig dig to 1..max
int min = Math.min(minSignificantDigits, max);
minSignificantDigits = min;
maxSignificantDigits = max;
setSignificantDigitsUsed(true);
}
/**
* {@icu} Returns true if significant digits are in use or false if integer and
* fraction digit counts are in use.
*
* @return true if significant digits are in use
* @stable ICU 3.0
*/
public boolean areSignificantDigitsUsed() {
return useSignificantDigits;
}
/**
* {@icu} Sets whether significant digits are in use, or integer and fraction digit
* counts are in use.
*
* @param useSignificantDigits true to use significant digits, or false to use integer
* and fraction digit counts
* @stable ICU 3.0
*/
public void setSignificantDigitsUsed(boolean useSignificantDigits) {
this.useSignificantDigits = useSignificantDigits;
}
/**
* Sets the Currency object used to display currency amounts. This takes
* effect immediately, if this format is a currency format. If this format is not a
* currency format, then the currency object is used if and when this object becomes a
* currency format through the application of a new pattern.
*
* @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
// locale, and adjust the decimal digits and rounding for the
// given currency.
super.setCurrency(theCurrency);
if (theCurrency != null) {
boolean[] isChoiceFormat = new boolean[1];
String s = theCurrency.getName(symbols.getULocale(),
Currency.SYMBOL_NAME, isChoiceFormat);
symbols.setCurrency(theCurrency);
symbols.setCurrencySymbol(s);
}
if (currencySignCount != CURRENCY_SIGN_COUNT_ZERO) {
if (theCurrency != null) {
setRoundingIncrement(theCurrency.getRoundingIncrement());
int d = theCurrency.getDefaultFractionDigits();
setMinimumFractionDigits(d);
setMaximumFractionDigits(d);
}
if (currencySignCount != CURRENCY_SIGN_COUNT_IN_PLURAL_FORMAT) {
// This is not necessary for plural format type
// because affixes will be resolved in subformat
expandAffixes(null);
}
}
}
/**
* Returns the currency in effect for this formatter. Subclasses should override this
* method as needed. Unlike getCurrency(), this method should never return null.
*
* @internal
* @deprecated This API is ICU internal only.
*/
@Deprecated
@Override
protected Currency getEffectiveCurrency() {
Currency c = getCurrency();
if (c == null) {
c = Currency.getInstance(symbols.getInternationalCurrencySymbol());
}
return c;
}
/**
* Sets the maximum number of digits allowed in the fraction portion of a number. This
* override limits the fraction digit count to 340.
*
* @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));
}
/**
* Sets the minimum number of digits allowed in the fraction portion of a number. This
* override limits the fraction digit count to 340.
*
* @see NumberFormat#setMinimumFractionDigits
* @stable ICU 2.0
*/
@Override
public void setMinimumFractionDigits(int newValue) {
super.setMinimumFractionDigits(Math.min(newValue, DOUBLE_FRACTION_DIGITS));
}
/**
* Sets whether {@link #parse(String, ParsePosition)} returns BigDecimal. The
* default value is false.
*
* @param value true if {@link #parse(String, ParsePosition)}
* returns BigDecimal.
* @stable ICU 3.6
*/
public void setParseBigDecimal(boolean value) {
parseBigDecimal = value;
}
/**
* Returns whether {@link #parse(String, ParsePosition)} returns BigDecimal.
*
* @return true if {@link #parse(String, ParsePosition)} returns BigDecimal.
* @stable ICU 3.6
*/
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
* @stable ICU 51
*/
public void setParseMaxDigits(int newValue) {
if (newValue > 0) {
PARSE_MAX_EXPONENT = newValue;
}
}
/**
* Get the current maximum number of exponent digits when parsing a
* number.
* @return the maximum number of exponent digits for parsing
* @stable ICU 51
*/
public int getParseMaxDigits() {
return PARSE_MAX_EXPONENT;
}
private void writeObject(ObjectOutputStream stream) throws IOException {
// Ticket#6449 Format.Field instances are not serializable. When
// formatToCharacterIterator is called, attributes (ArrayList) stores
// FieldPosition instances with NumberFormat.Field. Because NumberFormat.Field is
// not serializable, we need to clear the contents of the list when writeObject is
// called. We could remove the field or make it transient, but it will break
// serialization compatibility.
attributes.clear();
stream.defaultWriteObject();
}
/**
* First, read the default serializable fields from the stream. Then if
* serialVersionOnStream
is less than 1, indicating that the stream was
* written by JDK 1.1, initialize useExponentialNotation
to false, since
* it was not present in JDK 1.1. Finally, set serialVersionOnStream back to the
* maximum allowed value so that default serialization will work properly if this
* object is streamed out again.
*/
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
stream.defaultReadObject();
// Bug 4185761 validate fields [Richard/GCL]
// We only need to check the maximum counts because NumberFormat .readObject has
// already ensured that the maximum is greater than the minimum count.
// Commented for compatibility with previous version, and reserved for further use
// if (getMaximumIntegerDigits() > DOUBLE_INTEGER_DIGITS ||
// getMaximumFractionDigits() > DOUBLE_FRACTION_DIGITS) { throw new
// InvalidObjectException("Digit count out of range"); }
// Truncate the maximumIntegerDigits to DOUBLE_INTEGER_DIGITS and
// maximumFractionDigits to DOUBLE_FRACTION_DIGITS
if (getMaximumIntegerDigits() > DOUBLE_INTEGER_DIGITS) {
setMaximumIntegerDigits(DOUBLE_INTEGER_DIGITS);
}
if (getMaximumFractionDigits() > DOUBLE_FRACTION_DIGITS) {
_setMaximumFractionDigits(DOUBLE_FRACTION_DIGITS);
}
if (serialVersionOnStream < 2) {
exponentSignAlwaysShown = false;
setInternalRoundingIncrement(null);
roundingMode = BigDecimal.ROUND_HALF_EVEN;
formatWidth = 0;
pad = ' ';
padPosition = PAD_BEFORE_PREFIX;
if (serialVersionOnStream < 1) {
// Didn't have exponential fields
useExponentialNotation = false;
}
}
if (serialVersionOnStream < 3) {
// Versions prior to 3 do not store a currency object. Create one to match
// the DecimalFormatSymbols object.
setCurrencyForSymbols();
}
serialVersionOnStream = currentSerialVersion;
digitList = new DigitList();
if (roundingIncrement != null) {
setInternalRoundingIncrement(new BigDecimal(roundingIncrement));
}
resetActualRounding();
}
private void setInternalRoundingIncrement(BigDecimal value) {
roundingIncrementICU = value;
roundingIncrement = value == null ? null : value.toBigDecimal();
}
// ----------------------------------------------------------------------
// INSTANCE VARIABLES
// ----------------------------------------------------------------------
private transient DigitList digitList = new DigitList();
/**
* The symbol used as a prefix when formatting positive numbers, e.g. "+".
*
* @serial
* @see #getPositivePrefix
*/
private String positivePrefix = "";
/**
* The symbol used as a suffix when formatting positive numbers. This is often an
* empty string.
*
* @serial
* @see #getPositiveSuffix
*/
private String positiveSuffix = "";
/**
* The symbol used as a prefix when formatting negative numbers, e.g. "-".
*
* @serial
* @see #getNegativePrefix
*/
private String negativePrefix = "-";
/**
* The symbol used as a suffix when formatting negative numbers. This is often an
* empty string.
*
* @serial
* @see #getNegativeSuffix
*/
private String negativeSuffix = "";
/**
* The prefix pattern for non-negative numbers. This variable corresponds to
* positivePrefix
.
*
*
This pattern is expanded by the method expandAffix()
to
* positivePrefix
to update the latter to reflect changes in
* symbols
. If this variable is null
then
* positivePrefix
is taken as a literal value that does not change when
* symbols
changes. This variable is always null
for
* DecimalFormat
objects older than stream version 2 restored from
* stream.
*
* @serial
*/
// [Richard/GCL]
private String posPrefixPattern;
/**
* The suffix pattern for non-negative numbers. This variable corresponds to
* positiveSuffix
. This variable is analogous to
* posPrefixPattern
; see that variable for further documentation.
*
* @serial
*/
// [Richard/GCL]
private String posSuffixPattern;
/**
* The prefix pattern for negative numbers. This variable corresponds to
* negativePrefix
. This variable is analogous to
* posPrefixPattern
; see that variable for further documentation.
*
* @serial
*/
// [Richard/GCL]
private String negPrefixPattern;
/**
* The suffix pattern for negative numbers. This variable corresponds to
* negativeSuffix
. This variable is analogous to
* posPrefixPattern
; see that variable for further documentation.
*
* @serial
*/
// [Richard/GCL]
private String negSuffixPattern;
/**
* Formatter for ChoiceFormat-based currency names. If this field is not null, then
* delegate to it to format currency symbols.
*
* @since ICU 2.6
*/
private ChoiceFormat currencyChoice;
/**
* The multiplier for use in percent, permill, etc.
*
* @serial
* @see #getMultiplier
*/
private int multiplier = 1;
/**
* The number of digits between grouping separators in the integer portion of a
* number. Must be greater than 0 if NumberFormat.groupingUsed
is true.
*
* @serial
* @see #getGroupingSize
* @see NumberFormat#isGroupingUsed
*/
private byte groupingSize = 3; // invariant, > 0 if useThousands
/**
* The secondary grouping size. This is only used for Hindi numerals, which use a
* primary grouping of 3 and a secondary grouping of 2, e.g., "12,34,567". If this
* value is less than 1, then secondary grouping is equal to the primary grouping.
*
*/
private byte groupingSize2 = 0;
/**
* If true, forces the decimal separator to always appear in a formatted number, even
* if the fractional part of the number is zero.
*
* @serial
* @see #isDecimalSeparatorAlwaysShown
*/
private boolean decimalSeparatorAlwaysShown = false;
/**
* The DecimalFormatSymbols
object used by this format. It contains the
* symbols used to format numbers, e.g. the grouping separator, decimal separator, and
* so on.
*
* @serial
* @see #setDecimalFormatSymbols
* @see DecimalFormatSymbols
*/
private DecimalFormatSymbols symbols = null; // LIU new DecimalFormatSymbols();
/**
* True to use significant digits rather than integer and fraction digit counts.
*
* @serial
* @since ICU 3.0
*/
private boolean useSignificantDigits = false;
/**
* The minimum number of significant digits to show. Must be >= 1 and <=
* maxSignificantDigits. Ignored unless useSignificantDigits == true.
*
* @serial
* @since ICU 3.0
*/
private int minSignificantDigits = 1;
/**
* The maximum number of significant digits to show. Must be >=
* minSignficantDigits. Ignored unless useSignificantDigits == true.
*
* @serial
* @since ICU 3.0
*/
private int maxSignificantDigits = 6;
/**
* True to force the use of exponential (i.e. scientific) notation
* when formatting numbers.
*
*
Note that the JDK 1.2 public API provides no way to set this
* field, even though it is supported by the implementation and
* the stream format. The intent is that this will be added to the
* API in the future.
*
* @serial
*/
private boolean useExponentialNotation; // Newly persistent in JDK 1.2
/**
* The minimum number of digits used to display the exponent when a number is
* formatted in exponential notation. This field is ignored if
* useExponentialNotation
is not true.
*
*
Note that the JDK 1.2 public API provides no way to set this field, even though
* it is supported by the implementation and the stream format. The intent is that
* this will be added to the API in the future.
*
* @serial
*/
private byte minExponentDigits; // Newly persistent in JDK 1.2
/**
* If true, the exponent is always prefixed with either the plus sign or the minus
* sign. Otherwise, only negative exponents are prefixed with the minus sign. This has
* no effect unless useExponentialNotation
is true.
*
* @serial
* @since AlphaWorks NumberFormat
*/
private boolean exponentSignAlwaysShown = false;
/**
* The value to which numbers are rounded during formatting. For example, if the
* rounding increment is 0.05, then 13.371 would be formatted as 13.350, assuming 3
* fraction digits. Has the value null
if rounding is not in effect, or a
* positive value if rounding is in effect. Default value null
.
*
* @serial
* @since AlphaWorks NumberFormat
*/
// Note: this is kept in sync with roundingIncrementICU.
// it is only kept around to avoid a conversion when formatting a java.math.BigDecimal
private java.math.BigDecimal roundingIncrement = null;
/**
* The value to which numbers are rounded during formatting. For example, if the
* rounding increment is 0.05, then 13.371 would be formatted as 13.350, assuming 3
* fraction digits. Has the value null
if rounding is not in effect, or a
* positive value if rounding is in effect. Default value null
. WARNING:
* the roundingIncrement value is the one serialized.
*
* @serial
* @since AlphaWorks NumberFormat
*/
private transient BigDecimal roundingIncrementICU = null;
/**
* 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
* satisfy a maximum fraction digits limit. The value may assume any of the
* BigDecimal
rounding mode values. Default value
* BigDecimal.ROUND_HALF_EVEN
.
*
* @serial
* @since AlphaWorks NumberFormat
*/
private int roundingMode = BigDecimal.ROUND_HALF_EVEN;
/**
* Operations on BigDecimal
numbers are controlled by a {@link
* MathContext} object, which provides the context (precision and other information)
* for the operation. The default MathContext
settings are
* digits=0, form=PLAIN, lostDigits=false, roundingMode=ROUND_HALF_UP
;
* these settings perform fixed point arithmetic with unlimited precision, as defined
* for the original BigDecimal class in Java 1.1 and Java 1.2
*/
// context for plain unlimited math
private MathContext mathContext = new MathContext(0, MathContext.PLAIN);
/**
* The padded format width, or zero if there is no padding. Must be >= 0. Default
* value zero.
*
* @serial
* @since AlphaWorks NumberFormat
*/
private int formatWidth = 0;
/**
* The character used to pad the result of format to formatWidth
, if
* padding is in effect. Default value ' '.
*
* @serial
* @since AlphaWorks NumberFormat
*/
private char pad = ' ';
/**
* The position in the string at which the pad
character will be
* inserted, if padding is in effect. Must have a value from
* PAD_BEFORE_PREFIX
to PAD_AFTER_SUFFIX
. Default value
* PAD_BEFORE_PREFIX
.
*
* @serial
* @since AlphaWorks NumberFormat
*/
private int padPosition = PAD_BEFORE_PREFIX;
/**
* True if {@link #parse(String, ParsePosition)} to return BigDecimal rather than
* Long, Double or BigDecimal except special values. This property is introduced for
* J2SE 5 compatibility support.
*
* @serial
* @since ICU 3.6
* @see #setParseBigDecimal(boolean)
* @see #isParseBigDecimal()
*/
private boolean parseBigDecimal = false;
// ----------------------------------------------------------------------
static final int currentSerialVersion = 3;
/**
* The internal serial version which says which version was written Possible values
* are:
*
*
*
* - 0 (default): versions before JDK 1.2
*
*
- 1: version from JDK 1.2 and later, which includes the two new fields
*
useExponentialNotation
and minExponentDigits
.
*
* - 2: version on AlphaWorks, which adds roundingMode, formatWidth, pad,
* padPosition, exponentSignAlwaysShown, roundingIncrement.
*
*
- 3: ICU 2.2. Adds currency object.
*
*
*
* @serial
*/
private int serialVersionOnStream = currentSerialVersion;
// ----------------------------------------------------------------------
// CONSTANTS
// ----------------------------------------------------------------------
/**
* {@icu} Constant for {@link #getPadPosition()} and {@link #setPadPosition(int)} to
* specify pad characters inserted before the prefix.
*
* @see #setPadPosition
* @see #getPadPosition
* @see #PAD_AFTER_PREFIX
* @see #PAD_BEFORE_SUFFIX
* @see #PAD_AFTER_SUFFIX
* @stable ICU 2.0
*/
public static final int PAD_BEFORE_PREFIX = 0;
/**
* {@icu} Constant for {@link #getPadPosition()} and {@link #setPadPosition(int)} to
* specify pad characters inserted after the prefix.
*
* @see #setPadPosition
* @see #getPadPosition
* @see #PAD_BEFORE_PREFIX
* @see #PAD_BEFORE_SUFFIX
* @see #PAD_AFTER_SUFFIX
* @stable ICU 2.0
*/
public static final int PAD_AFTER_PREFIX = 1;
/**
* {@icu} Constant for {@link #getPadPosition()} and {@link #setPadPosition(int)} to
* specify pad characters inserted before the suffix.
*
* @see #setPadPosition
* @see #getPadPosition
* @see #PAD_BEFORE_PREFIX
* @see #PAD_AFTER_PREFIX
* @see #PAD_AFTER_SUFFIX
* @stable ICU 2.0
*/
public static final int PAD_BEFORE_SUFFIX = 2;
/**
* {@icu} Constant for {@link #getPadPosition()} and {@link #setPadPosition(int)} to
* specify pad characters inserted after the suffix.
*
* @see #setPadPosition
* @see #getPadPosition
* @see #PAD_BEFORE_PREFIX
* @see #PAD_AFTER_PREFIX
* @see #PAD_BEFORE_SUFFIX
* @stable ICU 2.0
*/
public static final int PAD_AFTER_SUFFIX = 3;
// Constants for characters used in programmatic (unlocalized) patterns.
static final char PATTERN_ZERO_DIGIT = '0';
static final char PATTERN_ONE_DIGIT = '1';
static final char PATTERN_TWO_DIGIT = '2';
static final char PATTERN_THREE_DIGIT = '3';
static final char PATTERN_FOUR_DIGIT = '4';
static final char PATTERN_FIVE_DIGIT = '5';
static final char PATTERN_SIX_DIGIT = '6';
static final char PATTERN_SEVEN_DIGIT = '7';
static final char PATTERN_EIGHT_DIGIT = '8';
static final char PATTERN_NINE_DIGIT = '9';
static final char PATTERN_GROUPING_SEPARATOR = ',';
static final char PATTERN_DECIMAL_SEPARATOR = '.';
static final char PATTERN_DIGIT = '#';
static final char PATTERN_SIGNIFICANT_DIGIT = '@';
static final char PATTERN_EXPONENT = 'E';
static final char PATTERN_PLUS_SIGN = '+';
// Affix
private static final char PATTERN_PER_MILLE = '\u2030';
private static final char PATTERN_PERCENT = '%';
static final char PATTERN_PAD_ESCAPE = '*';
/**
* Bug 4212072 To meet the need of expandAffix(String, StirngBuffer) [Richard/GCL]
*/
private static final char PATTERN_MINUS = '-';
// Other
private static final char PATTERN_SEPARATOR = ';';
// Pad escape is package private to allow access by DecimalFormatSymbols.
// Also plus sign. Also exponent.
/**
* The CURRENCY_SIGN is the standard Unicode symbol for currency. It is used in
* patterns and substitued with either the currency symbol, or if it is doubled, with
* the international currency symbol. If the CURRENCY_SIGN is seen in a pattern, then
* the decimal separator is replaced with the monetary decimal separator.
*
* The CURRENCY_SIGN is not localized.
*/
private static final char CURRENCY_SIGN = '\u00A4';
private static final char QUOTE = '\'';
/**
* Upper limit on integer and fraction digits for a Java double [Richard/GCL]
*/
static final int DOUBLE_INTEGER_DIGITS = 309;
static final int DOUBLE_FRACTION_DIGITS = 340;
/**
* When someone turns on scientific mode, we assume that more than this number of
* digits is due to flipping from some other mode that didn't restrict the maximum,
* and so we force 1 integer digit. We don't bother to track and see if someone is
* using exponential notation with more than this number, it wouldn't make sense
* anyway, and this is just to make sure that someone turning on scientific mode with
* default settings doesn't end up with lots of zeroes.
*/
static final int MAX_SCIENTIFIC_INTEGER_DIGITS = 8;
// Proclaim JDK 1.1 serial compatibility.
private static final long serialVersionUID = 864413376551465018L;
private ArrayList attributes = new ArrayList();
// The following are used in currency format
// -- triple currency sign char array
// private static final char[] tripleCurrencySign = {0xA4, 0xA4, 0xA4};
// -- triple currency sign string
// private static final String tripleCurrencyStr = new String(tripleCurrencySign);
//
// -- default currency plural pattern char array
// private static final char[] defaultCurrencyPluralPatternChar =
// {0, '.', '#', '#', ' ', 0xA4, 0xA4, 0xA4};
// -- default currency plural pattern string
// private static final String defaultCurrencyPluralPattern =
// new String(defaultCurrencyPluralPatternChar);
// pattern used in this formatter
private String formatPattern = "";
// style is only valid when decimal formatter is constructed by
// DecimalFormat(pattern, decimalFormatSymbol, style)
private int style = NumberFormat.NUMBERSTYLE;
/**
* Represents whether this is a currency format, and which currency format style. 0:
* not currency format type; 1: currency style -- symbol name, such as "$" for US
* dollar. 2: currency style -- ISO name, such as USD for US dollar. 3: currency style
* -- plural long name, such as "US Dollar" for "1.00 US Dollar", or "US Dollars" for
* "3.00 US Dollars".
*/
private int currencySignCount = CURRENCY_SIGN_COUNT_ZERO;
/**
* For parsing purposes, we need to remember all prefix patterns and suffix patterns
* of every currency format pattern, including the pattern of the default currency
* style, ISO currency style, and plural currency style. The patterns are set through
* applyPattern. The following are used to represent the affix patterns in currency
* plural formats.
*/
private static final class AffixForCurrency {
// negative prefix pattern
private String negPrefixPatternForCurrency = null;
// negative suffix pattern
private String negSuffixPatternForCurrency = null;
// positive prefix pattern
private String posPrefixPatternForCurrency = null;
// positive suffix pattern
private String posSuffixPatternForCurrency = null;
private final int patternType;
public AffixForCurrency(String negPrefix, String negSuffix, String posPrefix,
String posSuffix, int type) {
negPrefixPatternForCurrency = negPrefix;
negSuffixPatternForCurrency = negSuffix;
posPrefixPatternForCurrency = posPrefix;
posSuffixPatternForCurrency = posSuffix;
patternType = type;
}
public String getNegPrefix() {
return negPrefixPatternForCurrency;
}
public String getNegSuffix() {
return negSuffixPatternForCurrency;
}
public String getPosPrefix() {
return posPrefixPatternForCurrency;
}
public String getPosSuffix() {
return posSuffixPatternForCurrency;
}
public int getPatternType() {
return patternType;
}
}
// Affix pattern set for currency. It is a set of AffixForCurrency, each element of
// the set saves the negative prefix, negative suffix, positive prefix, and positive
// suffix of a pattern.
private transient Set affixPatternsForCurrency = null;
// For currency parsing. Since currency parsing needs to parse against all currency
// patterns, before the parsing, we need to set up the affix patterns for all currencies.
private transient boolean isReadyForParsing = false;
// 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 || isScientificNotation()) {
// This rounding fix is irrelevant if mode is ROUND_HALF_EVEN as DigitList
// does ROUND_HALF_EVEN for us. This rounding fix won't work at all for
// scientific notation.
actualRoundingIncrementICU = null;
} else {
if (getMaximumFractionDigits() > 0) {
actualRoundingIncrementICU = BigDecimal.ONE.movePointLeft(getMaximumFractionDigits());
} else {
actualRoundingIncrementICU = BigDecimal.ONE;
}
}
}
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