| /* |
| * Copyright 2008 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package com.google.gwt.i18n.client; |
| |
| import com.google.gwt.core.client.GWT; |
| import com.google.gwt.i18n.client.constants.NumberConstants; |
| |
| /** |
| * Formats and parses numbers using locale-sensitive patterns. |
| * |
| * This class provides comprehensive and flexible support for a wide variety of |
| * localized formats, including |
| * <ul> |
| * <li><b>Locale-specific symbols</b> such as decimal point, group separator, |
| * digit representation, currency symbol, percent, and permill</li> |
| * <li><b>Numeric variations</b> including integers ("123"), fixed-point |
| * numbers ("123.4"), scientific notation ("1.23E4"), percentages ("12%"), and |
| * currency amounts ("$123")</li> |
| * <li><b>Predefined standard patterns</b> that can be used both for parsing |
| * and formatting, including {@link #getDecimalFormat() decimal}, |
| * {@link #getCurrencyFormat() currency}, |
| * {@link #getPercentFormat() percentages}, and |
| * {@link #getScientificFormat() scientific}</li> |
| * <li><b>Custom patterns</b> and supporting features designed to make it |
| * possible to parse and format numbers in any locale, including support for |
| * Western, Arabic, and Indic digits</li> |
| * </ul> |
| * |
| * <h3>Patterns</h3> |
| * <p> |
| * Formatting and parsing are based on customizable patterns that can include a |
| * combination of literal characters and special characters that act as |
| * placeholders and are replaced by their localized counterparts. 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 '<code>#</code>' character is replaced by a localized digit. |
| * </p> |
| * |
| * <p> |
| * Often the replacement character is the same as the pattern character. In the |
| * U.S. locale, for example, the '<code>,</code>' grouping character is |
| * replaced by the same character '<code>,</code>'. However, the replacement |
| * is still actually happening, and in a different locale, the grouping |
| * character may change to a different character, such as '<code>.</code>'. |
| * 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. |
| * </p> |
| * |
| * <p> |
| * The characters listed below are used in patterns. Localized symbols use the |
| * corresponding characters taken from corresponding locale symbol collection, |
| * which can be found in the properties files residing in the |
| * <code><nobr>com.google.gwt.i18n.client.constants</nobr></code>. 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. |
| * </p> |
| * |
| * <table> |
| * <tr> |
| * <th>Symbol</th> |
| * <th>Location</th> |
| * <th>Localized?</th> |
| * <th>Meaning</th> |
| * </tr> |
| * |
| * <tr> |
| * <td><code>0</code></td> |
| * <td>Number</td> |
| * <td>Yes</td> |
| * <td>Digit</td> |
| * </tr> |
| * |
| * <tr> |
| * <td><code>#</code></td> |
| * <td>Number</td> |
| * <td>Yes</td> |
| * <td>Digit, zero shows as absent</td> |
| * </tr> |
| * |
| * <tr> |
| * <td><code>.</code></td> |
| * <td>Number</td> |
| * <td>Yes</td> |
| * <td>Decimal separator or monetary decimal separator</td> |
| * </tr> |
| * |
| * <tr> |
| * <td><code>-</code></td> |
| * <td>Number</td> |
| * <td>Yes</td> |
| * <td>Minus sign</td> |
| * </tr> |
| * |
| * <tr> |
| * <td><code>,</code></td> |
| * <td>Number</td> |
| * <td>Yes</td> |
| * <td>Grouping separator</td> |
| * </tr> |
| * |
| * <tr> |
| * <td><code>E</code></td> |
| * <td>Number</td> |
| * <td>Yes</td> |
| * <td>Separates mantissa and exponent in scientific notation; need not be |
| * quoted in prefix or suffix</td> |
| * </tr> |
| * |
| * <tr> |
| * <td><code>;</code></td> |
| * <td>Subpattern boundary</td> |
| * <td>Yes</td> |
| * <td>Separates positive and negative subpatterns</td> |
| * </tr> |
| * |
| * <tr> |
| * <td><code>%</code></td> |
| * <td>Prefix or suffix</td> |
| * <td>Yes</td> |
| * <td>Multiply by 100 and show as percentage</td> |
| * </tr> |
| * |
| * <tr> |
| * <td><nobr><code>\u2030</code> (\u005Cu2030)</nobr></td> |
| * <td>Prefix or suffix</td> |
| * <td>Yes</td> |
| * <td>Multiply by 1000 and show as per mille</td> |
| * </tr> |
| * |
| * <tr> |
| * <td><nobr><code>\u00A4</code> (\u005Cu00A4)</nobr></td> |
| * <td>Prefix or suffix</td> |
| * <td>No</td> |
| * <td>Currency sign, replaced by currency symbol; if doubled, replaced by |
| * international currency symbol; if present in a pattern, the monetary decimal |
| * separator is used instead of the decimal separator</td> |
| * </tr> |
| * |
| * <tr> |
| * <td><code>'</code></td> |
| * <td>Prefix or suffix</td> |
| * <td>No</td> |
| * <td>Used to quote special characters in a prefix or suffix; for example, |
| * <code>"'#'#"</code> formats <code>123</code> to <code>"#123"</code>; |
| * to create a single quote itself, use two in succession, such as |
| * <code>"# o''clock"</code></td> |
| * </tr> |
| * |
| * </table> |
| * |
| * <p> |
| * A <code>NumberFormat</code> pattern contains a postive and negative |
| * subpattern separated by a semicolon, such as |
| * <code>"#,##0.00;(#,##0.00)"</code>. 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, <code>"0.00"</code> alone is equivalent to |
| * <code>"0.00;-0.00"</code>. 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 <code>"#,##0.0#;(#)"</code> has precisely the |
| * same result as <code>"#,##0.0#;(#,##0.0#)"</code>. |
| * </p> |
| * |
| * <p> |
| * 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, the decimal separator and thousands separator should |
| * be distinct characters, or parsing will be impossible. |
| * </p> |
| * |
| * <p> |
| * 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". |
| * </p> |
| * |
| * <h3>Pattern Grammar (BNF)</h3> |
| * <p> |
| * The pattern itself uses the following grammar: |
| * </p> |
| * |
| * <table> |
| * <tr> |
| * <td>pattern</td> |
| * <td>:=</td> |
| * <td style="white-space: nowrap">subpattern ('<code>;</code>' |
| * subpattern)?</td> |
| * </tr> |
| * <tr> |
| * <td>subpattern</td> |
| * <td>:=</td> |
| * <td>prefix? number exponent? suffix?</td> |
| * </tr> |
| * <tr> |
| * <td>number</td> |
| * <td>:=</td> |
| * <td style="white-space: nowrap">(integer ('<code>.</code>' fraction)?) | |
| * sigDigits</td> |
| * </tr> |
| * <tr> |
| * <td>prefix</td> |
| * <td>:=</td> |
| * <td style="white-space: nowrap">'<code>\u005Cu0000</code>'..'<code>\u005CuFFFD</code>' - |
| * specialCharacters</td> |
| * </tr> |
| * <tr> |
| * <td>suffix</td> |
| * <td>:=</td> |
| * <td style="white-space: nowrap">'<code>\u005Cu0000</code>'..'<code>\u005CuFFFD</code>' - |
| * specialCharacters</td> |
| * </tr> |
| * <tr> |
| * <td>integer</td> |
| * <td>:=</td> |
| * <td style="white-space: nowrap">'<code>#</code>'* '<code>0</code>'*'<code>0</code>'</td> |
| * </tr> |
| * <tr> |
| * <td>fraction</td> |
| * <td>:=</td> |
| * <td style="white-space: nowrap">'<code>0</code>'* '<code>#</code>'*</td> |
| * </tr> |
| * <tr> |
| * <td>sigDigits</td> |
| * <td>:=</td> |
| * <td style="white-space: nowrap">'<code>#</code>'* '<code>@</code>''<code>@</code>'* '<code>#</code>'*</td> |
| * </tr> |
| * <tr> |
| * <td>exponent</td> |
| * <td>:=</td> |
| * <td style="white-space: nowrap">'<code>E</code>' '<code>+</code>'? '<code>0</code>'* '<code>0</code>'</td> |
| * </tr> |
| * <tr> |
| * <td>padSpec</td> |
| * <td>:=</td> |
| * <td style="white-space: nowrap">'<code>*</code>' padChar</td> |
| * </tr> |
| * <tr> |
| * <td>padChar</td> |
| * <td>:=</td> |
| * <td>'<code>\u005Cu0000</code>'..'<code>\u005CuFFFD</code>' - quote</td> |
| * </tr> |
| * </table> |
| * |
| * <p> |
| * Notation: |
| * </p> |
| * |
| * <table> |
| * <tr> |
| * <td>X*</td> |
| * <td style="white-space: nowrap">0 or more instances of X</td> |
| * </tr> |
| * |
| * <tr> |
| * <td>X?</td> |
| * <td style="white-space: nowrap">0 or 1 instances of X</td> |
| * </tr> |
| * |
| * <tr> |
| * <td>X|Y</td> |
| * <td style="white-space: nowrap">either X or Y</td> |
| * </tr> |
| * |
| * <tr> |
| * <td>C..D</td> |
| * <td style="white-space: nowrap">any character from C up to D, inclusive</td> |
| * </tr> |
| * |
| * <tr> |
| * <td>S-T</td> |
| * <td style="white-space: nowrap">characters in S, except those in T</td> |
| * </tr> |
| * </table> |
| * |
| * <p> |
| * The first subpattern is for positive numbers. The second (optional) |
| * subpattern is for negative numbers. |
| * </p> |
| * |
| * <h3>Example</h3> {@example com.google.gwt.examples.NumberFormatExample} |
| * |
| * |
| */ |
| public class NumberFormat { |
| |
| // Sets of constants as defined for the current locale from CLDR. |
| protected static final NumberConstants localizedNumberConstants = LocaleInfo.getCurrentLocale().getNumberConstants(); |
| |
| /** |
| * Current NumberConstants interface to use, see |
| * {@link #setForcedLatinDigits(boolean)} for changing it. |
| */ |
| protected static NumberConstants defaultNumberConstants = localizedNumberConstants; |
| |
| // Cached instances of standard formatters. |
| private static NumberFormat cachedCurrencyFormat; |
| private static NumberFormat cachedDecimalFormat; |
| private static NumberFormat cachedPercentFormat; |
| private static NumberFormat cachedScientificFormat; |
| |
| // Number constants mapped to use latin digits/separators. |
| private static NumberConstants latinNumberConstants = null; |
| |
| // Localized characters for dot and comma in number patterns, used to produce |
| // the latin mapping for arbitrary locales. Any separator not in either of |
| // these strings will be mapped to non-breaking space (U+00A0). |
| private static final String LOCALIZED_COMMA_EQUIVALENTS = ",\u060C\u066B\u3001\uFE10\uFE11\uFE50\uFE51\uFF0C\uFF64"; |
| private static final String LOCALIZED_DOT_EQUIVALENTS = ".\u2024\u3002\uFE12\uFE52\uFF0E\uFF61"; |
| |
| // Constants for characters used in programmatic (unlocalized) patterns. |
| private static final char CURRENCY_SIGN = '\u00A4'; |
| private static final char PATTERN_DECIMAL_SEPARATOR = '.'; |
| private static final char PATTERN_DIGIT = '#'; |
| private static final char PATTERN_EXPONENT = 'E'; |
| private static final char PATTERN_GROUPING_SEPARATOR = ','; |
| private static final char PATTERN_MINUS = '-'; |
| private static final char PATTERN_PER_MILLE = '\u2030'; |
| private static final char PATTERN_PERCENT = '%'; |
| private static final char PATTERN_SEPARATOR = ';'; |
| private static final char PATTERN_ZERO_DIGIT = '0'; |
| |
| private static final char QUOTE = '\''; |
| |
| /** |
| * @return true if all new NumberFormat instances will use latin digits |
| * and related characters rather than the localized ones. |
| */ |
| public static boolean forcedLatinDigits() { |
| return defaultNumberConstants != localizedNumberConstants; |
| } |
| |
| /** |
| * Provides the standard currency format for the default locale. |
| * |
| * @return a <code>NumberFormat</code> capable of producing and consuming |
| * currency format for the default locale |
| */ |
| public static NumberFormat getCurrencyFormat() { |
| if (cachedCurrencyFormat == null) { |
| cachedCurrencyFormat = new NumberFormat( |
| defaultNumberConstants.currencyPattern(), CurrencyList.get().getDefault(), false); |
| } |
| return cachedCurrencyFormat; |
| } |
| |
| /** |
| * Provides the standard currency format for the default locale using a |
| * specified currency. |
| * |
| * @param currencyData currency data to use |
| * @return a <code>NumberFormat</code> capable of producing and consuming |
| * currency format for the default locale |
| */ |
| public static NumberFormat getCurrencyFormat(CurrencyData currencyData) { |
| return new NumberFormat(defaultNumberConstants.currencyPattern(), |
| currencyData, false); |
| } |
| |
| /** |
| * Provides the standard currency format for the default locale using a |
| * specified currency. |
| * |
| * @param currencyCode valid currency code, as defined in |
| * com.google.gwt.i18n.client.constants.CurrencyCodeMapConstants.properties |
| * @return a <code>NumberFormat</code> capable of producing and consuming |
| * currency format for the default locale |
| * @throws IllegalArgumentException if the currency code is unknown |
| */ |
| public static NumberFormat getCurrencyFormat(String currencyCode) { |
| // TODO(jat): consider caching values per currency code. |
| return new NumberFormat(defaultNumberConstants.currencyPattern(), |
| lookupCurrency(currencyCode), false); |
| } |
| |
| /** |
| * Provides the standard decimal format for the default locale. |
| * |
| * @return a <code>NumberFormat</code> capable of producing and consuming |
| * decimal format for the default locale |
| */ |
| public static NumberFormat getDecimalFormat() { |
| if (cachedDecimalFormat == null) { |
| cachedDecimalFormat = new NumberFormat( |
| defaultNumberConstants.decimalPattern(), |
| CurrencyList.get().getDefault(), false); |
| } |
| return cachedDecimalFormat; |
| } |
| |
| /** |
| * Gets a <code>NumberFormat</code> instance for the default locale using |
| * the specified pattern and the default currencyCode. |
| * |
| * @param pattern pattern for this formatter |
| * @return a NumberFormat instance |
| * @throws IllegalArgumentException if the specified pattern is invalid |
| */ |
| public static NumberFormat getFormat(String pattern) { |
| return new NumberFormat(pattern, CurrencyList.get().getDefault(), true); |
| } |
| |
| /** |
| * Gets a custom <code>NumberFormat</code> instance for the default locale |
| * using the specified pattern and currency code. |
| * |
| * @param pattern pattern for this formatter |
| * @param currencyData currency data |
| * @return a NumberFormat instance |
| * @throws IllegalArgumentException if the specified pattern is invalid |
| */ |
| public static NumberFormat getFormat(String pattern, |
| CurrencyData currencyData) { |
| return new NumberFormat(pattern, currencyData, true); |
| } |
| |
| /** |
| * Gets a custom <code>NumberFormat</code> instance for the default locale |
| * using the specified pattern and currency code. |
| * |
| * @param pattern pattern for this formatter |
| * @param currencyCode international currency code |
| * @return a NumberFormat instance |
| * @throws IllegalArgumentException if the specified pattern is invalid |
| * or the currency code is unknown |
| */ |
| public static NumberFormat getFormat(String pattern, String currencyCode) { |
| return new NumberFormat(pattern, lookupCurrency(currencyCode), true); |
| } |
| |
| /** |
| * Provides the standard percent format for the default locale. |
| * |
| * @return a <code>NumberFormat</code> capable of producing and consuming |
| * percent format for the default locale |
| */ |
| public static NumberFormat getPercentFormat() { |
| if (cachedPercentFormat == null) { |
| cachedPercentFormat = new NumberFormat( |
| defaultNumberConstants.percentPattern(), |
| CurrencyList.get().getDefault(), false); |
| } |
| return cachedPercentFormat; |
| } |
| |
| /** |
| * Provides the standard scientific format for the default locale. |
| * |
| * @return a <code>NumberFormat</code> capable of producing and consuming |
| * scientific format for the default locale |
| */ |
| public static NumberFormat getScientificFormat() { |
| if (cachedScientificFormat == null) { |
| cachedScientificFormat = new NumberFormat( |
| defaultNumberConstants.scientificPattern(), |
| CurrencyList.get().getDefault(), false); |
| } |
| return cachedScientificFormat; |
| } |
| |
| /** |
| * Specify whether all new NumberFormat instances will use latin digits |
| * and related characters rather than the localized ones. |
| * |
| * @param useLatinDigits true if latin digits/etc should be used, false if |
| * localized digits/etc should be used. |
| */ |
| public static void setForcedLatinDigits(boolean useLatinDigits) { |
| // Invalidate cached formats if changing |
| if (useLatinDigits != forcedLatinDigits()) { |
| cachedCurrencyFormat = null; |
| cachedDecimalFormat = null; |
| cachedPercentFormat = null; |
| cachedScientificFormat = null; |
| } |
| if (useLatinDigits) { |
| if (latinNumberConstants == null) { |
| latinNumberConstants = createLatinNumberConstants( |
| localizedNumberConstants); |
| } |
| defaultNumberConstants = latinNumberConstants; |
| } else { |
| defaultNumberConstants = localizedNumberConstants; |
| } |
| } |
| |
| /** |
| * Create a delocalized NumberConstants instance from a localized one. |
| * |
| * @param orig localized NumberConstants instance |
| * @return NumberConstants instance using latin digits/etc |
| */ |
| protected static NumberConstants createLatinNumberConstants( |
| final NumberConstants orig) { |
| final String groupingSeparator = remapSeparator( |
| orig.groupingSeparator()); |
| final String decimalSeparator = remapSeparator( |
| orig.decimalSeparator()); |
| final String monetaryGroupingSeparator = remapSeparator( |
| orig.monetaryGroupingSeparator()); |
| final String monetarySeparator = remapSeparator( |
| orig.monetarySeparator()); |
| return new NumberConstants() { |
| public String currencyPattern() { |
| return orig.currencyPattern(); |
| } |
| |
| public String decimalPattern() { |
| return orig.decimalPattern(); |
| } |
| |
| public String decimalSeparator() { |
| return decimalSeparator; |
| } |
| |
| public String defCurrencyCode() { |
| return orig.defCurrencyCode(); |
| } |
| |
| public String exponentialSymbol() { |
| return orig.exponentialSymbol(); |
| } |
| |
| public String groupingSeparator() { |
| return groupingSeparator; |
| } |
| |
| public String infinity() { |
| return orig.infinity(); |
| } |
| |
| public String minusSign() { |
| return orig.minusSign(); |
| } |
| |
| public String monetaryGroupingSeparator() { |
| return monetaryGroupingSeparator; |
| } |
| |
| public String monetarySeparator() { |
| return monetarySeparator; |
| } |
| |
| public String notANumber() { |
| return orig.notANumber(); |
| } |
| |
| public String percent() { |
| return orig.percent(); |
| } |
| |
| public String percentPattern() { |
| return orig.percentPattern(); |
| } |
| |
| public String perMill() { |
| return orig.perMill(); |
| } |
| |
| public String plusSign() { |
| return orig.plusSign(); |
| } |
| |
| public String scientificPattern() { |
| return orig.scientificPattern(); |
| } |
| |
| public String zeroDigit() { |
| return "0"; |
| } |
| }; |
| } |
| |
| /** |
| * Remap a localized separator to an equivalent latin one. |
| * |
| * @param separator |
| * @return delocalized separator character |
| */ |
| protected static String remapSeparator(String separator) { |
| char ch = separator.length() > 0 ? separator.charAt(0) : 0xFFFF; |
| if (LOCALIZED_DOT_EQUIVALENTS.indexOf(ch) >= 0) { |
| return "."; |
| } |
| if (LOCALIZED_COMMA_EQUIVALENTS.indexOf(ch) >= 0) { |
| return ","; |
| } |
| return "\u00A0"; |
| } |
| |
| /** |
| * Lookup a currency code. |
| * |
| * @param currencyCode ISO4217 currency code |
| * @return a CurrencyData instance |
| * @throws IllegalArgumentException if the currency code is unknown |
| */ |
| private static CurrencyData lookupCurrency(String currencyCode) { |
| CurrencyData currencyData = CurrencyList.get().lookup(currencyCode); |
| if (currencyData == null) { |
| throw new IllegalArgumentException("Currency code " + currencyCode |
| + " is unkown in locale " |
| + LocaleInfo.getCurrentLocale().getLocaleName()); |
| } |
| return currencyData; |
| } |
| |
| private static native String toFixed(double d, int digits) /*-{ |
| return d.toFixed(digits); |
| }-*/; |
| |
| // The currency code. |
| private final String currencyCode; |
| |
| // Currency setting. |
| private final String currencySymbol; |
| |
| // Forces the decimal separator to always appear in a formatted number. |
| private boolean decimalSeparatorAlwaysShown = false; |
| |
| // The number of digits between grouping separators in the integer |
| // portion of a number. |
| private int groupingSize = 3; |
| |
| private boolean isCurrencyFormat = false; |
| |
| private int maximumFractionDigits = 3; // invariant, >= minFractionDigits. |
| private int maximumIntegerDigits = 40; |
| private int minExponentDigits; |
| private int minimumFractionDigits = 0; |
| private int minimumIntegerDigits = 1; |
| |
| // The multiplier for use in percent, per mille, etc. |
| private int multiplier = 1; |
| |
| private String negativePrefix = "-"; |
| |
| private String negativeSuffix = ""; |
| |
| // Locale specific symbol collection. |
| private final NumberConstants numberConstants; |
| |
| // The pattern to use for formatting and parsing. |
| private final String pattern; |
| |
| private String positivePrefix = ""; |
| |
| private String positiveSuffix = ""; |
| |
| // True to force the use of exponential (i.e. scientific) notation. |
| private boolean useExponentialNotation = false; |
| |
| /** |
| * Constructs a format object based on the specified settings. |
| * |
| * @param numberConstants the locale-specific number constants to use for this |
| * format -- **NOTE** subclasses passing their own instance here |
| * should pay attention to {@link #forcedLatinDigits()} and remap |
| * localized symbols using |
| * {@link #createLatinNumberConstants(NumberConstants)} |
| * @param pattern pattern that specify how number should be formatted |
| * @param cdata currency data that should be used |
| * @param userSuppliedPattern true if the pattern was supplied by the user |
| */ |
| protected NumberFormat(NumberConstants numberConstants, String pattern, CurrencyData cdata, |
| boolean userSuppliedPattern) { |
| if (cdata == null) { |
| throw new IllegalArgumentException("Unknown currency code"); |
| } |
| this.numberConstants = numberConstants; |
| this.pattern = pattern; |
| currencyCode = cdata.getCurrencyCode(); |
| currencySymbol = cdata.getCurrencySymbol(); |
| |
| // TODO: handle per-currency flags, such as symbol prefix/suffix and spacing |
| parsePattern(this.pattern); |
| if (!userSuppliedPattern && isCurrencyFormat) { |
| minimumFractionDigits = cdata.getDefaultFractionDigits(); |
| maximumFractionDigits = minimumFractionDigits; |
| } |
| } |
| |
| /** |
| * Constructs a format object for the default locale based on the specified |
| * settings. |
| * |
| * @param pattern pattern that specify how number should be formatted |
| * @param cdata currency data that should be used |
| * @param userSuppliedPattern true if the pattern was supplied by the user |
| */ |
| protected NumberFormat(String pattern, CurrencyData cdata, boolean userSuppliedPattern) { |
| this(defaultNumberConstants, pattern, cdata, userSuppliedPattern); |
| } |
| |
| /** |
| * This method formats a double to produce a string. |
| * |
| * @param number The double to format |
| * @return the formatted number string |
| */ |
| public String format(double number) { |
| StringBuffer result = new StringBuffer(); |
| |
| if (Double.isNaN(number)) { |
| result.append(numberConstants.notANumber()); |
| return result.toString(); |
| } |
| |
| boolean isNegative = ((number < 0.0) || (number == 0.0 && 1 / number < 0.0)); |
| |
| result.append(isNegative ? negativePrefix : positivePrefix); |
| if (Double.isInfinite(number)) { |
| result.append(numberConstants.infinity()); |
| } else { |
| if (isNegative) { |
| number = -number; |
| } |
| number *= multiplier; |
| if (useExponentialNotation) { |
| subformatExponential(number, result); |
| } else { |
| subformatFixed(number, result, minimumIntegerDigits); |
| } |
| } |
| |
| result.append(isNegative ? negativeSuffix : positiveSuffix); |
| |
| return result.toString(); |
| } |
| |
| /** |
| * Returns the pattern used by this number format. |
| */ |
| public String getPattern() { |
| return pattern; |
| } |
| |
| /** |
| * Parses text to produce a numeric value. A {@link NumberFormatException} is |
| * thrown if either the text is empty or if the parse does not consume all |
| * characters of the text. |
| * |
| * @param text the string being parsed |
| * @return a parsed number value |
| * @throws NumberFormatException if the entire text could not be converted |
| * into a number |
| */ |
| public double parse(String text) throws NumberFormatException { |
| int[] pos = {0}; |
| double result = parse(text, pos); |
| if (pos[0] == 0 || pos[0] != text.length()) { |
| throw new NumberFormatException(text); |
| } |
| return result; |
| } |
| |
| /** |
| * Parses text to produce a numeric value. |
| * |
| * <p> |
| * The method attempts to parse text starting at the index given by pos. If |
| * parsing succeeds, then the index of <code>pos</code> is updated to the |
| * index after the last character used (parsing does not necessarily use all |
| * characters up to the end of the string), and the parsed number is returned. |
| * The updated <code>pos</code> can be used to indicate the starting point |
| * for the next call to this method. If an error occurs, then the index of |
| * <code>pos</code> is not changed. |
| * </p> |
| * |
| * @param text the string to be parsed |
| * @param inOutPos position to pass in and get back |
| * @return a double value representing the parsed number, or <code>0.0</code> |
| * if the parse fails |
| * @throws NumberFormatException if the text segment could not be converted into a number |
| */ |
| public double parse(String text, int[] inOutPos) throws NumberFormatException { |
| double ret = 0.0; |
| |
| boolean gotPositivePrefix = text.startsWith(positivePrefix, inOutPos[0]); |
| boolean gotNegativePrefix = text.startsWith(negativePrefix, inOutPos[0]); |
| boolean gotPositiveSuffix = text.endsWith(positiveSuffix); |
| boolean gotNegativeSuffix = text.endsWith(negativeSuffix); |
| boolean gotPositive = gotPositivePrefix && gotPositiveSuffix; |
| boolean gotNegative = gotNegativePrefix && gotNegativeSuffix; |
| |
| // Handle conflicts where we get both patterns, which usually |
| // happens when one is a prefix of the other (such as the positive |
| // pattern having empty prefix/suffixes). |
| if (gotPositive && gotNegative) { |
| if (positivePrefix.length() > negativePrefix.length()) { |
| gotNegative = false; |
| } else if (positivePrefix.length() < negativePrefix.length()) { |
| gotPositive = false; |
| } else if (positiveSuffix.length() > negativeSuffix.length()) { |
| gotNegative = false; |
| } else if (positiveSuffix.length() < negativeSuffix.length()) { |
| gotPositive = false; |
| } else { |
| // can't tell patterns apart, must be positive |
| gotNegative = false; |
| } |
| } else if (!gotPositive && !gotNegative) { |
| throw new NumberFormatException(text |
| + " does not have either positive or negative affixes"); |
| } |
| |
| // Contains just the value to parse, stripping any prefix or suffix |
| String valueOnly = null; |
| if (gotPositive) { |
| inOutPos[0] += positivePrefix.length(); |
| valueOnly = text.substring(inOutPos[0], |
| text.length() - positiveSuffix.length()); |
| } else { |
| inOutPos[0] += negativePrefix.length(); |
| valueOnly = text.substring(inOutPos[0], |
| text.length() - negativeSuffix.length()); |
| } |
| |
| // Process digits or special values, and find decimal position. |
| if (valueOnly.equals(numberConstants.infinity())) { |
| inOutPos[0] += numberConstants.infinity().length(); |
| ret = Double.POSITIVE_INFINITY; |
| } else if (valueOnly.equals(numberConstants.notANumber())) { |
| inOutPos[0] += numberConstants.notANumber().length(); |
| ret = Double.NaN; |
| } else { |
| int[] tempPos = {0}; |
| ret = parseNumber(valueOnly, tempPos); |
| inOutPos[0] += tempPos[0]; |
| } |
| |
| // Check for suffix. |
| if (gotPositive) { |
| inOutPos[0] += positiveSuffix.length(); |
| } else if (gotNegative) { |
| inOutPos[0] += negativeSuffix.length(); |
| } |
| |
| if (gotNegative) { |
| ret = -ret; |
| } |
| |
| return ret; |
| } |
| |
| protected int getGroupingSize() { |
| return groupingSize; |
| } |
| |
| protected String getNegativePrefix() { |
| return negativePrefix; |
| } |
| |
| protected String getNegativeSuffix() { |
| return negativeSuffix; |
| } |
| |
| protected NumberConstants getNumberConstants() { |
| return numberConstants; |
| } |
| |
| protected String getPositivePrefix() { |
| return positivePrefix; |
| } |
| |
| protected String getPositiveSuffix() { |
| return positiveSuffix; |
| } |
| |
| protected boolean isDecimalSeparatorAlwaysShown() { |
| return decimalSeparatorAlwaysShown; |
| } |
| |
| /** |
| * This method formats the exponent part of a double. |
| * |
| * @param exponent exponential value |
| * @param result formatted exponential part will be append to it |
| */ |
| private void addExponentPart(int exponent, StringBuffer result) { |
| result.append(numberConstants.exponentialSymbol()); |
| |
| if (exponent < 0) { |
| exponent = -exponent; |
| result.append(numberConstants.minusSign()); |
| } |
| |
| String exponentDigits = String.valueOf(exponent); |
| int len = exponentDigits.length(); |
| for (int i = len; i < minExponentDigits; ++i) { |
| result.append(numberConstants.zeroDigit()); |
| } |
| int zeroDelta = numberConstants.zeroDigit().charAt(0) - '0'; |
| for (int i = 0; i < len; ++i) { |
| result.append((char) (exponentDigits.charAt(i) + zeroDelta)); |
| } |
| } |
| |
| /** |
| * This method return the digit that represented by current character, it |
| * could be either '0' to '9', or a locale specific digit. |
| * |
| * @param ch character that represents a digit |
| * @return the digit value |
| */ |
| private int getDigit(char ch) { |
| if ('0' <= ch && ch <= '0' + 9) { |
| return (ch - '0'); |
| } else { |
| char zeroChar = numberConstants.zeroDigit().charAt(0); |
| return ((zeroChar <= ch && ch <= zeroChar + 9) ? (ch - zeroChar) : -1); |
| } |
| } |
| |
| /** |
| * This does the work of String.valueOf(long), but given a double as input |
| * and avoiding our emulated longs. Contrasted with String.valueOf(double), |
| * it ensures (a) there will be no trailing .0, and (b) unwinds E-notation. |
| * |
| * @param number the integral value to convert |
| * @return the string representing that integer |
| */ |
| private String makeIntString(double number) { |
| String intPart = String.valueOf(number); |
| if (GWT.isScript()) { |
| return intPart; // JavaScript does the right thing for integral doubles |
| } |
| // ...but bytecode (hosted mode) does not... String.valueOf(double) will |
| // either end in .0 (non internationalized) which we don't want but is |
| // easy, or, for large numbers, it will be E-notation, which is annoying. |
| int digitLen = intPart.length(); |
| |
| if (intPart.charAt(digitLen - 2) == '.') { |
| return intPart.substring(0, digitLen - 2); |
| } |
| |
| // if we have E notation, (1) the exponent will be positive (else |
| // intValue is 0, which doesn't need E notation), and (2) there will |
| // be a radix dot (String.valueOf() isn't interationalized) |
| int radix = intPart.indexOf('.'); |
| int exp = intPart.indexOf('E'); |
| int digits = 0; |
| for (int i = exp + 1; i < intPart.length(); i++) { |
| digits = digits * 10 + (intPart.charAt(i) - '0'); |
| } |
| digits++; // exp of zero is one int digit... |
| StringBuffer newIntPart = new StringBuffer(); |
| newIntPart.append(intPart.substring(0, radix)); |
| newIntPart.append(intPart.substring(radix + 1, exp)); |
| while (newIntPart.length() < digits) { |
| newIntPart.append('0'); |
| } |
| newIntPart.setLength(digits); |
| return newIntPart.toString(); |
| } |
| |
| /** |
| * This method parses affix part of pattern. |
| * |
| * @param pattern pattern string that need to be parsed |
| * @param start start position to parse |
| * @param affix store the parsed result |
| * @param inNegativePattern true if we are parsing the negative pattern and |
| * therefore only care about the prefix and suffix |
| * @return how many characters parsed |
| */ |
| private int parseAffix(String pattern, int start, StringBuffer affix, |
| boolean inNegativePattern) { |
| affix.delete(0, affix.length()); |
| boolean inQuote = false; |
| int len = pattern.length(); |
| |
| for (int pos = start; pos < len; ++pos) { |
| char ch = pattern.charAt(pos); |
| if (ch == QUOTE) { |
| if ((pos + 1) < len && pattern.charAt(pos + 1) == QUOTE) { |
| ++pos; |
| affix.append("'"); // 'don''t' |
| } else { |
| inQuote = !inQuote; |
| } |
| continue; |
| } |
| |
| if (inQuote) { |
| affix.append(ch); |
| } else { |
| switch (ch) { |
| case PATTERN_DIGIT: |
| case PATTERN_ZERO_DIGIT: |
| case PATTERN_GROUPING_SEPARATOR: |
| case PATTERN_DECIMAL_SEPARATOR: |
| case PATTERN_SEPARATOR: |
| return pos - start; |
| case CURRENCY_SIGN: |
| isCurrencyFormat = true; |
| if ((pos + 1) < len && pattern.charAt(pos + 1) == CURRENCY_SIGN) { |
| ++pos; |
| affix.append(currencyCode); |
| } else { |
| affix.append(currencySymbol); |
| } |
| break; |
| case PATTERN_PERCENT: |
| if (!inNegativePattern) { |
| if (multiplier != 1) { |
| throw new IllegalArgumentException( |
| "Too many percent/per mille characters in pattern \"" |
| + pattern + '"'); |
| } |
| multiplier = 100; |
| } |
| affix.append(numberConstants.percent()); |
| break; |
| case PATTERN_PER_MILLE: |
| if (!inNegativePattern) { |
| if (multiplier != 1) { |
| throw new IllegalArgumentException( |
| "Too many percent/per mille characters in pattern \"" |
| + pattern + '"'); |
| } |
| multiplier = 1000; |
| } |
| affix.append(numberConstants.perMill()); |
| break; |
| case PATTERN_MINUS: |
| affix.append("-"); |
| break; |
| default: |
| affix.append(ch); |
| } |
| } |
| } |
| return len - start; |
| } |
| |
| /** |
| * This function parses a "localized" text into a <code>double</code>. It |
| * needs to handle locale specific decimal, grouping, exponent and digit. |
| * |
| * @param text the text that need to be parsed |
| * @param pos in/out parsing position. in case of failure, this shouldn't be |
| * changed |
| * @return double value, could be 0.0 if nothing can be parsed |
| */ |
| private double parseNumber(String text, int[] pos) { |
| double ret; |
| boolean sawDecimal = false; |
| boolean sawExponent = false; |
| boolean sawDigit = false; |
| int scale = 1; |
| String decimal = isCurrencyFormat ? numberConstants.monetarySeparator() |
| : numberConstants.decimalSeparator(); |
| String grouping = isCurrencyFormat |
| ? numberConstants.monetaryGroupingSeparator() |
| : numberConstants.groupingSeparator(); |
| String exponentChar = numberConstants.exponentialSymbol(); |
| |
| StringBuffer normalizedText = new StringBuffer(); |
| for (; pos[0] < text.length(); ++pos[0]) { |
| char ch = text.charAt(pos[0]); |
| int digit = getDigit(ch); |
| if (digit >= 0 && digit <= 9) { |
| normalizedText.append((char) (digit + '0')); |
| sawDigit = true; |
| } else if (ch == decimal.charAt(0)) { |
| if (sawDecimal || sawExponent) { |
| break; |
| } |
| normalizedText.append('.'); |
| sawDecimal = true; |
| } else if (ch == grouping.charAt(0)) { |
| if (sawDecimal || sawExponent) { |
| break; |
| } |
| continue; |
| } else if (ch == exponentChar.charAt(0)) { |
| if (sawExponent) { |
| break; |
| } |
| normalizedText.append('E'); |
| sawExponent = true; |
| } else if (ch == '+' || ch == '-') { |
| normalizedText.append(ch); |
| } else if (ch == numberConstants.percent().charAt(0)) { |
| if (scale != 1) { |
| break; |
| } |
| scale = 100; |
| if (sawDigit) { |
| ++pos[0]; |
| break; |
| } |
| } else if (ch == numberConstants.perMill().charAt(0)) { |
| if (scale != 1) { |
| break; |
| } |
| scale = 1000; |
| if (sawDigit) { |
| ++pos[0]; |
| break; |
| } |
| } else { |
| break; |
| } |
| } |
| |
| // parseDouble could throw NumberFormatException, rethrow with correct text. |
| try { |
| ret = Double.parseDouble(normalizedText.toString()); |
| } catch (NumberFormatException e) { |
| throw new NumberFormatException(text); |
| } |
| ret = ret / scale; |
| return ret; |
| } |
| |
| /** |
| * Method parses provided pattern, result is stored in member variables. |
| * |
| * @param pattern |
| */ |
| private void parsePattern(String pattern) { |
| int pos = 0; |
| StringBuffer affix = new StringBuffer(); |
| |
| pos += parseAffix(pattern, pos, affix, false); |
| positivePrefix = affix.toString(); |
| pos += parseTrunk(pattern, pos, false); |
| pos += parseAffix(pattern, pos, affix, false); |
| positiveSuffix = affix.toString(); |
| |
| if (pos < pattern.length() && pattern.charAt(pos) == PATTERN_SEPARATOR) { |
| ++pos; |
| pos += parseAffix(pattern, pos, affix, true); |
| negativePrefix = affix.toString(); |
| // the negative pattern is only used for prefix/suffix |
| pos += parseTrunk(pattern, pos, true); |
| pos += parseAffix(pattern, pos, affix, true); |
| negativeSuffix = affix.toString(); |
| } else { |
| negativePrefix = numberConstants.minusSign() + positivePrefix; |
| negativeSuffix = positiveSuffix; |
| } |
| } |
| |
| /** |
| * This method parses the trunk part of a pattern. |
| * |
| * @param pattern pattern string that need to be parsed |
| * @param start where parse started |
| * @param ignorePattern true if we are only parsing this for length |
| * and correctness, such as in the negative portion of the pattern |
| * @return how many characters parsed |
| */ |
| private int parseTrunk(String pattern, int start, boolean ignorePattern) { |
| int decimalPos = -1; |
| int digitLeftCount = 0, zeroDigitCount = 0, digitRightCount = 0; |
| byte groupingCount = -1; |
| |
| int len = pattern.length(); |
| int pos = start; |
| boolean loop = true; |
| for (; (pos < len) && loop; ++pos) { |
| char ch = pattern.charAt(pos); |
| switch (ch) { |
| case PATTERN_DIGIT: |
| if (zeroDigitCount > 0) { |
| ++digitRightCount; |
| } else { |
| ++digitLeftCount; |
| } |
| if (groupingCount >= 0 && decimalPos < 0) { |
| ++groupingCount; |
| } |
| break; |
| case PATTERN_ZERO_DIGIT: |
| if (digitRightCount > 0) { |
| throw new IllegalArgumentException("Unexpected '0' in pattern \"" |
| + pattern + '"'); |
| } |
| ++zeroDigitCount; |
| if (groupingCount >= 0 && decimalPos < 0) { |
| ++groupingCount; |
| } |
| break; |
| case PATTERN_GROUPING_SEPARATOR: |
| groupingCount = 0; |
| break; |
| case PATTERN_DECIMAL_SEPARATOR: |
| if (decimalPos >= 0) { |
| throw new IllegalArgumentException( |
| "Multiple decimal separators in pattern \"" + pattern + '"'); |
| } |
| decimalPos = digitLeftCount + zeroDigitCount + digitRightCount; |
| break; |
| case PATTERN_EXPONENT: |
| if (!ignorePattern) { |
| if (useExponentialNotation) { |
| throw new IllegalArgumentException("Multiple exponential " |
| + "symbols in pattern \"" + pattern + '"'); |
| } |
| useExponentialNotation = true; |
| minExponentDigits = 0; |
| } |
| |
| // Use lookahead to parse out the exponential part |
| // of the pattern, then jump into phase 2. |
| while ((pos + 1) < len |
| && pattern.charAt(pos + 1) == PATTERN_ZERO_DIGIT) { |
| ++pos; |
| if (!ignorePattern) { |
| ++minExponentDigits; |
| } |
| } |
| |
| if (!ignorePattern && (digitLeftCount + zeroDigitCount) < 1 |
| || minExponentDigits < 1) { |
| throw new IllegalArgumentException("Malformed exponential " |
| + "pattern \"" + pattern + '"'); |
| } |
| loop = false; |
| break; |
| default: |
| --pos; |
| loop = false; |
| break; |
| } |
| } |
| |
| if (zeroDigitCount == 0 && digitLeftCount > 0 && decimalPos >= 0) { |
| // Handle "###.###" and "###." and ".###". |
| int n = decimalPos; |
| if (n == 0) { // Handle ".###" |
| ++n; |
| } |
| digitRightCount = digitLeftCount - n; |
| digitLeftCount = n - 1; |
| zeroDigitCount = 1; |
| } |
| |
| // Do syntax checking on the digits. |
| if ((decimalPos < 0 && digitRightCount > 0) |
| || (decimalPos >= 0 && (decimalPos < digitLeftCount || decimalPos > (digitLeftCount + zeroDigitCount))) |
| || groupingCount == 0) { |
| throw new IllegalArgumentException("Malformed pattern \"" + pattern + '"'); |
| } |
| |
| if (ignorePattern) { |
| return pos - start; |
| } |
| |
| int totalDigits = digitLeftCount + zeroDigitCount + digitRightCount; |
| |
| maximumFractionDigits = (decimalPos >= 0 ? (totalDigits - decimalPos) : 0); |
| if (decimalPos >= 0) { |
| minimumFractionDigits = digitLeftCount + zeroDigitCount - decimalPos; |
| if (minimumFractionDigits < 0) { |
| minimumFractionDigits = 0; |
| } |
| } |
| |
| /* |
| * 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 : totalDigits; |
| minimumIntegerDigits = effectiveDecimalPos - digitLeftCount; |
| if (useExponentialNotation) { |
| maximumIntegerDigits = digitLeftCount + minimumIntegerDigits; |
| |
| // In exponential display, integer part can't be empty. |
| if (maximumFractionDigits == 0 && minimumIntegerDigits == 0) { |
| minimumIntegerDigits = 1; |
| } |
| } |
| |
| this.groupingSize = (groupingCount > 0) ? groupingCount : 0; |
| decimalSeparatorAlwaysShown = (decimalPos == 0 || decimalPos == totalDigits); |
| |
| return pos - start; |
| } |
| |
| /** |
| * This method formats a <code>double</code> in exponential format. |
| * |
| * @param number value need to be formated |
| * @param result where the formatted string goes |
| */ |
| private void subformatExponential(double number, StringBuffer result) { |
| if (number == 0.0) { |
| subformatFixed(number, result, minimumIntegerDigits); |
| addExponentPart(0, result); |
| return; |
| } |
| |
| int exponent = (int) Math.floor(Math.log(number) / Math.log(10)); |
| number /= Math.pow(10, exponent); |
| |
| int minIntDigits = minimumIntegerDigits; |
| if (maximumIntegerDigits > 1 && maximumIntegerDigits > minimumIntegerDigits) { |
| // A repeating range is defined; adjust to it as follows. |
| // If repeat == 3, we have 6,5,4=>3; 3,2,1=>0; 0,-1,-2=>-3; |
| // -3,-4,-5=>-6, etc. This takes into account that the |
| // exponent we have here is off by one from what we expect; |
| // it is for the format 0.MMMMMx10^n. |
| while ((exponent % maximumIntegerDigits) != 0) { |
| number *= 10; |
| exponent--; |
| } |
| minIntDigits = 1; |
| } else { |
| // No repeating range is defined; use minimum integer digits. |
| if (minimumIntegerDigits < 1) { |
| exponent++; |
| number /= 10; |
| } else { |
| for (int i = 1; i < minimumIntegerDigits; i++) { |
| exponent--; |
| number *= 10; |
| } |
| } |
| } |
| |
| subformatFixed(number, result, minIntDigits); |
| addExponentPart(exponent, result); |
| } |
| |
| /** |
| * This method formats a <code>double</code> into a fractional |
| * representation. |
| * |
| * @param number value need to be formated |
| * @param result result will be written here |
| * @param minIntDigits minimum integer digits |
| */ |
| private void subformatFixed(double number, StringBuffer result, |
| int minIntDigits) { |
| double power = Math.pow(10, maximumFractionDigits); |
| // Use 3 extra digits to allow us to do our own rounding since |
| // Java rounds up on .5 whereas some browsers might use 'round to even' |
| // or other rules. |
| |
| // There are cases where more digits would be required to get |
| // guaranteed results, but this at least makes such cases rarer. |
| String fixedString = toFixed(number, maximumFractionDigits + 3); |
| |
| double intValue = 0, fracValue = 0; |
| int exponentIndex = fixedString.indexOf('e'); |
| if (exponentIndex != -1) { |
| // Large numbers may be returned in exponential notation: such numbers |
| // are integers anyway |
| intValue = Math.floor(number); |
| } else { |
| int decimalIndex = fixedString.indexOf('.'); |
| int len = fixedString.length(); |
| if (decimalIndex == -1) { |
| decimalIndex = len; |
| } |
| if (decimalIndex > 0) { |
| intValue = Double.parseDouble(fixedString.substring(0, decimalIndex)); |
| } |
| if (decimalIndex < len - 1) { |
| fracValue = Double.parseDouble(fixedString.substring(decimalIndex + 1)); |
| fracValue = (((int) fracValue) + 500) / 1000; |
| if (fracValue >= power) { |
| fracValue -= power; |
| intValue++; |
| } |
| } |
| } |
| |
| boolean fractionPresent = (minimumFractionDigits > 0) || (fracValue > 0); |
| |
| String intPart = makeIntString(intValue); |
| String grouping = isCurrencyFormat |
| ? numberConstants.monetaryGroupingSeparator() |
| : numberConstants.groupingSeparator(); |
| String decimal = isCurrencyFormat ? numberConstants.monetarySeparator() |
| : numberConstants.decimalSeparator(); |
| |
| int zeroDelta = numberConstants.zeroDigit().charAt(0) - '0'; |
| int digitLen = intPart.length(); |
| |
| if (intValue > 0 || minIntDigits > 0) { |
| for (int i = digitLen; i < minIntDigits; i++) { |
| result.append(numberConstants.zeroDigit()); |
| } |
| |
| for (int i = 0; i < digitLen; i++) { |
| result.append((char) (intPart.charAt(i) + zeroDelta)); |
| |
| if (digitLen - i > 1 && groupingSize > 0 |
| && ((digitLen - i) % groupingSize == 1)) { |
| result.append(grouping); |
| } |
| } |
| } else if (!fractionPresent) { |
| // If there is no fraction present, and we haven't printed any |
| // integer digits, then print a zero. |
| result.append(numberConstants.zeroDigit()); |
| } |
| |
| // Output the decimal separator if we always do so. |
| if (decimalSeparatorAlwaysShown || fractionPresent) { |
| result.append(decimal); |
| } |
| |
| // To make sure it lead zero will be kept. |
| String fracPart = makeIntString(Math.floor(fracValue + power + 0.5d)); |
| int fracLen = fracPart.length(); |
| while (fracPart.charAt(fracLen - 1) == '0' && fracLen > minimumFractionDigits + 1) { |
| fracLen--; |
| } |
| |
| for (int i = 1; i < fracLen; i++) { |
| result.append((char) (fracPart.charAt(i) + zeroDelta)); |
| } |
| } |
| } |