package com.ibm.icu.impl.number;
public interface AffixPatternProvider {
- public static final class Flags {
- public static final int PLURAL_MASK = 0xff;
- public static final int PREFIX = 0x100;
- public static final int NEGATIVE_SUBPATTERN = 0x200;
- public static final int PADDING = 0x400;
- }
+ public static final class Flags {
+ public static final int PLURAL_MASK = 0xff;
+ public static final int PREFIX = 0x100;
+ public static final int NEGATIVE_SUBPATTERN = 0x200;
+ public static final int PADDING = 0x400;
+ }
- public char charAt(int flags, int i);
+ public char charAt(int flags, int i);
- public int length(int flags);
+ public int length(int flags);
- public boolean hasCurrencySign();
+ public boolean hasCurrencySign();
- public boolean positiveHasPlusSign();
+ public boolean positiveHasPlusSign();
- public boolean hasNegativeSubpattern();
+ public boolean hasNegativeSubpattern();
- public boolean negativeHasMinusSign();
+ public boolean negativeHasMinusSign();
- public boolean containsSymbolType(int type);
+ public boolean containsSymbolType(int type);
}
* format pattern. For example:
*
* <table>
- * <tr><th>Affix Pattern</th><th>Example Unescaped (Formatted) String</th></tr>
- * <tr><td>abc</td><td>abc</td></tr>
- * <tr><td>ab-</td><td>ab−</td></tr>
- * <tr><td>ab'-'</td><td>ab-</td></tr>
- * <tr><td>ab''</td><td>ab'</td></tr>
+ * <tr>
+ * <th>Affix Pattern</th>
+ * <th>Example Unescaped (Formatted) String</th>
+ * </tr>
+ * <tr>
+ * <td>abc</td>
+ * <td>abc</td>
+ * </tr>
+ * <tr>
+ * <td>ab-</td>
+ * <td>ab−</td>
+ * </tr>
+ * <tr>
+ * <td>ab'-'</td>
+ * <td>ab-</td>
+ * </tr>
+ * <tr>
+ * <td>ab''</td>
+ * <td>ab'</td>
+ * </tr>
* </table>
*
- * To manually iterate over tokens in a literal string, use the following pattern, which is designed
- * to be efficient.
+ * To manually iterate over tokens in a literal string, use the following pattern, which is designed to
+ * be efficient.
*
* <pre>
* long tag = 0L;
* while (AffixPatternUtils.hasNext(tag, patternString)) {
- * tag = AffixPatternUtils.nextToken(tag, patternString);
- * int typeOrCp = AffixPatternUtils.getTypeOrCp(tag);
- * switch (typeOrCp) {
+ * tag = AffixPatternUtils.nextToken(tag, patternString);
+ * int typeOrCp = AffixPatternUtils.getTypeOrCp(tag);
+ * switch (typeOrCp) {
* case AffixPatternUtils.TYPE_MINUS_SIGN:
- * // Current token is a minus sign.
- * break;
+ * // Current token is a minus sign.
+ * break;
* case AffixPatternUtils.TYPE_PLUS_SIGN:
- * // Current token is a plus sign.
- * break;
+ * // Current token is a plus sign.
+ * break;
* case AffixPatternUtils.TYPE_PERCENT:
- * // Current token is a percent sign.
- * break;
+ * // Current token is a percent sign.
+ * break;
* // ... other types ...
* default:
- * // Current token is an arbitrary code point.
- * // The variable typeOrCp is the code point.
- * break;
- * }
+ * // Current token is an arbitrary code point.
+ * // The variable typeOrCp is the code point.
+ * break;
+ * }
* }
* </pre>
*/
public class AffixUtils {
- private static final int STATE_BASE = 0;
- private static final int STATE_FIRST_QUOTE = 1;
- private static final int STATE_INSIDE_QUOTE = 2;
- private static final int STATE_AFTER_QUOTE = 3;
- private static final int STATE_FIRST_CURR = 4;
- private static final int STATE_SECOND_CURR = 5;
- private static final int STATE_THIRD_CURR = 6;
- private static final int STATE_FOURTH_CURR = 7;
- private static final int STATE_FIFTH_CURR = 8;
- private static final int STATE_OVERFLOW_CURR = 9;
-
- /** Represents a literal character; the value is stored in the code point field. */
- private static final int TYPE_CODEPOINT = 0;
-
- /** Represents a minus sign symbol '-'. */
- public static final int TYPE_MINUS_SIGN = -1;
-
- /** Represents a plus sign symbol '+'. */
- public static final int TYPE_PLUS_SIGN = -2;
-
- /** Represents a percent sign symbol '%'. */
- public static final int TYPE_PERCENT = -3;
-
- /** Represents a permille sign symbol '‰'. */
- public static final int TYPE_PERMILLE = -4;
-
- /** Represents a single currency symbol '¤'. */
- public static final int TYPE_CURRENCY_SINGLE = -5;
-
- /** Represents a double currency symbol '¤¤'. */
- public static final int TYPE_CURRENCY_DOUBLE = -6;
-
- /** Represents a triple currency symbol '¤¤¤'. */
- public static final int TYPE_CURRENCY_TRIPLE = -7;
-
- /** Represents a quadruple currency symbol '¤¤¤¤'. */
- public static final int TYPE_CURRENCY_QUAD = -8;
-
- /** Represents a quintuple currency symbol '¤¤¤¤¤'. */
- public static final int TYPE_CURRENCY_QUINT = -9;
-
- /** Represents a sequence of six or more currency symbols. */
- public static final int TYPE_CURRENCY_OVERFLOW = -15;
-
- public static interface SymbolProvider {
- public CharSequence getSymbol(int type);
- }
-
- /**
- * Estimates the number of code points present in an unescaped version of the affix pattern string
- * (one that would be returned by {@link #unescape}), assuming that all interpolated symbols
- * consume one code point and that currencies consume as many code points as their symbol width.
- * Used for computing padding width.
- *
- * @param patternString The original string whose width will be estimated.
- * @return The length of the unescaped string.
- */
- public static int estimateLength(CharSequence patternString) {
- if (patternString == null) return 0;
- int state = STATE_BASE;
- int offset = 0;
- int length = 0;
- for (; offset < patternString.length(); ) {
- int cp = Character.codePointAt(patternString, offset);
-
- switch (state) {
- case STATE_BASE:
- if (cp == '\'') {
- // First quote
- state = STATE_FIRST_QUOTE;
- } else {
- // Unquoted symbol
- length++;
- }
- break;
+ private static final int STATE_BASE = 0;
+ private static final int STATE_FIRST_QUOTE = 1;
+ private static final int STATE_INSIDE_QUOTE = 2;
+ private static final int STATE_AFTER_QUOTE = 3;
+ private static final int STATE_FIRST_CURR = 4;
+ private static final int STATE_SECOND_CURR = 5;
+ private static final int STATE_THIRD_CURR = 6;
+ private static final int STATE_FOURTH_CURR = 7;
+ private static final int STATE_FIFTH_CURR = 8;
+ private static final int STATE_OVERFLOW_CURR = 9;
+
+ /** Represents a literal character; the value is stored in the code point field. */
+ private static final int TYPE_CODEPOINT = 0;
+
+ /** Represents a minus sign symbol '-'. */
+ public static final int TYPE_MINUS_SIGN = -1;
+
+ /** Represents a plus sign symbol '+'. */
+ public static final int TYPE_PLUS_SIGN = -2;
+
+ /** Represents a percent sign symbol '%'. */
+ public static final int TYPE_PERCENT = -3;
+
+ /** Represents a permille sign symbol '‰'. */
+ public static final int TYPE_PERMILLE = -4;
+
+ /** Represents a single currency symbol '¤'. */
+ public static final int TYPE_CURRENCY_SINGLE = -5;
+
+ /** Represents a double currency symbol '¤¤'. */
+ public static final int TYPE_CURRENCY_DOUBLE = -6;
+
+ /** Represents a triple currency symbol '¤¤¤'. */
+ public static final int TYPE_CURRENCY_TRIPLE = -7;
+
+ /** Represents a quadruple currency symbol '¤¤¤¤'. */
+ public static final int TYPE_CURRENCY_QUAD = -8;
+
+ /** Represents a quintuple currency symbol '¤¤¤¤¤'. */
+ public static final int TYPE_CURRENCY_QUINT = -9;
+
+ /** Represents a sequence of six or more currency symbols. */
+ public static final int TYPE_CURRENCY_OVERFLOW = -15;
+
+ public static interface SymbolProvider {
+ public CharSequence getSymbol(int type);
+ }
+
+ /**
+ * Estimates the number of code points present in an unescaped version of the affix pattern string
+ * (one that would be returned by {@link #unescape}), assuming that all interpolated symbols consume
+ * one code point and that currencies consume as many code points as their symbol width. Used for
+ * computing padding width.
+ *
+ * @param patternString
+ * The original string whose width will be estimated.
+ * @return The length of the unescaped string.
+ */
+ public static int estimateLength(CharSequence patternString) {
+ if (patternString == null)
+ return 0;
+ int state = STATE_BASE;
+ int offset = 0;
+ int length = 0;
+ for (; offset < patternString.length();) {
+ int cp = Character.codePointAt(patternString, offset);
+
+ switch (state) {
+ case STATE_BASE:
+ if (cp == '\'') {
+ // First quote
+ state = STATE_FIRST_QUOTE;
+ } else {
+ // Unquoted symbol
+ length++;
+ }
+ break;
+ case STATE_FIRST_QUOTE:
+ if (cp == '\'') {
+ // Repeated quote
+ length++;
+ state = STATE_BASE;
+ } else {
+ // Quoted code point
+ length++;
+ state = STATE_INSIDE_QUOTE;
+ }
+ break;
+ case STATE_INSIDE_QUOTE:
+ if (cp == '\'') {
+ // End of quoted sequence
+ state = STATE_AFTER_QUOTE;
+ } else {
+ // Quoted code point
+ length++;
+ }
+ break;
+ case STATE_AFTER_QUOTE:
+ if (cp == '\'') {
+ // Double quote inside of quoted sequence
+ length++;
+ state = STATE_INSIDE_QUOTE;
+ } else {
+ // Unquoted symbol
+ length++;
+ }
+ break;
+ default:
+ throw new AssertionError();
+ }
+
+ offset += Character.charCount(cp);
+ }
+
+ switch (state) {
case STATE_FIRST_QUOTE:
- if (cp == '\'') {
- // Repeated quote
- length++;
- state = STATE_BASE;
- } else {
- // Quoted code point
- length++;
- state = STATE_INSIDE_QUOTE;
- }
- break;
case STATE_INSIDE_QUOTE:
- if (cp == '\'') {
- // End of quoted sequence
- state = STATE_AFTER_QUOTE;
- } else {
- // Quoted code point
- length++;
- }
- break;
- case STATE_AFTER_QUOTE:
- if (cp == '\'') {
- // Double quote inside of quoted sequence
- length++;
- state = STATE_INSIDE_QUOTE;
- } else {
- // Unquoted symbol
- length++;
- }
- break;
+ throw new IllegalArgumentException("Unterminated quote: \"" + patternString + "\"");
default:
- throw new AssertionError();
- }
+ break;
+ }
- offset += Character.charCount(cp);
+ return length;
}
- switch (state) {
- case STATE_FIRST_QUOTE:
- case STATE_INSIDE_QUOTE:
- throw new IllegalArgumentException("Unterminated quote: \"" + patternString + "\"");
- default:
- break;
- }
+ /**
+ * Takes a string and escapes (quotes) characters that have special meaning in the affix pattern
+ * syntax. This function does not reverse-lookup symbols.
+ *
+ * <p>
+ * Example input: "-$x"; example output: "'-'$x"
+ *
+ * @param input
+ * The string to be escaped.
+ * @param output
+ * The string builder to which to append the escaped string.
+ * @return The number of chars (UTF-16 code units) appended to the output.
+ */
+ public static int escape(CharSequence input, StringBuilder output) {
+ if (input == null)
+ return 0;
+ int state = STATE_BASE;
+ int offset = 0;
+ int startLength = output.length();
+ for (; offset < input.length();) {
+ int cp = Character.codePointAt(input, offset);
+
+ switch (cp) {
+ case '\'':
+ output.append("''");
+ break;
- return length;
- }
-
- /**
- * Takes a string and escapes (quotes) characters that have special meaning in the affix pattern
- * syntax. This function does not reverse-lookup symbols.
- *
- * <p>Example input: "-$x"; example output: "'-'$x"
- *
- * @param input The string to be escaped.
- * @param output The string builder to which to append the escaped string.
- * @return The number of chars (UTF-16 code units) appended to the output.
- */
- public static int escape(CharSequence input, StringBuilder output) {
- if (input == null) return 0;
- int state = STATE_BASE;
- int offset = 0;
- int startLength = output.length();
- for (; offset < input.length(); ) {
- int cp = Character.codePointAt(input, offset);
-
- switch (cp) {
- case '\'':
- output.append("''");
- break;
-
- case '-':
- case '+':
- case '%':
- case '‰':
- case '¤':
- if (state == STATE_BASE) {
- output.append('\'');
- output.appendCodePoint(cp);
- state = STATE_INSIDE_QUOTE;
- } else {
- output.appendCodePoint(cp);
- }
- break;
+ case '-':
+ case '+':
+ case '%':
+ case '‰':
+ case '¤':
+ if (state == STATE_BASE) {
+ output.append('\'');
+ output.appendCodePoint(cp);
+ state = STATE_INSIDE_QUOTE;
+ } else {
+ output.appendCodePoint(cp);
+ }
+ break;
- default:
- if (state == STATE_INSIDE_QUOTE) {
+ default:
+ if (state == STATE_INSIDE_QUOTE) {
+ output.append('\'');
+ output.appendCodePoint(cp);
+ state = STATE_BASE;
+ } else {
+ output.appendCodePoint(cp);
+ }
+ break;
+ }
+ offset += Character.charCount(cp);
+ }
+
+ if (state == STATE_INSIDE_QUOTE) {
output.append('\'');
- output.appendCodePoint(cp);
- state = STATE_BASE;
- } else {
- output.appendCodePoint(cp);
- }
- break;
- }
- offset += Character.charCount(cp);
+ }
+
+ return output.length() - startLength;
}
- if (state == STATE_INSIDE_QUOTE) {
- output.append('\'');
+ /** Version of {@link #escape} that returns a String, or null if input is null. */
+ public static String escape(CharSequence input) {
+ if (input == null)
+ return null;
+ StringBuilder sb = new StringBuilder();
+ escape(input, sb);
+ return sb.toString();
}
- return output.length() - startLength;
- }
-
- /** Version of {@link #escape} that returns a String, or null if input is null. */
- public static String escape(CharSequence input) {
- if (input == null) return null;
- StringBuilder sb = new StringBuilder();
- escape(input, sb);
- return sb.toString();
- }
-
- public static final NumberFormat.Field getFieldForType(int type) {
- switch (type) {
- case TYPE_MINUS_SIGN:
- return NumberFormat.Field.SIGN;
- case TYPE_PLUS_SIGN:
- return NumberFormat.Field.SIGN;
- case TYPE_PERCENT:
- return NumberFormat.Field.PERCENT;
- case TYPE_PERMILLE:
- return NumberFormat.Field.PERMILLE;
- case TYPE_CURRENCY_SINGLE:
- return NumberFormat.Field.CURRENCY;
- case TYPE_CURRENCY_DOUBLE:
- return NumberFormat.Field.CURRENCY;
- case TYPE_CURRENCY_TRIPLE:
- return NumberFormat.Field.CURRENCY;
- case TYPE_CURRENCY_QUAD:
- return NumberFormat.Field.CURRENCY;
- case TYPE_CURRENCY_QUINT:
- return NumberFormat.Field.CURRENCY;
- case TYPE_CURRENCY_OVERFLOW:
- return NumberFormat.Field.CURRENCY;
- default:
- throw new AssertionError();
+ public static final NumberFormat.Field getFieldForType(int type) {
+ switch (type) {
+ case TYPE_MINUS_SIGN:
+ return NumberFormat.Field.SIGN;
+ case TYPE_PLUS_SIGN:
+ return NumberFormat.Field.SIGN;
+ case TYPE_PERCENT:
+ return NumberFormat.Field.PERCENT;
+ case TYPE_PERMILLE:
+ return NumberFormat.Field.PERMILLE;
+ case TYPE_CURRENCY_SINGLE:
+ return NumberFormat.Field.CURRENCY;
+ case TYPE_CURRENCY_DOUBLE:
+ return NumberFormat.Field.CURRENCY;
+ case TYPE_CURRENCY_TRIPLE:
+ return NumberFormat.Field.CURRENCY;
+ case TYPE_CURRENCY_QUAD:
+ return NumberFormat.Field.CURRENCY;
+ case TYPE_CURRENCY_QUINT:
+ return NumberFormat.Field.CURRENCY;
+ case TYPE_CURRENCY_OVERFLOW:
+ return NumberFormat.Field.CURRENCY;
+ default:
+ throw new AssertionError();
+ }
}
- }
-
- /**
- * Executes the unescape state machine. Replaces the unquoted characters "-", "+", "%", "‰", and
- * "¤" with the corresponding symbols provided by the {@link SymbolProvider}, and inserts the
- * result into the NumberStringBuilder at the requested location.
- *
- * <p>Example input: "'-'¤x"; example output: "-$x"
- *
- * @param affixPattern The original string to be unescaped.
- * @param output The NumberStringBuilder to mutate with the result.
- * @param position The index into the NumberStringBuilder to insert the the string.
- * @param provider An object to generate locale symbols.
- * @return The length of the string added to affixPattern.
- */
- public static int unescape(
- CharSequence affixPattern,
- NumberStringBuilder output,
- int position,
- SymbolProvider provider) {
- assert affixPattern != null;
- int length = 0;
- long tag = 0L;
- while (hasNext(tag, affixPattern)) {
- tag = nextToken(tag, affixPattern);
- int typeOrCp = getTypeOrCp(tag);
- if (typeOrCp == TYPE_CURRENCY_OVERFLOW) {
- // Don't go to the provider for this special case
- length += output.insertCodePoint(position + length, 0xFFFD, NumberFormat.Field.CURRENCY);
- } else if (typeOrCp < 0) {
- length += output.insert(position + length, provider.getSymbol(typeOrCp), getFieldForType(typeOrCp));
- } else {
- length += output.insertCodePoint(position + length, typeOrCp, null);
- }
+
+ /**
+ * Executes the unescape state machine. Replaces the unquoted characters "-", "+", "%", "‰", and "¤"
+ * with the corresponding symbols provided by the {@link SymbolProvider}, and inserts the result into
+ * the NumberStringBuilder at the requested location.
+ *
+ * <p>
+ * Example input: "'-'¤x"; example output: "-$x"
+ *
+ * @param affixPattern
+ * The original string to be unescaped.
+ * @param output
+ * The NumberStringBuilder to mutate with the result.
+ * @param position
+ * The index into the NumberStringBuilder to insert the the string.
+ * @param provider
+ * An object to generate locale symbols.
+ * @return The length of the string added to affixPattern.
+ */
+ public static int unescape(
+ CharSequence affixPattern,
+ NumberStringBuilder output,
+ int position,
+ SymbolProvider provider) {
+ assert affixPattern != null;
+ int length = 0;
+ long tag = 0L;
+ while (hasNext(tag, affixPattern)) {
+ tag = nextToken(tag, affixPattern);
+ int typeOrCp = getTypeOrCp(tag);
+ if (typeOrCp == TYPE_CURRENCY_OVERFLOW) {
+ // Don't go to the provider for this special case
+ length += output.insertCodePoint(position + length, 0xFFFD, NumberFormat.Field.CURRENCY);
+ } else if (typeOrCp < 0) {
+ length += output.insert(position + length,
+ provider.getSymbol(typeOrCp),
+ getFieldForType(typeOrCp));
+ } else {
+ length += output.insertCodePoint(position + length, typeOrCp, null);
+ }
+ }
+ return length;
}
- return length;
- }
-
- /**
- * Sames as {@link #unescape}, but only calculates the code point count. More efficient than {@link #unescape}
- * if you only need the length but not the string itself.
- *
- * @param affixPattern The original string to be unescaped.
- * @param provider An object to generate locale symbols.
- * @return The number of code points in the unescaped string.
- */
- public static int unescapedCodePointCount(CharSequence affixPattern, SymbolProvider provider) {
- int length = 0;
- long tag = 0L;
- while (hasNext(tag, affixPattern)) {
- tag = nextToken(tag, affixPattern);
- int typeOrCp = getTypeOrCp(tag);
- if (typeOrCp == TYPE_CURRENCY_OVERFLOW) {
- length += 1;
- } else if (typeOrCp < 0) {
- CharSequence symbol = provider.getSymbol(typeOrCp);
- length += Character.codePointCount(symbol, 0, symbol.length());
- } else {
- length += 1;
- }
+
+ /**
+ * Sames as {@link #unescape}, but only calculates the code point count. More efficient than
+ * {@link #unescape} if you only need the length but not the string itself.
+ *
+ * @param affixPattern
+ * The original string to be unescaped.
+ * @param provider
+ * An object to generate locale symbols.
+ * @return The number of code points in the unescaped string.
+ */
+ public static int unescapedCodePointCount(CharSequence affixPattern, SymbolProvider provider) {
+ int length = 0;
+ long tag = 0L;
+ while (hasNext(tag, affixPattern)) {
+ tag = nextToken(tag, affixPattern);
+ int typeOrCp = getTypeOrCp(tag);
+ if (typeOrCp == TYPE_CURRENCY_OVERFLOW) {
+ length += 1;
+ } else if (typeOrCp < 0) {
+ CharSequence symbol = provider.getSymbol(typeOrCp);
+ length += Character.codePointCount(symbol, 0, symbol.length());
+ } else {
+ length += 1;
+ }
+ }
+ return length;
}
- return length;
- }
-
- /**
- * Checks whether the given affix pattern contains at least one token of the given type, which is
- * one of the constants "TYPE_" in {@link AffixUtils}.
- *
- * @param affixPattern The affix pattern to check.
- * @param type The token type.
- * @return true if the affix pattern contains the given token type; false otherwise.
- */
- public static boolean containsType(CharSequence affixPattern, int type) {
- if (affixPattern == null || affixPattern.length() == 0) {
+
+ /**
+ * Checks whether the given affix pattern contains at least one token of the given type, which is one
+ * of the constants "TYPE_" in {@link AffixUtils}.
+ *
+ * @param affixPattern
+ * The affix pattern to check.
+ * @param type
+ * The token type.
+ * @return true if the affix pattern contains the given token type; false otherwise.
+ */
+ public static boolean containsType(CharSequence affixPattern, int type) {
+ if (affixPattern == null || affixPattern.length() == 0) {
+ return false;
+ }
+ long tag = 0L;
+ while (hasNext(tag, affixPattern)) {
+ tag = nextToken(tag, affixPattern);
+ if (getTypeOrCp(tag) == type) {
+ return true;
+ }
+ }
return false;
}
- long tag = 0L;
- while (hasNext(tag, affixPattern)) {
- tag = nextToken(tag, affixPattern);
- if (getTypeOrCp(tag) == type) {
- return true;
- }
+
+ /**
+ * Checks whether the specified affix pattern has any unquoted currency symbols ("¤").
+ *
+ * @param affixPattern
+ * The string to check for currency symbols.
+ * @return true if the literal has at least one unquoted currency symbol; false otherwise.
+ */
+ public static boolean hasCurrencySymbols(CharSequence affixPattern) {
+ if (affixPattern == null || affixPattern.length() == 0)
+ return false;
+ long tag = 0L;
+ while (hasNext(tag, affixPattern)) {
+ tag = nextToken(tag, affixPattern);
+ int typeOrCp = getTypeOrCp(tag);
+ if (typeOrCp < 0 && getFieldForType(typeOrCp) == NumberFormat.Field.CURRENCY) {
+ return true;
+ }
+ }
+ return false;
}
- return false;
- }
-
- /**
- * Checks whether the specified affix pattern has any unquoted currency symbols ("¤").
- *
- * @param affixPattern The string to check for currency symbols.
- * @return true if the literal has at least one unquoted currency symbol; false otherwise.
- */
- public static boolean hasCurrencySymbols(CharSequence affixPattern) {
- if (affixPattern == null || affixPattern.length() == 0) return false;
- long tag = 0L;
- while (hasNext(tag, affixPattern)) {
- tag = nextToken(tag, affixPattern);
- int typeOrCp = getTypeOrCp(tag);
- if (typeOrCp < 0 && getFieldForType(typeOrCp) == NumberFormat.Field.CURRENCY) {
- return true;
- }
+
+ /**
+ * Replaces all occurrences of tokens with the given type with the given replacement char.
+ *
+ * @param affixPattern
+ * The source affix pattern (does not get modified).
+ * @param type
+ * The token type.
+ * @param replacementChar
+ * The char to substitute in place of chars of the given token type.
+ * @return A string containing the new affix pattern.
+ */
+ public static String replaceType(CharSequence affixPattern, int type, char replacementChar) {
+ if (affixPattern == null || affixPattern.length() == 0)
+ return "";
+ char[] chars = affixPattern.toString().toCharArray();
+ long tag = 0L;
+ while (hasNext(tag, affixPattern)) {
+ tag = nextToken(tag, affixPattern);
+ if (getTypeOrCp(tag) == type) {
+ int offset = getOffset(tag);
+ chars[offset - 1] = replacementChar;
+ }
+ }
+ return new String(chars);
}
- return false;
- }
-
- /**
- * Replaces all occurrences of tokens with the given type with the given replacement char.
- *
- * @param affixPattern The source affix pattern (does not get modified).
- * @param type The token type.
- * @param replacementChar The char to substitute in place of chars of the given token type.
- * @return A string containing the new affix pattern.
- */
- public static String replaceType(CharSequence affixPattern, int type, char replacementChar) {
- if (affixPattern == null || affixPattern.length() == 0) return "";
- char[] chars = affixPattern.toString().toCharArray();
- long tag = 0L;
- while (hasNext(tag, affixPattern)) {
- tag = nextToken(tag, affixPattern);
- if (getTypeOrCp(tag) == type) {
+
+ /**
+ * Returns the next token from the affix pattern.
+ *
+ * @param tag
+ * A bitmask used for keeping track of state from token to token. The initial value should
+ * be 0L.
+ * @param patternString
+ * The affix pattern.
+ * @return The bitmask tag to pass to the next call of this method to retrieve the following token
+ * (never negative), or -1 if there were no more tokens in the affix pattern.
+ * @see #hasNext
+ */
+ public static long nextToken(long tag, CharSequence patternString) {
int offset = getOffset(tag);
- chars[offset - 1] = replacementChar;
- }
- }
- return new String(chars);
- }
-
- /**
- * Returns the next token from the affix pattern.
- *
- * @param tag A bitmask used for keeping track of state from token to token. The initial value
- * should be 0L.
- * @param patternString The affix pattern.
- * @return The bitmask tag to pass to the next call of this method to retrieve the following token
- * (never negative), or -1 if there were no more tokens in the affix pattern.
- * @see #hasNext
- */
- public static long nextToken(long tag, CharSequence patternString) {
- int offset = getOffset(tag);
- int state = getState(tag);
- for (; offset < patternString.length(); ) {
- int cp = Character.codePointAt(patternString, offset);
- int count = Character.charCount(cp);
-
- switch (state) {
- case STATE_BASE:
- switch (cp) {
- case '\'':
- state = STATE_FIRST_QUOTE;
- offset += count;
- // continue to the next code point
- break;
- case '-':
- return makeTag(offset + count, TYPE_MINUS_SIGN, STATE_BASE, 0);
- case '+':
- return makeTag(offset + count, TYPE_PLUS_SIGN, STATE_BASE, 0);
- case '%':
- return makeTag(offset + count, TYPE_PERCENT, STATE_BASE, 0);
- case '‰':
- return makeTag(offset + count, TYPE_PERMILLE, STATE_BASE, 0);
- case '¤':
- state = STATE_FIRST_CURR;
- offset += count;
- // continue to the next code point
- break;
+ int state = getState(tag);
+ for (; offset < patternString.length();) {
+ int cp = Character.codePointAt(patternString, offset);
+ int count = Character.charCount(cp);
+
+ switch (state) {
+ case STATE_BASE:
+ switch (cp) {
+ case '\'':
+ state = STATE_FIRST_QUOTE;
+ offset += count;
+ // continue to the next code point
+ break;
+ case '-':
+ return makeTag(offset + count, TYPE_MINUS_SIGN, STATE_BASE, 0);
+ case '+':
+ return makeTag(offset + count, TYPE_PLUS_SIGN, STATE_BASE, 0);
+ case '%':
+ return makeTag(offset + count, TYPE_PERCENT, STATE_BASE, 0);
+ case '‰':
+ return makeTag(offset + count, TYPE_PERMILLE, STATE_BASE, 0);
+ case '¤':
+ state = STATE_FIRST_CURR;
+ offset += count;
+ // continue to the next code point
+ break;
+ default:
+ return makeTag(offset + count, TYPE_CODEPOINT, STATE_BASE, cp);
+ }
+ break;
+ case STATE_FIRST_QUOTE:
+ if (cp == '\'') {
+ return makeTag(offset + count, TYPE_CODEPOINT, STATE_BASE, cp);
+ } else {
+ return makeTag(offset + count, TYPE_CODEPOINT, STATE_INSIDE_QUOTE, cp);
+ }
+ case STATE_INSIDE_QUOTE:
+ if (cp == '\'') {
+ state = STATE_AFTER_QUOTE;
+ offset += count;
+ // continue to the next code point
+ break;
+ } else {
+ return makeTag(offset + count, TYPE_CODEPOINT, STATE_INSIDE_QUOTE, cp);
+ }
+ case STATE_AFTER_QUOTE:
+ if (cp == '\'') {
+ return makeTag(offset + count, TYPE_CODEPOINT, STATE_INSIDE_QUOTE, cp);
+ } else {
+ state = STATE_BASE;
+ // re-evaluate this code point
+ break;
+ }
+ case STATE_FIRST_CURR:
+ if (cp == '¤') {
+ state = STATE_SECOND_CURR;
+ offset += count;
+ // continue to the next code point
+ break;
+ } else {
+ return makeTag(offset, TYPE_CURRENCY_SINGLE, STATE_BASE, 0);
+ }
+ case STATE_SECOND_CURR:
+ if (cp == '¤') {
+ state = STATE_THIRD_CURR;
+ offset += count;
+ // continue to the next code point
+ break;
+ } else {
+ return makeTag(offset, TYPE_CURRENCY_DOUBLE, STATE_BASE, 0);
+ }
+ case STATE_THIRD_CURR:
+ if (cp == '¤') {
+ state = STATE_FOURTH_CURR;
+ offset += count;
+ // continue to the next code point
+ break;
+ } else {
+ return makeTag(offset, TYPE_CURRENCY_TRIPLE, STATE_BASE, 0);
+ }
+ case STATE_FOURTH_CURR:
+ if (cp == '¤') {
+ state = STATE_FIFTH_CURR;
+ offset += count;
+ // continue to the next code point
+ break;
+ } else {
+ return makeTag(offset, TYPE_CURRENCY_QUAD, STATE_BASE, 0);
+ }
+ case STATE_FIFTH_CURR:
+ if (cp == '¤') {
+ state = STATE_OVERFLOW_CURR;
+ offset += count;
+ // continue to the next code point
+ break;
+ } else {
+ return makeTag(offset, TYPE_CURRENCY_QUINT, STATE_BASE, 0);
+ }
+ case STATE_OVERFLOW_CURR:
+ if (cp == '¤') {
+ offset += count;
+ // continue to the next code point and loop back to this state
+ break;
+ } else {
+ return makeTag(offset, TYPE_CURRENCY_OVERFLOW, STATE_BASE, 0);
+ }
default:
- return makeTag(offset + count, TYPE_CODEPOINT, STATE_BASE, cp);
- }
- break;
+ throw new AssertionError();
+ }
+ }
+ // End of string
+ switch (state) {
+ case STATE_BASE:
+ // No more tokens in string.
+ return -1L;
case STATE_FIRST_QUOTE:
- if (cp == '\'') {
- return makeTag(offset + count, TYPE_CODEPOINT, STATE_BASE, cp);
- } else {
- return makeTag(offset + count, TYPE_CODEPOINT, STATE_INSIDE_QUOTE, cp);
- }
case STATE_INSIDE_QUOTE:
- if (cp == '\'') {
- state = STATE_AFTER_QUOTE;
- offset += count;
- // continue to the next code point
- break;
- } else {
- return makeTag(offset + count, TYPE_CODEPOINT, STATE_INSIDE_QUOTE, cp);
- }
+ // For consistent behavior with the JDK and ICU 58, throw an exception here.
+ throw new IllegalArgumentException(
+ "Unterminated quote in pattern affix: \"" + patternString + "\"");
case STATE_AFTER_QUOTE:
- if (cp == '\'') {
- return makeTag(offset + count, TYPE_CODEPOINT, STATE_INSIDE_QUOTE, cp);
- } else {
- state = STATE_BASE;
- // re-evaluate this code point
- break;
- }
+ // No more tokens in string.
+ return -1L;
case STATE_FIRST_CURR:
- if (cp == '¤') {
- state = STATE_SECOND_CURR;
- offset += count;
- // continue to the next code point
- break;
- } else {
return makeTag(offset, TYPE_CURRENCY_SINGLE, STATE_BASE, 0);
- }
case STATE_SECOND_CURR:
- if (cp == '¤') {
- state = STATE_THIRD_CURR;
- offset += count;
- // continue to the next code point
- break;
- } else {
return makeTag(offset, TYPE_CURRENCY_DOUBLE, STATE_BASE, 0);
- }
case STATE_THIRD_CURR:
- if (cp == '¤') {
- state = STATE_FOURTH_CURR;
- offset += count;
- // continue to the next code point
- break;
- } else {
return makeTag(offset, TYPE_CURRENCY_TRIPLE, STATE_BASE, 0);
- }
case STATE_FOURTH_CURR:
- if (cp == '¤') {
- state = STATE_FIFTH_CURR;
- offset += count;
- // continue to the next code point
- break;
- } else {
return makeTag(offset, TYPE_CURRENCY_QUAD, STATE_BASE, 0);
- }
case STATE_FIFTH_CURR:
- if (cp == '¤') {
- state = STATE_OVERFLOW_CURR;
- offset += count;
- // continue to the next code point
- break;
- } else {
return makeTag(offset, TYPE_CURRENCY_QUINT, STATE_BASE, 0);
- }
case STATE_OVERFLOW_CURR:
- if (cp == '¤') {
- offset += count;
- // continue to the next code point and loop back to this state
- break;
- } else {
return makeTag(offset, TYPE_CURRENCY_OVERFLOW, STATE_BASE, 0);
- }
default:
- throw new AssertionError();
- }
+ throw new AssertionError();
+ }
}
- // End of string
- switch (state) {
- case STATE_BASE:
- // No more tokens in string.
- return -1L;
- case STATE_FIRST_QUOTE:
- case STATE_INSIDE_QUOTE:
- // For consistent behavior with the JDK and ICU 58, throw an exception here.
- throw new IllegalArgumentException(
- "Unterminated quote in pattern affix: \"" + patternString + "\"");
- case STATE_AFTER_QUOTE:
- // No more tokens in string.
- return -1L;
- case STATE_FIRST_CURR:
- return makeTag(offset, TYPE_CURRENCY_SINGLE, STATE_BASE, 0);
- case STATE_SECOND_CURR:
- return makeTag(offset, TYPE_CURRENCY_DOUBLE, STATE_BASE, 0);
- case STATE_THIRD_CURR:
- return makeTag(offset, TYPE_CURRENCY_TRIPLE, STATE_BASE, 0);
- case STATE_FOURTH_CURR:
- return makeTag(offset, TYPE_CURRENCY_QUAD, STATE_BASE, 0);
- case STATE_FIFTH_CURR:
- return makeTag(offset, TYPE_CURRENCY_QUINT, STATE_BASE, 0);
- case STATE_OVERFLOW_CURR:
- return makeTag(offset, TYPE_CURRENCY_OVERFLOW, STATE_BASE, 0);
- default:
- throw new AssertionError();
+
+ /**
+ * Returns whether the affix pattern string has any more tokens to be retrieved from a call to
+ * {@link #nextToken}.
+ *
+ * @param tag
+ * The bitmask tag of the previous token, as returned by {@link #nextToken}.
+ * @param string
+ * The affix pattern.
+ * @return true if there are more tokens to consume; false otherwise.
+ */
+ public static boolean hasNext(long tag, CharSequence string) {
+ assert tag >= 0;
+ int state = getState(tag);
+ int offset = getOffset(tag);
+ // Special case: the last character in string is an end quote.
+ if (state == STATE_INSIDE_QUOTE
+ && offset == string.length() - 1
+ && string.charAt(offset) == '\'') {
+ return false;
+ } else if (state != STATE_BASE) {
+ return true;
+ } else {
+ return offset < string.length();
+ }
+ }
+
+ /**
+ * This function helps determine the identity of the token consumed by {@link #nextToken}. Converts
+ * from a bitmask tag, based on a call to {@link #nextToken}, to its corresponding symbol type or
+ * code point.
+ *
+ * @param tag
+ * The bitmask tag of the current token, as returned by {@link #nextToken}.
+ * @return If less than zero, a symbol type corresponding to one of the <code>TYPE_</code> constants,
+ * such as {@link #TYPE_MINUS_SIGN}. If greater than or equal to zero, a literal code point.
+ */
+ public static int getTypeOrCp(long tag) {
+ assert tag >= 0;
+ int type = getType(tag);
+ return (type == TYPE_CODEPOINT) ? getCodePoint(tag) : -type;
}
- }
-
- /**
- * Returns whether the affix pattern string has any more tokens to be retrieved from a call to
- * {@link #nextToken}.
- *
- * @param tag The bitmask tag of the previous token, as returned by {@link #nextToken}.
- * @param string The affix pattern.
- * @return true if there are more tokens to consume; false otherwise.
- */
- public static boolean hasNext(long tag, CharSequence string) {
- assert tag >= 0;
- int state = getState(tag);
- int offset = getOffset(tag);
- // Special case: the last character in string is an end quote.
- if (state == STATE_INSIDE_QUOTE
- && offset == string.length() - 1
- && string.charAt(offset) == '\'') {
- return false;
- } else if (state != STATE_BASE) {
- return true;
- } else {
- return offset < string.length();
+
+ /**
+ * Encodes the given values into a 64-bit tag.
+ *
+ * <ul>
+ * <li>Bits 0-31 => offset (int32)
+ * <li>Bits 32-35 => type (uint4)
+ * <li>Bits 36-39 => state (uint4)
+ * <li>Bits 40-60 => code point (uint21)
+ * <li>Bits 61-63 => unused
+ * </ul>
+ */
+ private static long makeTag(int offset, int type, int state, int cp) {
+ long tag = 0L;
+ tag |= offset;
+ tag |= (-(long) type) << 32;
+ tag |= ((long) state) << 36;
+ tag |= ((long) cp) << 40;
+ assert tag >= 0;
+ return tag;
+ }
+
+ static int getOffset(long tag) {
+ return (int) (tag & 0xffffffff);
+ }
+
+ static int getType(long tag) {
+ return (int) ((tag >>> 32) & 0xf);
+ }
+
+ static int getState(long tag) {
+ return (int) ((tag >>> 36) & 0xf);
+ }
+
+ static int getCodePoint(long tag) {
+ return (int) (tag >>> 40);
}
- }
-
- /**
- * This function helps determine the identity of the token consumed by {@link #nextToken}.
- * Converts from a bitmask tag, based on a call to {@link #nextToken}, to its corresponding symbol
- * type or code point.
- *
- * @param tag The bitmask tag of the current token, as returned by {@link #nextToken}.
- * @return If less than zero, a symbol type corresponding to one of the <code>TYPE_</code>
- * constants, such as {@link #TYPE_MINUS_SIGN}. If greater than or equal to zero, a literal
- * code point.
- */
- public static int getTypeOrCp(long tag) {
- assert tag >= 0;
- int type = getType(tag);
- return (type == TYPE_CODEPOINT) ? getCodePoint(tag) : -type;
- }
-
- /**
- * Encodes the given values into a 64-bit tag.
- *
- * <ul>
- * <li>Bits 0-31 => offset (int32)
- * <li>Bits 32-35 => type (uint4)
- * <li>Bits 36-39 => state (uint4)
- * <li>Bits 40-60 => code point (uint21)
- * <li>Bits 61-63 => unused
- * </ul>
- */
- private static long makeTag(int offset, int type, int state, int cp) {
- long tag = 0L;
- tag |= offset;
- tag |= (-(long) type) << 32;
- tag |= ((long) state) << 36;
- tag |= ((long) cp) << 40;
- assert tag >= 0;
- return tag;
- }
-
- static int getOffset(long tag) {
- return (int) (tag & 0xffffffff);
- }
-
- static int getType(long tag) {
- return (int) ((tag >>> 32) & 0xf);
- }
-
- static int getState(long tag) {
- return (int) ((tag >>> 36) & 0xf);
- }
-
- static int getCodePoint(long tag) {
- return (int) (tag >>> 40);
- }
}
isEmpty = true;
}
- public void populate(ULocale locale, String nsName, CompactStyle compactStyle, CompactType compactType) {
+ public void populate(
+ ULocale locale,
+ String nsName,
+ CompactStyle compactStyle,
+ CompactType compactType) {
assert isEmpty;
CompactDataSink sink = new CompactDataSink(this);
- ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
+ ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle
+ .getBundleInstance(ICUData.ICU_BASE_NAME, locale);
boolean nsIsLatn = nsName.equals("latn");
boolean compactIsShort = compactStyle == CompactStyle.SHORT;
}
/** Produces a string like "NumberElements/latn/patternsShort/decimalFormat". */
- private static void getResourceBundleKey(String nsName, CompactStyle compactStyle, CompactType compactType, StringBuilder sb) {
+ private static void getResourceBundleKey(
+ String nsName,
+ CompactStyle compactStyle,
+ CompactType compactType,
+ StringBuilder sb) {
sb.setLength(0);
sb.append("NumberElements/");
sb.append(nsName);
/** Java-only method used by CLDR tooling. */
public void populate(Map<String, Map<String, String>> powersToPluralsToPatterns) {
assert isEmpty;
- for (Map.Entry<String, Map<String, String>> magnitudeEntry : powersToPluralsToPatterns.entrySet()) {
+ for (Map.Entry<String, Map<String, String>> magnitudeEntry : powersToPluralsToPatterns
+ .entrySet()) {
byte magnitude = (byte) (magnitudeEntry.getKey().length() - 1);
for (Map.Entry<String, String> pluralEntry : magnitudeEntry.getValue().entrySet()) {
StandardPlural plural = StandardPlural.fromString(pluralEntry.getKey().toString());
for (int i3 = 0; powersOfTenTable.getKeyAndValue(i3, key, value); ++i3) {
// Assumes that the keys are always of the form "10000" where the magnitude is the
- // length of the key minus one. We expect magnitudes to be less than MAX_DIGITS.
+ // length of the key minus one. We expect magnitudes to be less than MAX_DIGITS.
byte magnitude = (byte) (key.length() - 1);
byte multiplier = data.multipliers[magnitude];
assert magnitude < COMPACT_MAX_DIGITS;
* Constructs an instance with the given strings.
*
* <p>
- * The arguments need to be Strings, not CharSequences, because Strings are immutable but CharSequences are not.
+ * The arguments need to be Strings, not CharSequences, because Strings are immutable but
+ * CharSequences are not.
*
* @param prefix
* The prefix string.
import com.ibm.icu.text.NumberFormat.Field;
/**
- * An implementation of {@link Modifier} that allows for multiple types of fields in the same modifier. Constructed
- * based on the contents of two {@link NumberStringBuilder} instances (one for the prefix, one for the suffix).
+ * An implementation of {@link Modifier} that allows for multiple types of fields in the same modifier.
+ * Constructed based on the contents of two {@link NumberStringBuilder} instances (one for the prefix,
+ * one for the suffix).
*/
public class ConstantMultiFieldModifier implements Modifier {
protected final Field[] suffixFields;
private final boolean strong;
- public ConstantMultiFieldModifier(NumberStringBuilder prefix, NumberStringBuilder suffix, boolean strong) {
+ public ConstantMultiFieldModifier(
+ NumberStringBuilder prefix,
+ NumberStringBuilder suffix,
+ boolean strong) {
prefixChars = prefix.toCharArray();
suffixChars = suffix.toCharArray();
prefixFields = prefix.toFieldArray();
NumberStringBuilder temp = new NumberStringBuilder();
apply(temp, 0, 0);
int prefixLength = getPrefixLength();
- return String.format("<ConstantMultiFieldModifier prefix:'%s' suffix:'%s'>", temp.subSequence(0, prefixLength),
+ return String.format("<ConstantMultiFieldModifier prefix:'%s' suffix:'%s'>",
+ temp.subSequence(0, prefixLength),
temp.subSequence(prefixLength, temp.length()));
}
}
private final String beforeSuffixInsert;
/** Safe code path */
- public CurrencySpacingEnabledModifier(NumberStringBuilder prefix, NumberStringBuilder suffix, boolean strong,
+ public CurrencySpacingEnabledModifier(
+ NumberStringBuilder prefix,
+ NumberStringBuilder suffix,
+ boolean strong,
DecimalFormatSymbols symbols) {
super(prefix, suffix, strong);
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
// Currency spacing logic
int length = 0;
- if (rightIndex - leftIndex > 0 && afterPrefixUnicodeSet != null
+ if (rightIndex - leftIndex > 0
+ && afterPrefixUnicodeSet != null
&& afterPrefixUnicodeSet.contains(output.codePointAt(leftIndex))) {
// TODO: Should we use the CURRENCY field here?
length += output.insert(leftIndex, afterPrefixInsert, null);
}
- if (rightIndex - leftIndex > 0 && beforeSuffixUnicodeSet != null
+ if (rightIndex - leftIndex > 0
+ && beforeSuffixUnicodeSet != null
&& beforeSuffixUnicodeSet.contains(output.codePointBefore(rightIndex))) {
// TODO: Should we use the CURRENCY field here?
length += output.insert(rightIndex + length, beforeSuffixInsert, null);
}
/** Unsafe code path */
- public static int applyCurrencySpacing(NumberStringBuilder output, int prefixStart, int prefixLen, int suffixStart,
- int suffixLen, DecimalFormatSymbols symbols) {
+ public static int applyCurrencySpacing(
+ NumberStringBuilder output,
+ int prefixStart,
+ int prefixLen,
+ int suffixStart,
+ int suffixLen,
+ DecimalFormatSymbols symbols) {
int length = 0;
boolean hasPrefix = (prefixLen > 0);
boolean hasSuffix = (suffixLen > 0);
}
/** Unsafe code path */
- private static int applyCurrencySpacingAffix(NumberStringBuilder output, int index, byte affix,
+ private static int applyCurrencySpacingAffix(
+ NumberStringBuilder output,
+ int index,
+ byte affix,
DecimalFormatSymbols symbols) {
// NOTE: For prefix, output.fieldAt(index-1) gets the last field type in the prefix.
// This works even if the last code point in the prefix is 2 code units because the
// field value gets populated to both indices in the field array.
- NumberFormat.Field affixField = (affix == PREFIX) ? output.fieldAt(index - 1) : output.fieldAt(index);
+ NumberFormat.Field affixField = (affix == PREFIX) ? output.fieldAt(index - 1)
+ : output.fieldAt(index);
if (affixField != NumberFormat.Field.CURRENCY) {
return 0;
}
private static UnicodeSet getUnicodeSet(DecimalFormatSymbols symbols, short position, byte affix) {
String pattern = symbols
- .getPatternForCurrencySpacing(position == IN_CURRENCY ? DecimalFormatSymbols.CURRENCY_SPC_CURRENCY_MATCH
- : DecimalFormatSymbols.CURRENCY_SPC_SURROUNDING_MATCH, affix == SUFFIX);
+ .getPatternForCurrencySpacing(
+ position == IN_CURRENCY ? DecimalFormatSymbols.CURRENCY_SPC_CURRENCY_MATCH
+ : DecimalFormatSymbols.CURRENCY_SPC_SURROUNDING_MATCH,
+ affix == SUFFIX);
if (pattern.equals("[:digit:]")) {
return UNISET_DIGIT;
} else if (pattern.equals("[:^S:]")) {
}
private static String getInsertString(DecimalFormatSymbols symbols, byte affix) {
- return symbols.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_INSERT, affix == SUFFIX);
+ return symbols.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_INSERT,
+ affix == SUFFIX);
}
}
import com.ibm.icu.util.ULocale;
public class CustomSymbolCurrency extends Currency {
- private static final long serialVersionUID = 2497493016770137670L;
- // TODO: Serialization methods?
+ private static final long serialVersionUID = 2497493016770137670L;
+ // TODO: Serialization methods?
- private String symbol1;
- private String symbol2;
+ private String symbol1;
+ private String symbol2;
- public static Currency resolve(Currency currency, ULocale locale, DecimalFormatSymbols symbols) {
- if (currency == null) {
- currency = symbols.getCurrency();
+ public static Currency resolve(Currency currency, ULocale locale, DecimalFormatSymbols symbols) {
+ if (currency == null) {
+ currency = symbols.getCurrency();
+ }
+ String currency1Sym = symbols.getCurrencySymbol();
+ String currency2Sym = symbols.getInternationalCurrencySymbol();
+ if (currency == null) {
+ return new CustomSymbolCurrency("XXX", currency1Sym, currency2Sym);
+ }
+ if (!currency.equals(symbols.getCurrency())) {
+ return currency;
+ }
+ String currency1 = currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null);
+ String currency2 = currency.getCurrencyCode();
+ if (!currency1.equals(currency1Sym) || !currency2.equals(currency2Sym)) {
+ return new CustomSymbolCurrency(currency2, currency1Sym, currency2Sym);
+ }
+ return currency;
}
- String currency1Sym = symbols.getCurrencySymbol();
- String currency2Sym = symbols.getInternationalCurrencySymbol();
- if (currency == null) {
- return new CustomSymbolCurrency("XXX", currency1Sym, currency2Sym);
- }
- if (!currency.equals(symbols.getCurrency())) {
- return currency;
- }
- String currency1 = currency.getName(symbols.getULocale(), Currency.SYMBOL_NAME, null);
- String currency2 = currency.getCurrencyCode();
- if (!currency1.equals(currency1Sym) || !currency2.equals(currency2Sym)) {
- return new CustomSymbolCurrency(currency2, currency1Sym, currency2Sym);
- }
- return currency;
- }
- public CustomSymbolCurrency(String isoCode, String currency1Sym, String currency2Sym) {
- super(isoCode);
- this.symbol1 = currency1Sym;
- this.symbol2 = currency2Sym;
- }
+ public CustomSymbolCurrency(String isoCode, String currency1Sym, String currency2Sym) {
+ super(isoCode);
+ this.symbol1 = currency1Sym;
+ this.symbol2 = currency2Sym;
+ }
- @Override
- public String getName(ULocale locale, int nameStyle, boolean[] isChoiceFormat) {
- if (nameStyle == SYMBOL_NAME) {
- return symbol1;
+ @Override
+ public String getName(ULocale locale, int nameStyle, boolean[] isChoiceFormat) {
+ if (nameStyle == SYMBOL_NAME) {
+ return symbol1;
+ }
+ return super.getName(locale, nameStyle, isChoiceFormat);
}
- return super.getName(locale, nameStyle, isChoiceFormat);
- }
- @Override
- public String getName(
- ULocale locale, int nameStyle, String pluralCount, boolean[] isChoiceFormat) {
- if (nameStyle == PLURAL_LONG_NAME && subType.equals("XXX")) {
- // Plural in absence of a currency should return the symbol
- return symbol1;
+ @Override
+ public String getName(ULocale locale, int nameStyle, String pluralCount, boolean[] isChoiceFormat) {
+ if (nameStyle == PLURAL_LONG_NAME && subType.equals("XXX")) {
+ // Plural in absence of a currency should return the symbol
+ return symbol1;
+ }
+ return super.getName(locale, nameStyle, pluralCount, isChoiceFormat);
}
- return super.getName(locale, nameStyle, pluralCount, isChoiceFormat);
- }
- @Override
- public String getCurrencyCode() {
- return symbol2;
- }
+ @Override
+ public String getCurrencyCode() {
+ return symbol2;
+ }
- @Override
- public int hashCode() {
- return super.hashCode() ^ symbol1.hashCode() ^ symbol2.hashCode();
- }
+ @Override
+ public int hashCode() {
+ return super.hashCode() ^ symbol1.hashCode() ^ symbol2.hashCode();
+ }
- @Override
- public boolean equals(Object other) {
- return super.equals(other)
- && ((CustomSymbolCurrency)other).symbol1.equals(symbol1)
- && ((CustomSymbolCurrency)other).symbol2.equals(symbol2);
- }
+ @Override
+ public boolean equals(Object other) {
+ return super.equals(other)
+ && ((CustomSymbolCurrency) other).symbol1.equals(symbol1)
+ && ((CustomSymbolCurrency) other).symbol2.equals(symbol2);
+ }
}
* Sets all properties to their defaults (unset).
*
* <p>
- * All integers default to -1 EXCEPT FOR MAGNITUDE MULTIPLIER which has a default of 0 (since negative numbers are
- * important).
+ * All integers default to -1 EXCEPT FOR MAGNITUDE MULTIPLIER which has a default of 0 (since
+ * negative numbers are important).
*
* <p>
* All booleans default to false.
readObjectImpl(ois);
}
- /* package-private */ void readObjectImpl(ObjectInputStream ois) throws IOException, ClassNotFoundException {
+ /* package-private */ void readObjectImpl(ObjectInputStream ois)
+ throws IOException, ClassNotFoundException {
ois.defaultReadObject();
// Initialize to empty
}
/**
- * Specifies custom data to be used instead of CLDR data when constructing a CompactDecimalFormat. The argument
- * should be a map with the following structure:
+ * Specifies custom data to be used instead of CLDR data when constructing a CompactDecimalFormat.
+ * The argument should be a map with the following structure:
*
* <pre>
* {
* A map with the above structure.
* @return The property bag, for chaining.
*/
- public DecimalFormatProperties setCompactCustomData(Map<String, Map<String, String>> compactCustomData) {
+ public DecimalFormatProperties setCompactCustomData(
+ Map<String, Map<String, String>> compactCustomData) {
// TODO: compactCustomData is not immutable.
this.compactCustomData = compactCustomData;
return this;
}
/**
- * Use compact decimal formatting with the specified {@link CompactStyle}. CompactStyle.SHORT produces output like
- * "10K" in locale <em>en-US</em>, whereas CompactStyle.LONG produces output like "10 thousand" in that locale.
+ * Use compact decimal formatting with the specified {@link CompactStyle}. CompactStyle.SHORT
+ * produces output like "10K" in locale <em>en-US</em>, whereas CompactStyle.LONG produces output
+ * like "10 thousand" in that locale.
*
* @param compactStyle
* The style of prefixes/suffixes to append.
}
/**
- * Use the specified {@link CurrencyUsage} instance, which provides default rounding rules for the currency in two
- * styles, CurrencyUsage.CASH and CurrencyUsage.STANDARD.
+ * Use the specified {@link CurrencyUsage} instance, which provides default rounding rules for the
+ * currency in two styles, CurrencyUsage.CASH and CurrencyUsage.STANDARD.
*
* <p>
- * The CurrencyUsage specified here will not be used unless there is a currency placeholder in the pattern.
+ * The CurrencyUsage specified here will not be used unless there is a currency placeholder in the
+ * pattern.
*
* @param currencyUsage
* The currency usage. Defaults to CurrencyUsage.STANDARD.
}
/**
- * PARSING: Whether to require that the presence of decimal point matches the pattern. If a decimal point is not
- * present, but the pattern contained a decimal point, parse will not succeed: null will be returned from
- * <code>parse()</code>, and an error index will be set in the {@link ParsePosition}.
+ * PARSING: Whether to require that the presence of decimal point matches the pattern. If a decimal
+ * point is not present, but the pattern contained a decimal point, parse will not succeed: null will
+ * be returned from <code>parse()</code>, and an error index will be set in the
+ * {@link ParsePosition}.
*
* @param decimalPatternMatchRequired
* true to set an error if decimal is not present
}
/**
- * Sets whether to always show the decimal point, even if the number doesn't require one. For example, if always
- * show decimal is true, the number 123 would be formatted as "123." in locale <em>en-US</em>.
+ * Sets whether to always show the decimal point, even if the number doesn't require one. For
+ * example, if always show decimal is true, the number 123 would be formatted as "123." in locale
+ * <em>en-US</em>.
*
* @param alwaysShowDecimal
* Whether to show the decimal point when it is optional.
}
/**
- * Sets whether to show the plus sign in the exponent part of numbers with a zero or positive exponent. For example,
- * the number "1200" with the pattern "0.0E0" would be formatted as "1.2E+3" instead of "1.2E3" in <em>en-US</em>.
+ * Sets whether to show the plus sign in the exponent part of numbers with a zero or positive
+ * exponent. For example, the number "1200" with the pattern "0.0E0" would be formatted as "1.2E+3"
+ * instead of "1.2E3" in <em>en-US</em>.
*
* @param exponentSignAlwaysShown
* Whether to show the plus sign in positive exponents.
}
/**
- * Sets the minimum width of the string output by the formatting pipeline. For example, if padding is enabled and
- * paddingWidth is set to 6, formatting the number "3.14159" with the pattern "0.00" will result in "··3.14" if '·'
- * is your padding string.
+ * Sets the minimum width of the string output by the formatting pipeline. For example, if padding is
+ * enabled and paddingWidth is set to 6, formatting the number "3.14159" with the pattern "0.00" will
+ * result in "··3.14" if '·' is your padding string.
*
* <p>
- * If the number is longer than your padding width, the number will display as if no padding width had been
- * specified, which may result in strings longer than the padding width.
+ * If the number is longer than your padding width, the number will display as if no padding width
+ * had been specified, which may result in strings longer than the padding width.
*
* <p>
* Width is counted in UTF-16 code units.
}
/**
- * Sets the number of digits between grouping separators. For example, the <em>en-US</em> locale uses a grouping
- * size of 3, so the number 1234567 would be formatted as "1,234,567". For locales whose grouping sizes vary with
- * magnitude, see {@link #setSecondaryGroupingSize(int)}.
+ * Sets the number of digits between grouping separators. For example, the <em>en-US</em> locale uses
+ * a grouping size of 3, so the number 1234567 would be formatted as "1,234,567". For locales whose
+ * grouping sizes vary with magnitude, see {@link #setSecondaryGroupingSize(int)}.
*
* @param groupingSize
* The primary grouping size.
}
/**
- * Multiply all numbers by this power of ten before formatting. Negative multipliers reduce the magnitude and make
- * numbers smaller (closer to zero).
+ * Multiply all numbers by this power of ten before formatting. Negative multipliers reduce the
+ * magnitude and make numbers smaller (closer to zero).
*
* @param magnitudeMultiplier
* The number of powers of ten to scale.
}
/**
- * Sets the {@link MathContext} to be used during math and rounding operations. A MathContext encapsulates a
- * RoundingMode and the number of significant digits in the output.
+ * Sets the {@link MathContext} to be used during math and rounding operations. A MathContext
+ * encapsulates a RoundingMode and the number of significant digits in the output.
*
* @param mathContext
* The math context to use when rounding is required.
}
/**
- * Sets the maximum number of digits to display after the decimal point. If the number has fewer than this number of
- * digits, the number will be rounded off using the rounding mode specified by
- * {@link #setRoundingMode(RoundingMode)}. The pattern "#00.0#", for example, corresponds to 2 maximum fraction
- * digits, and the number 456.789 would be formatted as "456.79" in locale <em>en-US</em> with the default rounding
- * mode. Note that the number 456.999 would be formatted as "457.0" given the same configurations.
+ * Sets the maximum number of digits to display after the decimal point. If the number has fewer than
+ * this number of digits, the number will be rounded off using the rounding mode specified by
+ * {@link #setRoundingMode(RoundingMode)}. The pattern "#00.0#", for example, corresponds to 2
+ * maximum fraction digits, and the number 456.789 would be formatted as "456.79" in locale
+ * <em>en-US</em> with the default rounding mode. Note that the number 456.999 would be formatted as
+ * "457.0" given the same configurations.
*
* @param maximumFractionDigits
* The maximum number of fraction digits to output.
}
/**
- * Sets the maximum number of digits to display before the decimal point. If the number has more than this number of
- * digits, the extra digits will be truncated. For example, if maximum integer digits is 2, and you attempt to
- * format the number 1970, you will get "70" in locale <em>en-US</em>. It is not possible to specify the maximum
- * integer digits using a pattern string, except in the special case of a scientific format pattern.
+ * Sets the maximum number of digits to display before the decimal point. If the number has more than
+ * this number of digits, the extra digits will be truncated. For example, if maximum integer digits
+ * is 2, and you attempt to format the number 1970, you will get "70" in locale <em>en-US</em>. It is
+ * not possible to specify the maximum integer digits using a pattern string, except in the special
+ * case of a scientific format pattern.
*
* @param maximumIntegerDigits
* The maximum number of integer digits to output.
}
/**
- * Sets the maximum number of significant digits to display. The number of significant digits is equal to the number
- * of digits counted from the leftmost nonzero digit through the rightmost nonzero digit; for example, the number
- * "2010" has 3 significant digits. If the number has more significant digits than specified here, the extra
- * significant digits will be rounded off using the rounding mode specified by
- * {@link #setRoundingMode(RoundingMode)}. For example, if maximum significant digits is 3, the number 1234.56 will
- * be formatted as "1230" in locale <em>en-US</em> with the default rounding mode.
+ * Sets the maximum number of significant digits to display. The number of significant digits is
+ * equal to the number of digits counted from the leftmost nonzero digit through the rightmost
+ * nonzero digit; for example, the number "2010" has 3 significant digits. If the number has more
+ * significant digits than specified here, the extra significant digits will be rounded off using the
+ * rounding mode specified by {@link #setRoundingMode(RoundingMode)}. For example, if maximum
+ * significant digits is 3, the number 1234.56 will be formatted as "1230" in locale <em>en-US</em>
+ * with the default rounding mode.
*
* <p>
- * If both maximum significant digits and maximum integer/fraction digits are set at the same time, the behavior is
- * undefined.
+ * If both maximum significant digits and maximum integer/fraction digits are set at the same time,
+ * the behavior is undefined.
*
* <p>
- * The number of significant digits can be specified in a pattern string using the '@' character. For example, the
- * pattern "@@#" corresponds to a minimum of 2 and a maximum of 3 significant digits.
+ * The number of significant digits can be specified in a pattern string using the '@' character. For
+ * example, the pattern "@@#" corresponds to a minimum of 2 and a maximum of 3 significant digits.
*
* @param maximumSignificantDigits
* The maximum number of significant digits to display.
}
/**
- * Sets the minimum number of digits to display in the exponent. For example, the number "1200" with the pattern
- * "0.0E00", which has 2 exponent digits, would be formatted as "1.2E03" in <em>en-US</em>.
+ * Sets the minimum number of digits to display in the exponent. For example, the number "1200" with
+ * the pattern "0.0E00", which has 2 exponent digits, would be formatted as "1.2E03" in
+ * <em>en-US</em>.
*
* @param minimumExponentDigits
* The minimum number of digits to display in the exponent field.
}
/**
- * Sets the minimum number of digits to display after the decimal point. If the number has fewer than this number of
- * digits, the number will be padded with zeros. The pattern "#00.0#", for example, corresponds to 1 minimum
- * fraction digit, and the number 456 would be formatted as "456.0" in locale <em>en-US</em>.
+ * Sets the minimum number of digits to display after the decimal point. If the number has fewer than
+ * this number of digits, the number will be padded with zeros. The pattern "#00.0#", for example,
+ * corresponds to 1 minimum fraction digit, and the number 456 would be formatted as "456.0" in
+ * locale <em>en-US</em>.
*
* @param minimumFractionDigits
* The minimum number of fraction digits to output.
}
/**
- * Sets the minimum number of digits required to be beyond the first grouping separator in order to enable grouping.
- * For example, if the minimum grouping digits is 2, then 1234 would be formatted as "1234" but 12345 would be
- * formatted as "12,345" in <em>en-US</em>. Note that 1234567 would still be formatted as "1,234,567", not
- * "1234,567".
+ * Sets the minimum number of digits required to be beyond the first grouping separator in order to
+ * enable grouping. For example, if the minimum grouping digits is 2, then 1234 would be formatted as
+ * "1234" but 12345 would be formatted as "12,345" in <em>en-US</em>. Note that 1234567 would still
+ * be formatted as "1,234,567", not "1234,567".
*
* @param minimumGroupingDigits
* How many digits must appear before a grouping separator before enabling grouping.
}
/**
- * Sets the minimum number of digits to display before the decimal point. If the number has fewer than this number
- * of digits, the number will be padded with zeros. The pattern "#00.0#", for example, corresponds to 2 minimum
- * integer digits, and the number 5.3 would be formatted as "05.3" in locale <em>en-US</em>.
+ * Sets the minimum number of digits to display before the decimal point. If the number has fewer
+ * than this number of digits, the number will be padded with zeros. The pattern "#00.0#", for
+ * example, corresponds to 2 minimum integer digits, and the number 5.3 would be formatted as "05.3"
+ * in locale <em>en-US</em>.
*
* @param minimumIntegerDigits
* The minimum number of integer digits to output.
}
/**
- * Sets the minimum number of significant digits to display. If, after rounding to the number of significant digits
- * specified by {@link #setMaximumSignificantDigits}, the number of remaining significant digits is less than the
- * minimum, the number will be padded with zeros. For example, if minimum significant digits is 3, the number 5.8
- * will be formatted as "5.80" in locale <em>en-US</em>. Note that minimum significant digits is relevant only when
- * numbers have digits after the decimal point.
+ * Sets the minimum number of significant digits to display. If, after rounding to the number of
+ * significant digits specified by {@link #setMaximumSignificantDigits}, the number of remaining
+ * significant digits is less than the minimum, the number will be padded with zeros. For example, if
+ * minimum significant digits is 3, the number 5.8 will be formatted as "5.80" in locale
+ * <em>en-US</em>. Note that minimum significant digits is relevant only when numbers have digits
+ * after the decimal point.
*
* <p>
- * If both minimum significant digits and minimum integer/fraction digits are set at the same time, both values will
- * be respected, and the one that results in the greater number of padding zeros will be used. For example,
- * formatting the number 73 with 3 minimum significant digits and 2 minimum fraction digits will produce "73.00".
+ * If both minimum significant digits and minimum integer/fraction digits are set at the same time,
+ * both values will be respected, and the one that results in the greater number of padding zeros
+ * will be used. For example, formatting the number 73 with 3 minimum significant digits and 2
+ * minimum fraction digits will produce "73.00".
*
* <p>
- * The number of significant digits can be specified in a pattern string using the '@' character. For example, the
- * pattern "@@#" corresponds to a minimum of 2 and a maximum of 3 significant digits.
+ * The number of significant digits can be specified in a pattern string using the '@' character. For
+ * example, the pattern "@@#" corresponds to a minimum of 2 and a maximum of 3 significant digits.
*
* @param minimumSignificantDigits
* The minimum number of significant digits to display.
}
/**
- * Sets the prefix to prepend to negative numbers. The prefix will be interpreted literally. For example, if you set
- * a negative prefix of <code>n</code>, then the number -123 will be formatted as "n123" in the locale
- * <em>en-US</em>. Note that if the negative prefix is left unset, the locale's minus sign is used.
+ * Sets the prefix to prepend to negative numbers. The prefix will be interpreted literally. For
+ * example, if you set a negative prefix of <code>n</code>, then the number -123 will be formatted as
+ * "n123" in the locale <em>en-US</em>. Note that if the negative prefix is left unset, the locale's
+ * minus sign is used.
*
* <p>
* For more information on prefixes and suffixes, see {@link MutablePatternModifier}.
}
/**
- * Sets the prefix to prepend to negative numbers. Locale-specific symbols will be substituted into the string
- * according to Unicode Technical Standard #35 (LDML).
+ * Sets the prefix to prepend to negative numbers. Locale-specific symbols will be substituted into
+ * the string according to Unicode Technical Standard #35 (LDML).
*
* <p>
* For more information on prefixes and suffixes, see {@link MutablePatternModifier}.
*
* @param negativePrefixPattern
- * The CharSequence to prepend to negative numbers after locale symbol substitutions take place.
+ * The CharSequence to prepend to negative numbers after locale symbol substitutions take
+ * place.
* @return The property bag, for chaining.
* @see #setNegativePrefix
*/
}
/**
- * Sets the suffix to append to negative numbers. The suffix will be interpreted literally. For example, if you set
- * a suffix prefix of <code>n</code>, then the number -123 will be formatted as "-123n" in the locale
- * <em>en-US</em>. Note that the minus sign is prepended by default unless otherwise specified in either the pattern
- * string or in one of the {@link #setNegativePrefix} methods.
+ * Sets the suffix to append to negative numbers. The suffix will be interpreted literally. For
+ * example, if you set a suffix prefix of <code>n</code>, then the number -123 will be formatted as
+ * "-123n" in the locale <em>en-US</em>. Note that the minus sign is prepended by default unless
+ * otherwise specified in either the pattern string or in one of the {@link #setNegativePrefix}
+ * methods.
*
* <p>
* For more information on prefixes and suffixes, see {@link MutablePatternModifier}.
}
/**
- * Sets the suffix to append to negative numbers. Locale-specific symbols will be substituted into the string
- * according to Unicode Technical Standard #35 (LDML).
+ * Sets the suffix to append to negative numbers. Locale-specific symbols will be substituted into
+ * the string according to Unicode Technical Standard #35 (LDML).
*
* <p>
* For more information on prefixes and suffixes, see {@link MutablePatternModifier}.
*
* @param negativeSuffixPattern
- * The CharSequence to append to negative numbers after locale symbol substitutions take place.
+ * The CharSequence to append to negative numbers after locale symbol substitutions take
+ * place.
* @return The property bag, for chaining.
* @see #setNegativeSuffix
*/
}
/**
- * Sets the location where the padding string is to be inserted to maintain the padding width: one of BEFORE_PREFIX,
- * AFTER_PREFIX, BEFORE_SUFFIX, or AFTER_SUFFIX.
+ * Sets the location where the padding string is to be inserted to maintain the padding width: one of
+ * BEFORE_PREFIX, AFTER_PREFIX, BEFORE_SUFFIX, or AFTER_SUFFIX.
*
* <p>
* Must be used in conjunction with {@link #setFormatWidth}.
}
/**
- * Sets the string used for padding. The string should contain a single character or grapheme cluster.
+ * Sets the string used for padding. The string should contain a single character or grapheme
+ * cluster.
*
* <p>
* Must be used in conjunction with {@link #setFormatWidth}.
}
/**
- * Whether to require cases to match when parsing strings; default is true. Case sensitivity applies to prefixes,
- * suffixes, the exponent separator, the symbol "NaN", and the infinity symbol. Grouping separators, decimal
- * separators, and padding are always case-sensitive. Currencies are always case-insensitive.
+ * Whether to require cases to match when parsing strings; default is true. Case sensitivity applies
+ * to prefixes, suffixes, the exponent separator, the symbol "NaN", and the infinity symbol. Grouping
+ * separators, decimal separators, and padding are always case-sensitive. Currencies are always
+ * case-insensitive.
*
* <p>
- * This setting is ignored in fast mode. In fast mode, strings are always compared in a case-sensitive way.
+ * This setting is ignored in fast mode. In fast mode, strings are always compared in a
+ * case-sensitive way.
*
* @param parseCaseSensitive
* true to be case-sensitive when parsing; false to allow any case.
}
/**
- * Sets the strategy used during parsing when a code point needs to be interpreted as either a decimal separator or
- * a grouping separator.
+ * Sets the strategy used during parsing when a code point needs to be interpreted as either a
+ * decimal separator or a grouping separator.
*
* <p>
- * The comma, period, space, and apostrophe have different meanings in different locales. For example, in
- * <em>en-US</em> and most American locales, the period is used as a decimal separator, but in <em>es-PY</em> and
- * most European locales, it is used as a grouping separator.
+ * The comma, period, space, and apostrophe have different meanings in different locales. For
+ * example, in <em>en-US</em> and most American locales, the period is used as a decimal separator,
+ * but in <em>es-PY</em> and most European locales, it is used as a grouping separator.
*
* <p>
- * Suppose you are in <em>fr-FR</em> the parser encounters the string "1.234". In <em>fr-FR</em>, the grouping is a
- * space and the decimal is a comma. The <em>grouping mode</em> is a mechanism to let you specify whether to accept
- * the string as 1234 (GroupingMode.DEFAULT) or whether to reject it since the separators don't match
- * (GroupingMode.RESTRICTED).
+ * Suppose you are in <em>fr-FR</em> the parser encounters the string "1.234". In <em>fr-FR</em>, the
+ * grouping is a space and the decimal is a comma. The <em>grouping mode</em> is a mechanism to let
+ * you specify whether to accept the string as 1234 (GroupingMode.DEFAULT) or whether to reject it
+ * since the separators don't match (GroupingMode.RESTRICTED).
*
* <p>
- * When resolving grouping separators, it is the <em>equivalence class</em> of separators that is considered. For
- * example, a period is seen as equal to a fixed set of other period-like characters.
+ * When resolving grouping separators, it is the <em>equivalence class</em> of separators that is
+ * considered. For example, a period is seen as equal to a fixed set of other period-like characters.
*
* @param parseGroupingMode
* The {@link GroupingMode} to use; either DEFAULT or RESTRICTED.
}
/**
- * Whether to ignore the fractional part of numbers. For example, parses "123.4" to "123" instead of "123.4".
+ * Whether to ignore the fractional part of numbers. For example, parses "123.4" to "123" instead of
+ * "123.4".
*
* @param parseIntegerOnly
* true to parse integers only; false to parse integers with their fraction parts
}
/**
- * Controls certain rules for how strict this parser is when reading strings. See {@link ParseMode#LENIENT} and
- * {@link ParseMode#STRICT}.
+ * Controls certain rules for how strict this parser is when reading strings. See
+ * {@link ParseMode#LENIENT} and {@link ParseMode#STRICT}.
*
* @param parseMode
* Either {@link ParseMode#LENIENT} or {@link ParseMode#STRICT}.
}
/**
- * Whether to ignore the exponential part of numbers. For example, parses "123E4" to "123" instead of "1230000".
+ * Whether to ignore the exponential part of numbers. For example, parses "123E4" to "123" instead of
+ * "1230000".
*
* @param parseNoExponent
* true to ignore exponents; false to parse them.
}
/**
- * Whether to always return a BigDecimal from {@link Parse#parse} and all other parse methods. By default, a Long or
- * a BigInteger are returned when possible.
+ * Whether to always return a BigDecimal from {@link Parse#parse} and all other parse methods. By
+ * default, a Long or a BigInteger are returned when possible.
*
* @param parseToBigDecimal
- * true to always return a BigDecimal; false to return a Long or a BigInteger when possible.
+ * true to always return a BigDecimal; false to return a Long or a BigInteger when
+ * possible.
* @return The property bag, for chaining.
*/
public DecimalFormatProperties setParseToBigDecimal(boolean parseToBigDecimal) {
}
/**
- * Sets the prefix to prepend to positive numbers. The prefix will be interpreted literally. For example, if you set
- * a positive prefix of <code>p</code>, then the number 123 will be formatted as "p123" in the locale
- * <em>en-US</em>.
+ * Sets the prefix to prepend to positive numbers. The prefix will be interpreted literally. For
+ * example, if you set a positive prefix of <code>p</code>, then the number 123 will be formatted as
+ * "p123" in the locale <em>en-US</em>.
*
* <p>
* For more information on prefixes and suffixes, see {@link MutablePatternModifier}.
}
/**
- * Sets the prefix to prepend to positive numbers. Locale-specific symbols will be substituted into the string
- * according to Unicode Technical Standard #35 (LDML).
+ * Sets the prefix to prepend to positive numbers. Locale-specific symbols will be substituted into
+ * the string according to Unicode Technical Standard #35 (LDML).
*
* <p>
* For more information on prefixes and suffixes, see {@link MutablePatternModifier}.
*
* @param positivePrefixPattern
- * The CharSequence to prepend to positive numbers after locale symbol substitutions take place.
+ * The CharSequence to prepend to positive numbers after locale symbol substitutions take
+ * place.
* @return The property bag, for chaining.
* @see #setPositivePrefix
*/
}
/**
- * Sets the suffix to append to positive numbers. The suffix will be interpreted literally. For example, if you set
- * a positive suffix of <code>p</code>, then the number 123 will be formatted as "123p" in the locale
- * <em>en-US</em>.
+ * Sets the suffix to append to positive numbers. The suffix will be interpreted literally. For
+ * example, if you set a positive suffix of <code>p</code>, then the number 123 will be formatted as
+ * "123p" in the locale <em>en-US</em>.
*
* <p>
* For more information on prefixes and suffixes, see {@link MutablePatternModifier}.
}
/**
- * Sets the suffix to append to positive numbers. Locale-specific symbols will be substituted into the string
- * according to Unicode Technical Standard #35 (LDML).
+ * Sets the suffix to append to positive numbers. Locale-specific symbols will be substituted into
+ * the string according to Unicode Technical Standard #35 (LDML).
*
* <p>
* For more information on prefixes and suffixes, see {@link MutablePatternModifier}.
*
* @param positiveSuffixPattern
- * The CharSequence to append to positive numbers after locale symbol substitutions take place.
+ * The CharSequence to append to positive numbers after locale symbol substitutions take
+ * place.
* @return The property bag, for chaining.
* @see #setPositiveSuffix
*/
}
/**
- * Sets the increment to which to round numbers. For example, with a rounding interval of 0.05, the number 11.17
- * would be formatted as "11.15" in locale <em>en-US</em> with the default rounding mode.
+ * Sets the increment to which to round numbers. For example, with a rounding interval of 0.05, the
+ * number 11.17 would be formatted as "11.15" in locale <em>en-US</em> with the default rounding
+ * mode.
*
* <p>
* You can use either a rounding increment or significant digits, but not both at the same time.
*
* <p>
- * The rounding increment can be specified in a pattern string. For example, the pattern "#,##0.05" corresponds to a
- * rounding interval of 0.05 with 1 minimum integer digit and a grouping size of 3.
+ * The rounding increment can be specified in a pattern string. For example, the pattern "#,##0.05"
+ * corresponds to a rounding interval of 0.05 with 1 minimum integer digit and a grouping size of 3.
*
* @param roundingIncrement
* The interval to which to round.
}
/**
- * Sets the rounding mode, which determines under which conditions extra decimal places are rounded either up or
- * down. See {@link RoundingMode} for details on the choices of rounding mode. The default if not set explicitly is
- * {@link RoundingMode#HALF_EVEN}.
+ * Sets the rounding mode, which determines under which conditions extra decimal places are rounded
+ * either up or down. See {@link RoundingMode} for details on the choices of rounding mode. The
+ * default if not set explicitly is {@link RoundingMode#HALF_EVEN}.
*
* <p>
* This setting is ignored if {@link #setMathContext} is used.
}
/**
- * Sets the number of digits between grouping separators higher than the least-significant grouping separator. For
- * example, the locale <em>hi</em> uses a primary grouping size of 3 and a secondary grouping size of 2, so the
- * number 1234567 would be formatted as "12,34,567".
+ * Sets the number of digits between grouping separators higher than the least-significant grouping
+ * separator. For example, the locale <em>hi</em> uses a primary grouping size of 3 and a secondary
+ * grouping size of 2, so the number 1234567 would be formatted as "12,34,567".
*
* <p>
- * The two levels of grouping separators can be specified in the pattern string. For example, the <em>hi</em>
- * locale's default decimal format pattern is "#,##,##0.###".
+ * The two levels of grouping separators can be specified in the pattern string. For example, the
+ * <em>hi</em> locale's default decimal format pattern is "#,##,##0.###".
*
* @param secondaryGroupingSize
* The secondary grouping size.
* Sets whether to always display of a plus sign on positive numbers.
*
* <p>
- * If the location of the negative sign is specified by the decimal format pattern (or by the negative prefix/suffix
- * pattern methods), a plus sign is substituted into that location, in accordance with Unicode Technical Standard
- * #35 (LDML) section 3.2.1. Otherwise, the plus sign is prepended to the number. For example, if the decimal format
- * pattern <code>#;#-</code> is used, then formatting 123 would result in "123+" in the locale <em>en-US</em>.
+ * If the location of the negative sign is specified by the decimal format pattern (or by the
+ * negative prefix/suffix pattern methods), a plus sign is substituted into that location, in
+ * accordance with Unicode Technical Standard #35 (LDML) section 3.2.1. Otherwise, the plus sign is
+ * prepended to the number. For example, if the decimal format pattern <code>#;#-</code> is used,
+ * then formatting 123 would result in "123+" in the locale <em>en-US</em>.
*
* <p>
- * This method should be used <em>instead of</em> setting the positive prefix/suffix. The behavior is undefined if
- * alwaysShowPlusSign is set but the positive prefix/suffix already contains a plus sign.
+ * This method should be used <em>instead of</em> setting the positive prefix/suffix. The behavior is
+ * undefined if alwaysShowPlusSign is set but the positive prefix/suffix already contains a plus
+ * sign.
*
* @param signAlwaysShown
* Whether positive numbers should display a plus sign.
}
/**
- * Appends a string containing properties that differ from the default, but without being surrounded by
- * <Properties>.
+ * Appends a string containing properties that differ from the default, but without being surrounded
+ * by <Properties>.
*/
public void toStringBare(StringBuilder result) {
Field[] fields = DecimalFormatProperties.class.getDeclaredFields();
}
/**
- * Custom serialization: save fields along with their name, so that fields can be easily added in the future in any
- * order. Only save fields that differ from their default value.
+ * Custom serialization: save fields along with their name, so that fields can be easily added in the
+ * future in any order. Only save fields that differ from their default value.
*/
private void writeObject(ObjectOutputStream oos) throws IOException {
writeObjectImpl(oos);
* An interface representing a number to be processed by the decimal formatting pipeline. Includes
* methods for rounding, plural rules, and decimal digit extraction.
*
- * <p>By design, this is NOT IMMUTABLE and NOT THREAD SAFE. It is intended to be an intermediate
- * object holding state during a pass through the decimal formatting pipeline.
+ * <p>
+ * By design, this is NOT IMMUTABLE and NOT THREAD SAFE. It is intended to be an intermediate object
+ * holding state during a pass through the decimal formatting pipeline.
*
- * <p>Implementations of this interface are free to use any internal storage mechanism.
+ * <p>
+ * Implementations of this interface are free to use any internal storage mechanism.
*
- * <p>TODO: Should I change this to an abstract class so that logic for min/max digits doesn't need
- * to be copied to every implementation?
+ * <p>
+ * TODO: Should I change this to an abstract class so that logic for min/max digits doesn't need to be
+ * copied to every implementation?
*/
public interface DecimalQuantity extends PluralRules.IFixedDecimal {
- /**
- * Sets the minimum and maximum integer digits that this {@link DecimalQuantity} should generate.
- * This method does not perform rounding.
- *
- * @param minInt The minimum number of integer digits.
- * @param maxInt The maximum number of integer digits.
- */
- public void setIntegerLength(int minInt, int maxInt);
-
- /**
- * Sets the minimum and maximum fraction digits that this {@link DecimalQuantity} should generate.
- * This method does not perform rounding.
- *
- * @param minFrac The minimum number of fraction digits.
- * @param maxFrac The maximum number of fraction digits.
- */
- public void setFractionLength(int minFrac, int maxFrac);
-
- /**
- * Rounds the number to a specified interval, such as 0.05.
- *
- * <p>If rounding to a power of ten, use the more efficient {@link #roundToMagnitude} instead.
- *
- * @param roundingInterval The increment to which to round.
- * @param mathContext The {@link MathContext} to use if rounding is necessary. Undefined behavior
- * if null.
- */
- public void roundToIncrement(BigDecimal roundingInterval, MathContext mathContext);
-
- /**
- * Rounds the number to a specified magnitude (power of ten).
- *
- * @param roundingMagnitude The power of ten to which to round. For example, a value of -2 will
- * round to 2 decimal places.
- * @param mathContext The {@link MathContext} to use if rounding is necessary. Undefined behavior
- * if null.
- */
- public void roundToMagnitude(int roundingMagnitude, MathContext mathContext);
-
- /**
- * Rounds the number to an infinite number of decimal points. This has no effect except for
- * forcing the double in {@link DecimalQuantity_AbstractBCD} to adopt its exact representation.
- */
- public void roundToInfinity();
-
- /**
- * Multiply the internal value.
- *
- * @param multiplicand The value by which to multiply.
- */
- public void multiplyBy(BigDecimal multiplicand);
-
- /**
- * Scales the number by a power of ten. For example, if the value is currently "1234.56", calling
- * this method with delta=-3 will change the value to "1.23456".
- *
- * @param delta The number of magnitudes of ten to change by.
- */
- public void adjustMagnitude(int delta);
-
- /**
- * @return The power of ten corresponding to the most significant nonzero digit.
- * @throws ArithmeticException If the value represented is zero.
- */
- public int getMagnitude() throws ArithmeticException;
-
- /** @return Whether the value represented by this {@link DecimalQuantity} is zero. */
- public boolean isZero();
-
- /** @return Whether the value represented by this {@link DecimalQuantity} is less than zero. */
- public boolean isNegative();
-
- /** @return Whether the value represented by this {@link DecimalQuantity} is infinite. */
- @Override
- public boolean isInfinite();
-
- /** @return Whether the value represented by this {@link DecimalQuantity} is not a number. */
- @Override
- public boolean isNaN();
-
- /** @return The value contained in this {@link DecimalQuantity} approximated as a double. */
- public double toDouble();
-
- public BigDecimal toBigDecimal();
-
- public void setToBigDecimal(BigDecimal input);
-
- public int maxRepresentableDigits();
-
- // TODO: Should this method be removed, since DecimalQuantity implements IFixedDecimal now?
- /**
- * Computes the plural form for this number based on the specified set of rules.
- *
- * @param rules A {@link PluralRules} object representing the set of rules.
- * @return The {@link StandardPlural} according to the PluralRules. If the plural form is not in
- * the set of standard plurals, {@link StandardPlural#OTHER} is returned instead.
- */
- public StandardPlural getStandardPlural(PluralRules rules);
-
- /**
- * Gets the digit at the specified magnitude. For example, if the represented number is 12.3,
- * getDigit(-1) returns 3, since 3 is the digit corresponding to 10^-1.
- *
- * @param magnitude The magnitude of the digit.
- * @return The digit at the specified magnitude.
- */
- public byte getDigit(int magnitude);
-
- /**
- * Gets the largest power of ten that needs to be displayed. The value returned by this function
- * will be bounded between minInt and maxInt.
- *
- * @return The highest-magnitude digit to be displayed.
- */
- public int getUpperDisplayMagnitude();
-
- /**
- * Gets the smallest power of ten that needs to be displayed. The value returned by this function
- * will be bounded between -minFrac and -maxFrac.
- *
- * @return The lowest-magnitude digit to be displayed.
- */
- public int getLowerDisplayMagnitude();
-
- /**
- * Returns the string in "plain" format (no exponential notation) using ASCII digits.
- */
- public String toPlainString();
-
- /**
- * Like clone, but without the restrictions of the Cloneable interface clone.
- *
- * @return A copy of this instance which can be mutated without affecting this instance.
- */
- public DecimalQuantity createCopy();
-
- /**
- * Sets this instance to be equal to another instance.
- *
- * @param other The instance to copy from.
- */
- public void copyFrom(DecimalQuantity other);
-
- /** This method is for internal testing only. */
- public long getPositionFingerprint();
-
- /**
- * If the given {@link FieldPosition} is a {@link UFieldPosition}, populates it with the fraction
- * length and fraction long value. If the argument is not a {@link UFieldPosition}, nothing
- * happens.
- *
- * @param fp The {@link UFieldPosition} to populate.
- */
- public void populateUFieldPosition(FieldPosition fp);
+ /**
+ * Sets the minimum and maximum integer digits that this {@link DecimalQuantity} should generate.
+ * This method does not perform rounding.
+ *
+ * @param minInt
+ * The minimum number of integer digits.
+ * @param maxInt
+ * The maximum number of integer digits.
+ */
+ public void setIntegerLength(int minInt, int maxInt);
+
+ /**
+ * Sets the minimum and maximum fraction digits that this {@link DecimalQuantity} should generate.
+ * This method does not perform rounding.
+ *
+ * @param minFrac
+ * The minimum number of fraction digits.
+ * @param maxFrac
+ * The maximum number of fraction digits.
+ */
+ public void setFractionLength(int minFrac, int maxFrac);
+
+ /**
+ * Rounds the number to a specified interval, such as 0.05.
+ *
+ * <p>
+ * If rounding to a power of ten, use the more efficient {@link #roundToMagnitude} instead.
+ *
+ * @param roundingInterval
+ * The increment to which to round.
+ * @param mathContext
+ * The {@link MathContext} to use if rounding is necessary. Undefined behavior if null.
+ */
+ public void roundToIncrement(BigDecimal roundingInterval, MathContext mathContext);
+
+ /**
+ * Rounds the number to a specified magnitude (power of ten).
+ *
+ * @param roundingMagnitude
+ * The power of ten to which to round. For example, a value of -2 will round to 2 decimal
+ * places.
+ * @param mathContext
+ * The {@link MathContext} to use if rounding is necessary. Undefined behavior if null.
+ */
+ public void roundToMagnitude(int roundingMagnitude, MathContext mathContext);
+
+ /**
+ * Rounds the number to an infinite number of decimal points. This has no effect except for forcing
+ * the double in {@link DecimalQuantity_AbstractBCD} to adopt its exact representation.
+ */
+ public void roundToInfinity();
+
+ /**
+ * Multiply the internal value.
+ *
+ * @param multiplicand
+ * The value by which to multiply.
+ */
+ public void multiplyBy(BigDecimal multiplicand);
+
+ /**
+ * Scales the number by a power of ten. For example, if the value is currently "1234.56", calling
+ * this method with delta=-3 will change the value to "1.23456".
+ *
+ * @param delta
+ * The number of magnitudes of ten to change by.
+ */
+ public void adjustMagnitude(int delta);
+
+ /**
+ * @return The power of ten corresponding to the most significant nonzero digit.
+ * @throws ArithmeticException
+ * If the value represented is zero.
+ */
+ public int getMagnitude() throws ArithmeticException;
+
+ /** @return Whether the value represented by this {@link DecimalQuantity} is zero. */
+ public boolean isZero();
+
+ /** @return Whether the value represented by this {@link DecimalQuantity} is less than zero. */
+ public boolean isNegative();
+
+ /** @return Whether the value represented by this {@link DecimalQuantity} is infinite. */
+ @Override
+ public boolean isInfinite();
+
+ /** @return Whether the value represented by this {@link DecimalQuantity} is not a number. */
+ @Override
+ public boolean isNaN();
+
+ /** @return The value contained in this {@link DecimalQuantity} approximated as a double. */
+ public double toDouble();
+
+ public BigDecimal toBigDecimal();
+
+ public void setToBigDecimal(BigDecimal input);
+
+ public int maxRepresentableDigits();
+
+ // TODO: Should this method be removed, since DecimalQuantity implements IFixedDecimal now?
+ /**
+ * Computes the plural form for this number based on the specified set of rules.
+ *
+ * @param rules
+ * A {@link PluralRules} object representing the set of rules.
+ * @return The {@link StandardPlural} according to the PluralRules. If the plural form is not in the
+ * set of standard plurals, {@link StandardPlural#OTHER} is returned instead.
+ */
+ public StandardPlural getStandardPlural(PluralRules rules);
+
+ /**
+ * Gets the digit at the specified magnitude. For example, if the represented number is 12.3,
+ * getDigit(-1) returns 3, since 3 is the digit corresponding to 10^-1.
+ *
+ * @param magnitude
+ * The magnitude of the digit.
+ * @return The digit at the specified magnitude.
+ */
+ public byte getDigit(int magnitude);
+
+ /**
+ * Gets the largest power of ten that needs to be displayed. The value returned by this function will
+ * be bounded between minInt and maxInt.
+ *
+ * @return The highest-magnitude digit to be displayed.
+ */
+ public int getUpperDisplayMagnitude();
+
+ /**
+ * Gets the smallest power of ten that needs to be displayed. The value returned by this function
+ * will be bounded between -minFrac and -maxFrac.
+ *
+ * @return The lowest-magnitude digit to be displayed.
+ */
+ public int getLowerDisplayMagnitude();
+
+ /**
+ * Returns the string in "plain" format (no exponential notation) using ASCII digits.
+ */
+ public String toPlainString();
+
+ /**
+ * Like clone, but without the restrictions of the Cloneable interface clone.
+ *
+ * @return A copy of this instance which can be mutated without affecting this instance.
+ */
+ public DecimalQuantity createCopy();
+
+ /**
+ * Sets this instance to be equal to another instance.
+ *
+ * @param other
+ * The instance to copy from.
+ */
+ public void copyFrom(DecimalQuantity other);
+
+ /** This method is for internal testing only. */
+ public long getPositionFingerprint();
+
+ /**
+ * If the given {@link FieldPosition} is a {@link UFieldPosition}, populates it with the fraction
+ * length and fraction long value. If the argument is not a {@link UFieldPosition}, nothing happens.
+ *
+ * @param fp
+ * The {@link UFieldPosition} to populate.
+ */
+ public void populateUFieldPosition(FieldPosition fp);
}
*/
public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
- /**
- * The power of ten corresponding to the least significant digit in the BCD. For example, if this
- * object represents the number "3.14", the BCD will be "0x314" and the scale will be -2.
- *
- * <p>Note that in {@link java.math.BigDecimal}, the scale is defined differently: the number of
- * digits after the decimal place, which is the negative of our definition of scale.
- */
- protected int scale;
-
- /**
- * The number of digits in the BCD. For example, "1007" has BCD "0x1007" and precision 4. The
- * maximum precision is 16 since a long can hold only 16 digits.
- *
- * <p>This value must be re-calculated whenever the value in bcd changes by using {@link
- * #computePrecisionAndCompact()}.
- */
- protected int precision;
-
- /**
- * A bitmask of properties relating to the number represented by this object.
- *
- * @see #NEGATIVE_FLAG
- * @see #INFINITY_FLAG
- * @see #NAN_FLAG
- */
- protected byte flags;
-
- protected static final int NEGATIVE_FLAG = 1;
- protected static final int INFINITY_FLAG = 2;
- protected static final int NAN_FLAG = 4;
-
- // The following three fields relate to the double-to-ascii fast path algorithm.
- // When a double is given to DecimalQuantityBCD, it is converted to using a fast algorithm. The
- // fast algorithm guarantees correctness to only the first ~12 digits of the double. The process
- // of rounding the number ensures that the converted digits are correct, falling back to a slow-
- // path algorithm if required. Therefore, if a DecimalQuantity is constructed from a double, it
- // is *required* that roundToMagnitude(), roundToIncrement(), or roundToInfinity() is called. If
- // you don't round, assertions will fail in certain other methods if you try calling them.
-
- /**
- * The original number provided by the user and which is represented in BCD. Used when we need to
- * re-compute the BCD for an exact double representation.
- */
- protected double origDouble;
-
- /**
- * The change in magnitude relative to the original double. Used when we need to re-compute the
- * BCD for an exact double representation.
- */
- protected int origDelta;
-
- /**
- * Whether the value in the BCD comes from the double fast path without having been rounded to
- * ensure correctness
- */
- protected boolean isApproximate;
-
- // Four positions: left optional '(', left required '[', right required ']', right optional ')'.
- // These four positions determine which digits are displayed in the output string. They do NOT
- // affect rounding. These positions are internal-only and can be specified only by the public
- // endpoints like setFractionLength, setIntegerLength, and setSignificantDigits, among others.
- //
- // * Digits between lReqPos and rReqPos are in the "required zone" and are always displayed.
- // * Digits between lOptPos and rOptPos but outside the required zone are in the "optional zone"
- // and are displayed unless they are trailing off the left or right edge of the number and
- // have a numerical value of zero. In order to be "trailing", the digits need to be beyond
- // the decimal point in their respective directions.
- // * Digits outside of the "optional zone" are never displayed.
- //
- // See the table below for illustrative examples.
- //
- // +---------+---------+---------+---------+------------+------------------------+--------------+
- // | lOptPos | lReqPos | rReqPos | rOptPos | number | positions | en-US string |
- // +---------+---------+---------+---------+------------+------------------------+--------------+
- // | 5 | 2 | -1 | -5 | 1234.567 | ( 12[34.5]67 ) | 1,234.567 |
- // | 3 | 2 | -1 | -5 | 1234.567 | 1(2[34.5]67 ) | 234.567 |
- // | 3 | 2 | -1 | -2 | 1234.567 | 1(2[34.5]6)7 | 234.56 |
- // | 6 | 4 | 2 | -5 | 123456789. | 123(45[67]89. ) | 456,789. |
- // | 6 | 4 | 2 | 1 | 123456789. | 123(45[67]8)9. | 456,780. |
- // | -1 | -1 | -3 | -4 | 0.123456 | 0.1([23]4)56 | .0234 |
- // | 6 | 4 | -2 | -2 | 12.3 | ( [ 12.3 ]) | 0012.30 |
- // +---------+---------+---------+---------+------------+------------------------+--------------+
- //
- protected int lOptPos = Integer.MAX_VALUE;
- protected int lReqPos = 0;
- protected int rReqPos = 0;
- protected int rOptPos = Integer.MIN_VALUE;
-
- @Override
- public void copyFrom(DecimalQuantity _other) {
- copyBcdFrom(_other);
- DecimalQuantity_AbstractBCD other = (DecimalQuantity_AbstractBCD) _other;
- lOptPos = other.lOptPos;
- lReqPos = other.lReqPos;
- rReqPos = other.rReqPos;
- rOptPos = other.rOptPos;
- scale = other.scale;
- precision = other.precision;
- flags = other.flags;
- origDouble = other.origDouble;
- origDelta = other.origDelta;
- isApproximate = other.isApproximate;
- }
-
- public DecimalQuantity_AbstractBCD clear() {
- lOptPos = Integer.MAX_VALUE;
- lReqPos = 0;
- rReqPos = 0;
- rOptPos = Integer.MIN_VALUE;
- flags = 0;
- setBcdToZero(); // sets scale, precision, hasDouble, origDouble, origDelta, and BCD data
- return this;
- }
-
- @Override
- public void setIntegerLength(int minInt, int maxInt) {
- // Validation should happen outside of DecimalQuantity, e.g., in the Rounder class.
- assert minInt >= 0;
- assert maxInt >= minInt;
-
- // Save values into internal state
- // Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE
- lOptPos = maxInt;
- lReqPos = minInt;
- }
-
- @Override
- public void setFractionLength(int minFrac, int maxFrac) {
- // Validation should happen outside of DecimalQuantity, e.g., in the Rounder class.
- assert minFrac >= 0;
- assert maxFrac >= minFrac;
-
- // Save values into internal state
- // Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE
- rReqPos = -minFrac;
- rOptPos = -maxFrac;
- }
-
- @Override
- public long getPositionFingerprint() {
- long fingerprint = 0;
- fingerprint ^= lOptPos;
- fingerprint ^= (lReqPos << 16);
- fingerprint ^= ((long) rReqPos << 32);
- fingerprint ^= ((long) rOptPos << 48);
- return fingerprint;
- }
-
- @Override
- public void roundToIncrement(BigDecimal roundingIncrement, MathContext mathContext) {
- // TODO: Avoid converting back and forth to BigDecimal.
- BigDecimal temp = toBigDecimal();
- temp =
- temp.divide(roundingIncrement, 0, mathContext.getRoundingMode())
- .multiply(roundingIncrement)
- .round(mathContext);
- if (temp.signum() == 0) {
- setBcdToZero(); // keeps negative flag for -0.0
- } else {
- setToBigDecimal(temp);
- }
- }
-
- @Override
- public void multiplyBy(BigDecimal multiplicand) {
- if (isInfinite() || isZero() || isNaN()) {
- return;
- }
- BigDecimal temp = toBigDecimal();
- temp = temp.multiply(multiplicand);
- setToBigDecimal(temp);
- }
-
- @Override
- public int getMagnitude() throws ArithmeticException {
- if (precision == 0) {
- throw new ArithmeticException("Magnitude is not well-defined for zero");
- } else {
- return scale + precision - 1;
- }
- }
-
- @Override
- public void adjustMagnitude(int delta) {
- if (precision != 0) {
- scale += delta;
- origDelta += delta;
- }
- }
-
- @Override
- public StandardPlural getStandardPlural(PluralRules rules) {
- if (rules == null) {
- // Fail gracefully if the user didn't provide a PluralRules
- return StandardPlural.OTHER;
- } else {
- @SuppressWarnings("deprecation")
- String ruleString = rules.select(this);
- return StandardPlural.orOtherFromString(ruleString);
- }
- }
-
- @Override
- public double getPluralOperand(Operand operand) {
- // If this assertion fails, you need to call roundToInfinity() or some other rounding method.
- // See the comment at the top of this file explaining the "isApproximate" field.
- assert !isApproximate;
-
- switch (operand) {
- case i:
- return toLong();
- case f:
- return toFractionLong(true);
- case t:
- return toFractionLong(false);
- case v:
- return fractionCount();
- case w:
- return fractionCountWithoutTrailingZeros();
- default:
- return Math.abs(toDouble());
- }
- }
-
- @Override
- public void populateUFieldPosition(FieldPosition fp) {
- if (fp instanceof UFieldPosition) {
- ((UFieldPosition) fp)
- .setFractionDigits((int) getPluralOperand(Operand.v), (long) getPluralOperand(Operand.f));
- }
- }
-
- @Override
- public int getUpperDisplayMagnitude() {
- // If this assertion fails, you need to call roundToInfinity() or some other rounding method.
- // See the comment at the top of this file explaining the "isApproximate" field.
- assert !isApproximate;
-
- int magnitude = scale + precision;
- int result = (lReqPos > magnitude) ? lReqPos : (lOptPos < magnitude) ? lOptPos : magnitude;
- return result - 1;
- }
-
- @Override
- public int getLowerDisplayMagnitude() {
- // If this assertion fails, you need to call roundToInfinity() or some other rounding method.
- // See the comment at the top of this file explaining the "isApproximate" field.
- assert !isApproximate;
-
- int magnitude = scale;
- int result = (rReqPos < magnitude) ? rReqPos : (rOptPos > magnitude) ? rOptPos : magnitude;
- return result;
- }
-
- @Override
- public byte getDigit(int magnitude) {
- // If this assertion fails, you need to call roundToInfinity() or some other rounding method.
- // See the comment at the top of this file explaining the "isApproximate" field.
- assert !isApproximate;
-
- return getDigitPos(magnitude - scale);
- }
-
- private int fractionCount() {
- return -getLowerDisplayMagnitude();
- }
-
- private int fractionCountWithoutTrailingZeros() {
- return Math.max(-scale, 0);
- }
-
- @Override
- public boolean isNegative() {
- return (flags & NEGATIVE_FLAG) != 0;
- }
-
- @Override
- public boolean isInfinite() {
- return (flags & INFINITY_FLAG) != 0;
- }
-
- @Override
- public boolean isNaN() {
- return (flags & NAN_FLAG) != 0;
- }
-
- @Override
- public boolean isZero() {
- return precision == 0;
- }
-
- public void setToInt(int n) {
- setBcdToZero();
- flags = 0;
- if (n < 0) {
- flags |= NEGATIVE_FLAG;
- n = -n;
- }
- if (n != 0) {
- _setToInt(n);
- compact();
- }
- }
-
- private void _setToInt(int n) {
- if (n == Integer.MIN_VALUE) {
- readLongToBcd(-(long) n);
- } else {
- readIntToBcd(n);
- }
- }
-
- public void setToLong(long n) {
- setBcdToZero();
- flags = 0;
- if (n < 0) {
- flags |= NEGATIVE_FLAG;
- n = -n;
- }
- if (n != 0) {
- _setToLong(n);
- compact();
- }
- }
-
- private void _setToLong(long n) {
- if (n == Long.MIN_VALUE) {
- readBigIntegerToBcd(BigInteger.valueOf(n).negate());
- } else if (n <= Integer.MAX_VALUE) {
- readIntToBcd((int) n);
- } else {
- readLongToBcd(n);
- }
- }
-
- public void setToBigInteger(BigInteger n) {
- setBcdToZero();
- flags = 0;
- if (n.signum() == -1) {
- flags |= NEGATIVE_FLAG;
- n = n.negate();
- }
- if (n.signum() != 0) {
- _setToBigInteger(n);
- compact();
- }
- }
-
- private void _setToBigInteger(BigInteger n) {
- if (n.bitLength() < 32) {
- readIntToBcd(n.intValue());
- } else if (n.bitLength() < 64) {
- readLongToBcd(n.longValue());
- } else {
- readBigIntegerToBcd(n);
- }
- }
-
- /**
- * Sets the internal BCD state to represent the value in the given double.
- *
- * @param n The value to consume.
- */
- public void setToDouble(double n) {
- setBcdToZero();
- flags = 0;
- // Double.compare() handles +0.0 vs -0.0
- if (Double.compare(n, 0.0) < 0) {
- flags |= NEGATIVE_FLAG;
- n = -n;
- }
- if (Double.isNaN(n)) {
- flags |= NAN_FLAG;
- } else if (Double.isInfinite(n)) {
- flags |= INFINITY_FLAG;
- } else if (n != 0) {
- _setToDoubleFast(n);
- compact();
- }
- }
-
- private static final double[] DOUBLE_MULTIPLIERS = {
- 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16,
- 1e17, 1e18, 1e19, 1e20, 1e21
- };
-
- /**
- * Uses double multiplication and division to get the number into integer space before converting
- * to digits. Since double arithmetic is inexact, the resulting digits may not be accurate.
- */
- private void _setToDoubleFast(double n) {
- isApproximate = true;
- origDouble = n;
- origDelta = 0;
-
- // NOTE: Unlike ICU4C, doubles are always IEEE 754 doubles.
- long ieeeBits = Double.doubleToLongBits(n);
- int exponent = (int) ((ieeeBits & 0x7ff0000000000000L) >> 52) - 0x3ff;
-
- // Not all integers can be represented exactly for exponent > 52
- if (exponent <= 52 && (long) n == n) {
- _setToLong((long) n);
- return;
- }
-
- // 3.3219... is log2(10)
- int fracLength = (int) ((52 - exponent) / 3.32192809489);
- if (fracLength >= 0) {
- int i = fracLength;
- // 1e22 is the largest exact double.
- for (; i >= 22; i -= 22) n *= 1e22;
- n *= DOUBLE_MULTIPLIERS[i];
- } else {
- int i = fracLength;
- // 1e22 is the largest exact double.
- for (; i <= -22; i += 22) n /= 1e22;
- n /= DOUBLE_MULTIPLIERS[-i];
- }
- long result = Math.round(n);
- if (result != 0) {
- _setToLong(result);
- scale -= fracLength;
- }
- }
-
- /**
- * Uses Double.toString() to obtain an exact accurate representation of the double, overwriting it
- * into the BCD. This method can be called at any point after {@link #_setToDoubleFast} while
- * {@link #isApproximate} is still true.
- */
- private void convertToAccurateDouble() {
- double n = origDouble;
- assert n != 0;
- int delta = origDelta;
- setBcdToZero();
-
- // Call the slow oracle function (Double.toString in Java, sprintf in C++).
- String dstr = Double.toString(n);
-
- if (dstr.indexOf('E') != -1) {
- // Case 1: Exponential notation.
- assert dstr.indexOf('.') == 1;
- int expPos = dstr.indexOf('E');
- _setToLong(Long.parseLong(dstr.charAt(0) + dstr.substring(2, expPos)));
- scale += Integer.parseInt(dstr.substring(expPos + 1)) - (expPos - 1) + 1;
- } else if (dstr.charAt(0) == '0') {
- // Case 2: Fraction-only number.
- assert dstr.indexOf('.') == 1;
- _setToLong(Long.parseLong(dstr.substring(2)));
- scale += 2 - dstr.length();
- } else if (dstr.charAt(dstr.length() - 1) == '0') {
- // Case 3: Integer-only number.
- // Note: this path should not normally happen, because integer-only numbers are captured
- // before the approximate double logic is performed.
- assert dstr.indexOf('.') == dstr.length() - 2;
- assert dstr.length() - 2 <= 18;
- _setToLong(Long.parseLong(dstr.substring(0, dstr.length() - 2)));
- // no need to adjust scale
- } else {
- // Case 4: Number with both a fraction and an integer.
- int decimalPos = dstr.indexOf('.');
- _setToLong(Long.parseLong(dstr.substring(0, decimalPos) + dstr.substring(decimalPos + 1)));
- scale += decimalPos - dstr.length() + 1;
- }
-
- scale += delta;
- compact();
- explicitExactDouble = true;
- }
-
- /**
- * Whether this {@link DecimalQuantity_DualStorageBCD} has been explicitly converted to an exact double. true if
- * backed by a double that was explicitly converted via convertToAccurateDouble; false otherwise.
- * Used for testing.
- *
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated public boolean explicitExactDouble = false;
-
- /**
- * Sets the internal BCD state to represent the value in the given BigDecimal.
- *
- * @param n The value to consume.
- */
- @Override
- public void setToBigDecimal(BigDecimal n) {
- setBcdToZero();
- flags = 0;
- if (n.signum() == -1) {
- flags |= NEGATIVE_FLAG;
- n = n.negate();
- }
- if (n.signum() != 0) {
- _setToBigDecimal(n);
- compact();
- }
- }
-
- private void _setToBigDecimal(BigDecimal n) {
- int fracLength = n.scale();
- n = n.scaleByPowerOfTen(fracLength);
- BigInteger bi = n.toBigInteger();
- _setToBigInteger(bi);
- scale -= fracLength;
- }
-
- /**
- * Returns a long approximating the internal BCD. A long can only represent the integral part of
- * the number.
- *
- * @return A double representation of the internal BCD.
- */
- protected long toLong() {
- long result = 0L;
- for (int magnitude = scale + precision - 1; magnitude >= 0; magnitude--) {
- result = result * 10 + getDigitPos(magnitude - scale);
- }
- return result;
- }
-
- /**
- * This returns a long representing the fraction digits of the number, as required by PluralRules.
- * For example, if we represent the number "1.20" (including optional and required digits), then
- * this function returns "20" if includeTrailingZeros is true or "2" if false.
- */
- protected long toFractionLong(boolean includeTrailingZeros) {
- long result = 0L;
- int magnitude = -1;
- for (;
- (magnitude >= scale || (includeTrailingZeros && magnitude >= rReqPos))
- && magnitude >= rOptPos;
- magnitude--) {
- result = result * 10 + getDigitPos(magnitude - scale);
- }
- return result;
- }
-
- /**
- * Returns a double approximating the internal BCD. The double may not retain all of the
- * information encoded in the BCD if the BCD represents a number out of range of a double.
- *
- * @return A double representation of the internal BCD.
- */
- @Override
- public double toDouble() {
- if (isApproximate) {
- return toDoubleFromOriginal();
- }
-
- if (isNaN()) {
- return Double.NaN;
- } else if (isInfinite()) {
- return isNegative() ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
- }
-
- long tempLong = 0L;
- int lostDigits = precision - Math.min(precision, 17);
- for (int shift = precision - 1; shift >= lostDigits; shift--) {
- tempLong = tempLong * 10 + getDigitPos(shift);
- }
- double result = tempLong;
- int _scale = scale + lostDigits;
- if (_scale >= 0) {
- // 1e22 is the largest exact double.
- int i = _scale;
- for (; i >= 22; i -= 22) result *= 1e22;
- result *= DOUBLE_MULTIPLIERS[i];
- } else {
- // 1e22 is the largest exact double.
- int i = _scale;
- for (; i <= -22; i += 22) result /= 1e22;
- result /= DOUBLE_MULTIPLIERS[-i];
- }
- if (isNegative()) result = -result;
- return result;
- }
-
- @Override
- public BigDecimal toBigDecimal() {
- if (isApproximate) {
- // Converting to a BigDecimal requires Double.toString().
- convertToAccurateDouble();
- }
- return bcdToBigDecimal();
- }
-
- protected double toDoubleFromOriginal() {
- double result = origDouble;
- int delta = origDelta;
- if (delta >= 0) {
- // 1e22 is the largest exact double.
- for (; delta >= 22; delta -= 22) result *= 1e22;
- result *= DOUBLE_MULTIPLIERS[delta];
- } else {
- // 1e22 is the largest exact double.
- for (; delta <= -22; delta += 22) result /= 1e22;
- result /= DOUBLE_MULTIPLIERS[-delta];
- }
- if (isNegative()) result *= -1;
- return result;
- }
-
- private static int safeSubtract(int a, int b) {
- int diff = a - b;
- if (b < 0 && diff < a) return Integer.MAX_VALUE;
- if (b > 0 && diff > a) return Integer.MIN_VALUE;
- return diff;
- }
-
- private static final int SECTION_LOWER_EDGE = -1;
- private static final int SECTION_UPPER_EDGE = -2;
-
- @Override
- public void roundToMagnitude(int magnitude, MathContext mathContext) {
- // The position in the BCD at which rounding will be performed; digits to the right of position
- // will be rounded away.
- // TODO: Andy: There was a test failure because of integer overflow here. Should I do
- // "safe subtraction" everywhere in the code? What's the nicest way to do it?
- int position = safeSubtract(magnitude, scale);
-
- // Enforce the number of digits required by the MathContext.
- int _mcPrecision = mathContext.getPrecision();
- if (magnitude == Integer.MAX_VALUE
- || (_mcPrecision > 0 && precision - position > _mcPrecision)) {
- position = precision - _mcPrecision;
- }
-
- if (position <= 0 && !isApproximate) {
- // All digits are to the left of the rounding magnitude.
- } else if (precision == 0) {
- // No rounding for zero.
- } else {
- // Perform rounding logic.
- // "leading" = most significant digit to the right of rounding
- // "trailing" = least significant digit to the left of rounding
- byte leadingDigit = getDigitPos(safeSubtract(position, 1));
- byte trailingDigit = getDigitPos(position);
-
- // Compute which section of the number we are in.
- // EDGE means we are at the bottom or top edge, like 1.000 or 1.999 (used by doubles)
- // LOWER means we are between the bottom edge and the midpoint, like 1.391
- // MIDPOINT means we are exactly in the middle, like 1.500
- // UPPER means we are between the midpoint and the top edge, like 1.916
- int section = RoundingUtils.SECTION_MIDPOINT;
- if (!isApproximate) {
- if (leadingDigit < 5) {
- section = RoundingUtils.SECTION_LOWER;
- } else if (leadingDigit > 5) {
- section = RoundingUtils.SECTION_UPPER;
+ /**
+ * The power of ten corresponding to the least significant digit in the BCD. For example, if this
+ * object represents the number "3.14", the BCD will be "0x314" and the scale will be -2.
+ *
+ * <p>
+ * Note that in {@link java.math.BigDecimal}, the scale is defined differently: the number of digits
+ * after the decimal place, which is the negative of our definition of scale.
+ */
+ protected int scale;
+
+ /**
+ * The number of digits in the BCD. For example, "1007" has BCD "0x1007" and precision 4. The maximum
+ * precision is 16 since a long can hold only 16 digits.
+ *
+ * <p>
+ * This value must be re-calculated whenever the value in bcd changes by using
+ * {@link #computePrecisionAndCompact()}.
+ */
+ protected int precision;
+
+ /**
+ * A bitmask of properties relating to the number represented by this object.
+ *
+ * @see #NEGATIVE_FLAG
+ * @see #INFINITY_FLAG
+ * @see #NAN_FLAG
+ */
+ protected byte flags;
+
+ protected static final int NEGATIVE_FLAG = 1;
+ protected static final int INFINITY_FLAG = 2;
+ protected static final int NAN_FLAG = 4;
+
+ // The following three fields relate to the double-to-ascii fast path algorithm.
+ // When a double is given to DecimalQuantityBCD, it is converted to using a fast algorithm. The
+ // fast algorithm guarantees correctness to only the first ~12 digits of the double. The process
+ // of rounding the number ensures that the converted digits are correct, falling back to a slow-
+ // path algorithm if required. Therefore, if a DecimalQuantity is constructed from a double, it
+ // is *required* that roundToMagnitude(), roundToIncrement(), or roundToInfinity() is called. If
+ // you don't round, assertions will fail in certain other methods if you try calling them.
+
+ /**
+ * The original number provided by the user and which is represented in BCD. Used when we need to
+ * re-compute the BCD for an exact double representation.
+ */
+ protected double origDouble;
+
+ /**
+ * The change in magnitude relative to the original double. Used when we need to re-compute the BCD
+ * for an exact double representation.
+ */
+ protected int origDelta;
+
+ /**
+ * Whether the value in the BCD comes from the double fast path without having been rounded to ensure
+ * correctness
+ */
+ protected boolean isApproximate;
+
+ // Four positions: left optional '(', left required '[', right required ']', right optional ')'.
+ // These four positions determine which digits are displayed in the output string. They do NOT
+ // affect rounding. These positions are internal-only and can be specified only by the public
+ // endpoints like setFractionLength, setIntegerLength, and setSignificantDigits, among others.
+ //
+ // * Digits between lReqPos and rReqPos are in the "required zone" and are always displayed.
+ // * Digits between lOptPos and rOptPos but outside the required zone are in the "optional zone"
+ // and are displayed unless they are trailing off the left or right edge of the number and
+ // have a numerical value of zero. In order to be "trailing", the digits need to be beyond
+ // the decimal point in their respective directions.
+ // * Digits outside of the "optional zone" are never displayed.
+ //
+ // See the table below for illustrative examples.
+ //
+ // +---------+---------+---------+---------+------------+------------------------+--------------+
+ // | lOptPos | lReqPos | rReqPos | rOptPos | number | positions | en-US string |
+ // +---------+---------+---------+---------+------------+------------------------+--------------+
+ // | 5 | 2 | -1 | -5 | 1234.567 | ( 12[34.5]67 ) | 1,234.567 |
+ // | 3 | 2 | -1 | -5 | 1234.567 | 1(2[34.5]67 ) | 234.567 |
+ // | 3 | 2 | -1 | -2 | 1234.567 | 1(2[34.5]6)7 | 234.56 |
+ // | 6 | 4 | 2 | -5 | 123456789. | 123(45[67]89. ) | 456,789. |
+ // | 6 | 4 | 2 | 1 | 123456789. | 123(45[67]8)9. | 456,780. |
+ // | -1 | -1 | -3 | -4 | 0.123456 | 0.1([23]4)56 | .0234 |
+ // | 6 | 4 | -2 | -2 | 12.3 | ( [ 12.3 ]) | 0012.30 |
+ // +---------+---------+---------+---------+------------+------------------------+--------------+
+ //
+ protected int lOptPos = Integer.MAX_VALUE;
+ protected int lReqPos = 0;
+ protected int rReqPos = 0;
+ protected int rOptPos = Integer.MIN_VALUE;
+
+ @Override
+ public void copyFrom(DecimalQuantity _other) {
+ copyBcdFrom(_other);
+ DecimalQuantity_AbstractBCD other = (DecimalQuantity_AbstractBCD) _other;
+ lOptPos = other.lOptPos;
+ lReqPos = other.lReqPos;
+ rReqPos = other.rReqPos;
+ rOptPos = other.rOptPos;
+ scale = other.scale;
+ precision = other.precision;
+ flags = other.flags;
+ origDouble = other.origDouble;
+ origDelta = other.origDelta;
+ isApproximate = other.isApproximate;
+ }
+
+ public DecimalQuantity_AbstractBCD clear() {
+ lOptPos = Integer.MAX_VALUE;
+ lReqPos = 0;
+ rReqPos = 0;
+ rOptPos = Integer.MIN_VALUE;
+ flags = 0;
+ setBcdToZero(); // sets scale, precision, hasDouble, origDouble, origDelta, and BCD data
+ return this;
+ }
+
+ @Override
+ public void setIntegerLength(int minInt, int maxInt) {
+ // Validation should happen outside of DecimalQuantity, e.g., in the Rounder class.
+ assert minInt >= 0;
+ assert maxInt >= minInt;
+
+ // Save values into internal state
+ // Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE
+ lOptPos = maxInt;
+ lReqPos = minInt;
+ }
+
+ @Override
+ public void setFractionLength(int minFrac, int maxFrac) {
+ // Validation should happen outside of DecimalQuantity, e.g., in the Rounder class.
+ assert minFrac >= 0;
+ assert maxFrac >= minFrac;
+
+ // Save values into internal state
+ // Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE
+ rReqPos = -minFrac;
+ rOptPos = -maxFrac;
+ }
+
+ @Override
+ public long getPositionFingerprint() {
+ long fingerprint = 0;
+ fingerprint ^= lOptPos;
+ fingerprint ^= (lReqPos << 16);
+ fingerprint ^= ((long) rReqPos << 32);
+ fingerprint ^= ((long) rOptPos << 48);
+ return fingerprint;
+ }
+
+ @Override
+ public void roundToIncrement(BigDecimal roundingIncrement, MathContext mathContext) {
+ // TODO: Avoid converting back and forth to BigDecimal.
+ BigDecimal temp = toBigDecimal();
+ temp = temp.divide(roundingIncrement, 0, mathContext.getRoundingMode())
+ .multiply(roundingIncrement).round(mathContext);
+ if (temp.signum() == 0) {
+ setBcdToZero(); // keeps negative flag for -0.0
} else {
- for (int p = safeSubtract(position, 2); p >= 0; p--) {
- if (getDigitPos(p) != 0) {
- section = RoundingUtils.SECTION_UPPER;
- break;
- }
- }
- }
- } else {
- int p = safeSubtract(position, 2);
- int minP = Math.max(0, precision - 14);
- if (leadingDigit == 0) {
- section = SECTION_LOWER_EDGE;
- for (; p >= minP; p--) {
- if (getDigitPos(p) != 0) {
- section = RoundingUtils.SECTION_LOWER;
- break;
- }
- }
- } else if (leadingDigit == 4) {
- for (; p >= minP; p--) {
- if (getDigitPos(p) != 9) {
- section = RoundingUtils.SECTION_LOWER;
- break;
- }
- }
- } else if (leadingDigit == 5) {
- for (; p >= minP; p--) {
- if (getDigitPos(p) != 0) {
- section = RoundingUtils.SECTION_UPPER;
- break;
- }
- }
- } else if (leadingDigit == 9) {
- section = SECTION_UPPER_EDGE;
- for (; p >= minP; p--) {
- if (getDigitPos(p) != 9) {
- section = RoundingUtils.SECTION_UPPER;
- break;
- }
- }
- } else if (leadingDigit < 5) {
- section = RoundingUtils.SECTION_LOWER;
+ setToBigDecimal(temp);
+ }
+ }
+
+ @Override
+ public void multiplyBy(BigDecimal multiplicand) {
+ if (isInfinite() || isZero() || isNaN()) {
+ return;
+ }
+ BigDecimal temp = toBigDecimal();
+ temp = temp.multiply(multiplicand);
+ setToBigDecimal(temp);
+ }
+
+ @Override
+ public int getMagnitude() throws ArithmeticException {
+ if (precision == 0) {
+ throw new ArithmeticException("Magnitude is not well-defined for zero");
+ } else {
+ return scale + precision - 1;
+ }
+ }
+
+ @Override
+ public void adjustMagnitude(int delta) {
+ if (precision != 0) {
+ scale += delta;
+ origDelta += delta;
+ }
+ }
+
+ @Override
+ public StandardPlural getStandardPlural(PluralRules rules) {
+ if (rules == null) {
+ // Fail gracefully if the user didn't provide a PluralRules
+ return StandardPlural.OTHER;
+ } else {
+ @SuppressWarnings("deprecation")
+ String ruleString = rules.select(this);
+ return StandardPlural.orOtherFromString(ruleString);
+ }
+ }
+
+ @Override
+ public double getPluralOperand(Operand operand) {
+ // If this assertion fails, you need to call roundToInfinity() or some other rounding method.
+ // See the comment at the top of this file explaining the "isApproximate" field.
+ assert !isApproximate;
+
+ switch (operand) {
+ case i:
+ return toLong();
+ case f:
+ return toFractionLong(true);
+ case t:
+ return toFractionLong(false);
+ case v:
+ return fractionCount();
+ case w:
+ return fractionCountWithoutTrailingZeros();
+ default:
+ return Math.abs(toDouble());
+ }
+ }
+
+ @Override
+ public void populateUFieldPosition(FieldPosition fp) {
+ if (fp instanceof UFieldPosition) {
+ ((UFieldPosition) fp).setFractionDigits((int) getPluralOperand(Operand.v),
+ (long) getPluralOperand(Operand.f));
+ }
+ }
+
+ @Override
+ public int getUpperDisplayMagnitude() {
+ // If this assertion fails, you need to call roundToInfinity() or some other rounding method.
+ // See the comment at the top of this file explaining the "isApproximate" field.
+ assert !isApproximate;
+
+ int magnitude = scale + precision;
+ int result = (lReqPos > magnitude) ? lReqPos : (lOptPos < magnitude) ? lOptPos : magnitude;
+ return result - 1;
+ }
+
+ @Override
+ public int getLowerDisplayMagnitude() {
+ // If this assertion fails, you need to call roundToInfinity() or some other rounding method.
+ // See the comment at the top of this file explaining the "isApproximate" field.
+ assert !isApproximate;
+
+ int magnitude = scale;
+ int result = (rReqPos < magnitude) ? rReqPos : (rOptPos > magnitude) ? rOptPos : magnitude;
+ return result;
+ }
+
+ @Override
+ public byte getDigit(int magnitude) {
+ // If this assertion fails, you need to call roundToInfinity() or some other rounding method.
+ // See the comment at the top of this file explaining the "isApproximate" field.
+ assert !isApproximate;
+
+ return getDigitPos(magnitude - scale);
+ }
+
+ private int fractionCount() {
+ return -getLowerDisplayMagnitude();
+ }
+
+ private int fractionCountWithoutTrailingZeros() {
+ return Math.max(-scale, 0);
+ }
+
+ @Override
+ public boolean isNegative() {
+ return (flags & NEGATIVE_FLAG) != 0;
+ }
+
+ @Override
+ public boolean isInfinite() {
+ return (flags & INFINITY_FLAG) != 0;
+ }
+
+ @Override
+ public boolean isNaN() {
+ return (flags & NAN_FLAG) != 0;
+ }
+
+ @Override
+ public boolean isZero() {
+ return precision == 0;
+ }
+
+ public void setToInt(int n) {
+ setBcdToZero();
+ flags = 0;
+ if (n < 0) {
+ flags |= NEGATIVE_FLAG;
+ n = -n;
+ }
+ if (n != 0) {
+ _setToInt(n);
+ compact();
+ }
+ }
+
+ private void _setToInt(int n) {
+ if (n == Integer.MIN_VALUE) {
+ readLongToBcd(-(long) n);
+ } else {
+ readIntToBcd(n);
+ }
+ }
+
+ public void setToLong(long n) {
+ setBcdToZero();
+ flags = 0;
+ if (n < 0) {
+ flags |= NEGATIVE_FLAG;
+ n = -n;
+ }
+ if (n != 0) {
+ _setToLong(n);
+ compact();
+ }
+ }
+
+ private void _setToLong(long n) {
+ if (n == Long.MIN_VALUE) {
+ readBigIntegerToBcd(BigInteger.valueOf(n).negate());
+ } else if (n <= Integer.MAX_VALUE) {
+ readIntToBcd((int) n);
+ } else {
+ readLongToBcd(n);
+ }
+ }
+
+ public void setToBigInteger(BigInteger n) {
+ setBcdToZero();
+ flags = 0;
+ if (n.signum() == -1) {
+ flags |= NEGATIVE_FLAG;
+ n = n.negate();
+ }
+ if (n.signum() != 0) {
+ _setToBigInteger(n);
+ compact();
+ }
+ }
+
+ private void _setToBigInteger(BigInteger n) {
+ if (n.bitLength() < 32) {
+ readIntToBcd(n.intValue());
+ } else if (n.bitLength() < 64) {
+ readLongToBcd(n.longValue());
} else {
- section = RoundingUtils.SECTION_UPPER;
+ readBigIntegerToBcd(n);
}
+ }
- boolean roundsAtMidpoint =
- RoundingUtils.roundsAtMidpoint(mathContext.getRoundingMode().ordinal());
- if (safeSubtract(position, 1) < precision - 14
- || (roundsAtMidpoint && section == RoundingUtils.SECTION_MIDPOINT)
- || (!roundsAtMidpoint && section < 0 /* i.e. at upper or lower edge */)) {
- // Oops! This means that we have to get the exact representation of the double, because
- // the zone of uncertainty is along the rounding boundary.
- convertToAccurateDouble();
- roundToMagnitude(magnitude, mathContext); // start over
- return;
+ /**
+ * Sets the internal BCD state to represent the value in the given double.
+ *
+ * @param n
+ * The value to consume.
+ */
+ public void setToDouble(double n) {
+ setBcdToZero();
+ flags = 0;
+ // Double.compare() handles +0.0 vs -0.0
+ if (Double.compare(n, 0.0) < 0) {
+ flags |= NEGATIVE_FLAG;
+ n = -n;
+ }
+ if (Double.isNaN(n)) {
+ flags |= NAN_FLAG;
+ } else if (Double.isInfinite(n)) {
+ flags |= INFINITY_FLAG;
+ } else if (n != 0) {
+ _setToDoubleFast(n);
+ compact();
}
+ }
- // Turn off the approximate double flag, since the value is now confirmed to be exact.
- isApproximate = false;
- origDouble = 0.0;
+ private static final double[] DOUBLE_MULTIPLIERS = {
+ 1e0,
+ 1e1,
+ 1e2,
+ 1e3,
+ 1e4,
+ 1e5,
+ 1e6,
+ 1e7,
+ 1e8,
+ 1e9,
+ 1e10,
+ 1e11,
+ 1e12,
+ 1e13,
+ 1e14,
+ 1e15,
+ 1e16,
+ 1e17,
+ 1e18,
+ 1e19,
+ 1e20,
+ 1e21 };
+
+ /**
+ * Uses double multiplication and division to get the number into integer space before converting to
+ * digits. Since double arithmetic is inexact, the resulting digits may not be accurate.
+ */
+ private void _setToDoubleFast(double n) {
+ isApproximate = true;
+ origDouble = n;
origDelta = 0;
- if (position <= 0) {
- // All digits are to the left of the rounding magnitude.
- return;
+ // NOTE: Unlike ICU4C, doubles are always IEEE 754 doubles.
+ long ieeeBits = Double.doubleToLongBits(n);
+ int exponent = (int) ((ieeeBits & 0x7ff0000000000000L) >> 52) - 0x3ff;
+
+ // Not all integers can be represented exactly for exponent > 52
+ if (exponent <= 52 && (long) n == n) {
+ _setToLong((long) n);
+ return;
}
- // Good to continue rounding.
- if (section == SECTION_LOWER_EDGE) section = RoundingUtils.SECTION_LOWER;
- if (section == SECTION_UPPER_EDGE) section = RoundingUtils.SECTION_UPPER;
- }
+ // 3.3219... is log2(10)
+ int fracLength = (int) ((52 - exponent) / 3.32192809489);
+ if (fracLength >= 0) {
+ int i = fracLength;
+ // 1e22 is the largest exact double.
+ for (; i >= 22; i -= 22)
+ n *= 1e22;
+ n *= DOUBLE_MULTIPLIERS[i];
+ } else {
+ int i = fracLength;
+ // 1e22 is the largest exact double.
+ for (; i <= -22; i += 22)
+ n /= 1e22;
+ n /= DOUBLE_MULTIPLIERS[-i];
+ }
+ long result = Math.round(n);
+ if (result != 0) {
+ _setToLong(result);
+ scale -= fracLength;
+ }
+ }
- boolean roundDown =
- RoundingUtils.getRoundingDirection(
- (trailingDigit % 2) == 0,
- isNegative(),
- section,
- mathContext.getRoundingMode().ordinal(),
- this);
+ /**
+ * Uses Double.toString() to obtain an exact accurate representation of the double, overwriting it
+ * into the BCD. This method can be called at any point after {@link #_setToDoubleFast} while
+ * {@link #isApproximate} is still true.
+ */
+ private void convertToAccurateDouble() {
+ double n = origDouble;
+ assert n != 0;
+ int delta = origDelta;
+ setBcdToZero();
- // Perform truncation
- if (position >= precision) {
+ // Call the slow oracle function (Double.toString in Java, sprintf in C++).
+ String dstr = Double.toString(n);
+
+ if (dstr.indexOf('E') != -1) {
+ // Case 1: Exponential notation.
+ assert dstr.indexOf('.') == 1;
+ int expPos = dstr.indexOf('E');
+ _setToLong(Long.parseLong(dstr.charAt(0) + dstr.substring(2, expPos)));
+ scale += Integer.parseInt(dstr.substring(expPos + 1)) - (expPos - 1) + 1;
+ } else if (dstr.charAt(0) == '0') {
+ // Case 2: Fraction-only number.
+ assert dstr.indexOf('.') == 1;
+ _setToLong(Long.parseLong(dstr.substring(2)));
+ scale += 2 - dstr.length();
+ } else if (dstr.charAt(dstr.length() - 1) == '0') {
+ // Case 3: Integer-only number.
+ // Note: this path should not normally happen, because integer-only numbers are captured
+ // before the approximate double logic is performed.
+ assert dstr.indexOf('.') == dstr.length() - 2;
+ assert dstr.length() - 2 <= 18;
+ _setToLong(Long.parseLong(dstr.substring(0, dstr.length() - 2)));
+ // no need to adjust scale
+ } else {
+ // Case 4: Number with both a fraction and an integer.
+ int decimalPos = dstr.indexOf('.');
+ _setToLong(Long.parseLong(dstr.substring(0, decimalPos) + dstr.substring(decimalPos + 1)));
+ scale += decimalPos - dstr.length() + 1;
+ }
+
+ scale += delta;
+ compact();
+ explicitExactDouble = true;
+ }
+
+ /**
+ * Whether this {@link DecimalQuantity_DualStorageBCD} has been explicitly converted to an exact
+ * double. true if backed by a double that was explicitly converted via convertToAccurateDouble;
+ * false otherwise. Used for testing.
+ *
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ public boolean explicitExactDouble = false;
+
+ /**
+ * Sets the internal BCD state to represent the value in the given BigDecimal.
+ *
+ * @param n
+ * The value to consume.
+ */
+ @Override
+ public void setToBigDecimal(BigDecimal n) {
setBcdToZero();
- scale = magnitude;
- } else {
- shiftRight(position);
- }
-
- // Bubble the result to the higher digits
- if (!roundDown) {
- if (trailingDigit == 9) {
- int bubblePos = 0;
- // Note: in the long implementation, the most digits BCD can have at this point is 15,
- // so bubblePos <= 15 and getDigitPos(bubblePos) is safe.
- for (; getDigitPos(bubblePos) == 9; bubblePos++) {}
- shiftRight(bubblePos); // shift off the trailing 9s
- }
- byte digit0 = getDigitPos(0);
- assert digit0 != 9;
- setDigitPos(0, (byte) (digit0 + 1));
- precision += 1; // in case an extra digit got added
- }
-
- compact();
- }
- }
-
- @Override
- public void roundToInfinity() {
- if (isApproximate) {
- convertToAccurateDouble();
- }
- }
-
- /**
- * Appends a digit, optionally with one or more leading zeros, to the end of the value represented
- * by this DecimalQuantity.
- *
- * <p>The primary use of this method is to construct numbers during a parsing loop. It allows
- * parsing to take advantage of the digit list infrastructure primarily designed for formatting.
- *
- * @param value The digit to append.
- * @param leadingZeros The number of zeros to append before the digit. For example, if the value
- * in this instance starts as 12.3, and you append a 4 with 1 leading zero, the value becomes
- * 12.304.
- * @param appendAsInteger If true, increase the magnitude of existing digits to make room for the
- * new digit. If false, append to the end like a fraction digit. If true, there must not be
- * any fraction digits already in the number.
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- public void appendDigit(byte value, int leadingZeros, boolean appendAsInteger) {
- assert leadingZeros >= 0;
-
- // Zero requires special handling to maintain the invariant that the least-significant digit
- // in the BCD is nonzero.
- if (value == 0) {
- if (appendAsInteger && precision != 0) {
- scale += leadingZeros + 1;
- }
- return;
- }
-
- // Deal with trailing zeros
- if (scale > 0) {
- leadingZeros += scale;
- if (appendAsInteger) {
- scale = 0;
- }
- }
-
- // Append digit
- shiftLeft(leadingZeros + 1);
- setDigitPos(0, value);
-
- // Fix scale if in integer mode
- if (appendAsInteger) {
- scale += leadingZeros + 1;
- }
- }
-
- @Override
- public String toPlainString() {
- // NOTE: This logic is duplicated between here and DecimalQuantity_SimpleStorage.
- StringBuilder sb = new StringBuilder();
- if (isNegative()) {
- sb.append('-');
- }
- for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) {
- sb.append(getDigit(m));
- if (m == 0) sb.append('.');
- }
- return sb.toString();
- }
-
- /**
- * Returns a single digit from the BCD list. No internal state is changed by calling this method.
- *
- * @param position The position of the digit to pop, counted in BCD units from the least
- * significant digit. If outside the range supported by the implementation, zero is returned.
- * @return The digit at the specified location.
- */
- protected abstract byte getDigitPos(int position);
-
- /**
- * Sets the digit in the BCD list. This method only sets the digit; it is the caller's
- * responsibility to call {@link #compact} after setting the digit.
- *
- * @param position The position of the digit to pop, counted in BCD units from the least
- * significant digit. If outside the range supported by the implementation, an AssertionError
- * is thrown.
- * @param value The digit to set at the specified location.
- */
- protected abstract void setDigitPos(int position, byte value);
-
- /**
- * Adds zeros to the end of the BCD list. This will result in an invalid BCD representation; it is
- * the caller's responsibility to do further manipulation and then call {@link #compact}.
- *
- * @param numDigits The number of zeros to add.
- */
- protected abstract void shiftLeft(int numDigits);
-
- protected abstract void shiftRight(int numDigits);
-
- /**
- * Sets the internal representation to zero. Clears any values stored in scale, precision,
- * hasDouble, origDouble, origDelta, and BCD data.
- */
- protected abstract void setBcdToZero();
-
- /**
- * Sets the internal BCD state to represent the value in the given int. The int is guaranteed to
- * be either positive. The internal state is guaranteed to be empty when this method is called.
- *
- * @param n The value to consume.
- */
- protected abstract void readIntToBcd(int input);
-
- /**
- * Sets the internal BCD state to represent the value in the given long. The long is guaranteed to
- * be either positive. The internal state is guaranteed to be empty when this method is called.
- *
- * @param n The value to consume.
- */
- protected abstract void readLongToBcd(long input);
-
- /**
- * Sets the internal BCD state to represent the value in the given BigInteger. The BigInteger is
- * guaranteed to be positive, and it is guaranteed to be larger than Long.MAX_VALUE. The internal
- * state is guaranteed to be empty when this method is called.
- *
- * @param n The value to consume.
- */
- protected abstract void readBigIntegerToBcd(BigInteger input);
-
- /**
- * Returns a BigDecimal encoding the internal BCD value.
- *
- * @return A BigDecimal representation of the internal BCD.
- */
- protected abstract BigDecimal bcdToBigDecimal();
-
- protected abstract void copyBcdFrom(DecimalQuantity _other);
-
- /**
- * Removes trailing zeros from the BCD (adjusting the scale as required) and then computes the
- * precision. The precision is the number of digits in the number up through the greatest nonzero
- * digit.
- *
- * <p>This method must always be called when bcd changes in order for assumptions to be correct in
- * methods like {@link #fractionCount()}.
- */
- protected abstract void compact();
+ flags = 0;
+ if (n.signum() == -1) {
+ flags |= NEGATIVE_FLAG;
+ n = n.negate();
+ }
+ if (n.signum() != 0) {
+ _setToBigDecimal(n);
+ compact();
+ }
+ }
+
+ private void _setToBigDecimal(BigDecimal n) {
+ int fracLength = n.scale();
+ n = n.scaleByPowerOfTen(fracLength);
+ BigInteger bi = n.toBigInteger();
+ _setToBigInteger(bi);
+ scale -= fracLength;
+ }
+
+ /**
+ * Returns a long approximating the internal BCD. A long can only represent the integral part of the
+ * number.
+ *
+ * @return A double representation of the internal BCD.
+ */
+ protected long toLong() {
+ long result = 0L;
+ for (int magnitude = scale + precision - 1; magnitude >= 0; magnitude--) {
+ result = result * 10 + getDigitPos(magnitude - scale);
+ }
+ return result;
+ }
+
+ /**
+ * This returns a long representing the fraction digits of the number, as required by PluralRules.
+ * For example, if we represent the number "1.20" (including optional and required digits), then this
+ * function returns "20" if includeTrailingZeros is true or "2" if false.
+ */
+ protected long toFractionLong(boolean includeTrailingZeros) {
+ long result = 0L;
+ int magnitude = -1;
+ for (; (magnitude >= scale || (includeTrailingZeros && magnitude >= rReqPos))
+ && magnitude >= rOptPos; magnitude--) {
+ result = result * 10 + getDigitPos(magnitude - scale);
+ }
+ return result;
+ }
+
+ /**
+ * Returns a double approximating the internal BCD. The double may not retain all of the information
+ * encoded in the BCD if the BCD represents a number out of range of a double.
+ *
+ * @return A double representation of the internal BCD.
+ */
+ @Override
+ public double toDouble() {
+ if (isApproximate) {
+ return toDoubleFromOriginal();
+ }
+
+ if (isNaN()) {
+ return Double.NaN;
+ } else if (isInfinite()) {
+ return isNegative() ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY;
+ }
+
+ long tempLong = 0L;
+ int lostDigits = precision - Math.min(precision, 17);
+ for (int shift = precision - 1; shift >= lostDigits; shift--) {
+ tempLong = tempLong * 10 + getDigitPos(shift);
+ }
+ double result = tempLong;
+ int _scale = scale + lostDigits;
+ if (_scale >= 0) {
+ // 1e22 is the largest exact double.
+ int i = _scale;
+ for (; i >= 22; i -= 22)
+ result *= 1e22;
+ result *= DOUBLE_MULTIPLIERS[i];
+ } else {
+ // 1e22 is the largest exact double.
+ int i = _scale;
+ for (; i <= -22; i += 22)
+ result /= 1e22;
+ result /= DOUBLE_MULTIPLIERS[-i];
+ }
+ if (isNegative())
+ result = -result;
+ return result;
+ }
+
+ @Override
+ public BigDecimal toBigDecimal() {
+ if (isApproximate) {
+ // Converting to a BigDecimal requires Double.toString().
+ convertToAccurateDouble();
+ }
+ return bcdToBigDecimal();
+ }
+
+ protected double toDoubleFromOriginal() {
+ double result = origDouble;
+ int delta = origDelta;
+ if (delta >= 0) {
+ // 1e22 is the largest exact double.
+ for (; delta >= 22; delta -= 22)
+ result *= 1e22;
+ result *= DOUBLE_MULTIPLIERS[delta];
+ } else {
+ // 1e22 is the largest exact double.
+ for (; delta <= -22; delta += 22)
+ result /= 1e22;
+ result /= DOUBLE_MULTIPLIERS[-delta];
+ }
+ if (isNegative())
+ result *= -1;
+ return result;
+ }
+
+ private static int safeSubtract(int a, int b) {
+ int diff = a - b;
+ if (b < 0 && diff < a)
+ return Integer.MAX_VALUE;
+ if (b > 0 && diff > a)
+ return Integer.MIN_VALUE;
+ return diff;
+ }
+
+ private static final int SECTION_LOWER_EDGE = -1;
+ private static final int SECTION_UPPER_EDGE = -2;
+
+ @Override
+ public void roundToMagnitude(int magnitude, MathContext mathContext) {
+ // The position in the BCD at which rounding will be performed; digits to the right of position
+ // will be rounded away.
+ // TODO: Andy: There was a test failure because of integer overflow here. Should I do
+ // "safe subtraction" everywhere in the code? What's the nicest way to do it?
+ int position = safeSubtract(magnitude, scale);
+
+ // Enforce the number of digits required by the MathContext.
+ int _mcPrecision = mathContext.getPrecision();
+ if (magnitude == Integer.MAX_VALUE
+ || (_mcPrecision > 0 && precision - position > _mcPrecision)) {
+ position = precision - _mcPrecision;
+ }
+
+ if (position <= 0 && !isApproximate) {
+ // All digits are to the left of the rounding magnitude.
+ } else if (precision == 0) {
+ // No rounding for zero.
+ } else {
+ // Perform rounding logic.
+ // "leading" = most significant digit to the right of rounding
+ // "trailing" = least significant digit to the left of rounding
+ byte leadingDigit = getDigitPos(safeSubtract(position, 1));
+ byte trailingDigit = getDigitPos(position);
+
+ // Compute which section of the number we are in.
+ // EDGE means we are at the bottom or top edge, like 1.000 or 1.999 (used by doubles)
+ // LOWER means we are between the bottom edge and the midpoint, like 1.391
+ // MIDPOINT means we are exactly in the middle, like 1.500
+ // UPPER means we are between the midpoint and the top edge, like 1.916
+ int section = RoundingUtils.SECTION_MIDPOINT;
+ if (!isApproximate) {
+ if (leadingDigit < 5) {
+ section = RoundingUtils.SECTION_LOWER;
+ } else if (leadingDigit > 5) {
+ section = RoundingUtils.SECTION_UPPER;
+ } else {
+ for (int p = safeSubtract(position, 2); p >= 0; p--) {
+ if (getDigitPos(p) != 0) {
+ section = RoundingUtils.SECTION_UPPER;
+ break;
+ }
+ }
+ }
+ } else {
+ int p = safeSubtract(position, 2);
+ int minP = Math.max(0, precision - 14);
+ if (leadingDigit == 0) {
+ section = SECTION_LOWER_EDGE;
+ for (; p >= minP; p--) {
+ if (getDigitPos(p) != 0) {
+ section = RoundingUtils.SECTION_LOWER;
+ break;
+ }
+ }
+ } else if (leadingDigit == 4) {
+ for (; p >= minP; p--) {
+ if (getDigitPos(p) != 9) {
+ section = RoundingUtils.SECTION_LOWER;
+ break;
+ }
+ }
+ } else if (leadingDigit == 5) {
+ for (; p >= minP; p--) {
+ if (getDigitPos(p) != 0) {
+ section = RoundingUtils.SECTION_UPPER;
+ break;
+ }
+ }
+ } else if (leadingDigit == 9) {
+ section = SECTION_UPPER_EDGE;
+ for (; p >= minP; p--) {
+ if (getDigitPos(p) != 9) {
+ section = RoundingUtils.SECTION_UPPER;
+ break;
+ }
+ }
+ } else if (leadingDigit < 5) {
+ section = RoundingUtils.SECTION_LOWER;
+ } else {
+ section = RoundingUtils.SECTION_UPPER;
+ }
+
+ boolean roundsAtMidpoint = RoundingUtils
+ .roundsAtMidpoint(mathContext.getRoundingMode().ordinal());
+ if (safeSubtract(position, 1) < precision - 14
+ || (roundsAtMidpoint && section == RoundingUtils.SECTION_MIDPOINT)
+ || (!roundsAtMidpoint && section < 0 /* i.e. at upper or lower edge */)) {
+ // Oops! This means that we have to get the exact representation of the double,
+ // because
+ // the zone of uncertainty is along the rounding boundary.
+ convertToAccurateDouble();
+ roundToMagnitude(magnitude, mathContext); // start over
+ return;
+ }
+
+ // Turn off the approximate double flag, since the value is now confirmed to be exact.
+ isApproximate = false;
+ origDouble = 0.0;
+ origDelta = 0;
+
+ if (position <= 0) {
+ // All digits are to the left of the rounding magnitude.
+ return;
+ }
+
+ // Good to continue rounding.
+ if (section == SECTION_LOWER_EDGE)
+ section = RoundingUtils.SECTION_LOWER;
+ if (section == SECTION_UPPER_EDGE)
+ section = RoundingUtils.SECTION_UPPER;
+ }
+
+ boolean roundDown = RoundingUtils.getRoundingDirection((trailingDigit % 2) == 0,
+ isNegative(),
+ section,
+ mathContext.getRoundingMode().ordinal(),
+ this);
+
+ // Perform truncation
+ if (position >= precision) {
+ setBcdToZero();
+ scale = magnitude;
+ } else {
+ shiftRight(position);
+ }
+
+ // Bubble the result to the higher digits
+ if (!roundDown) {
+ if (trailingDigit == 9) {
+ int bubblePos = 0;
+ // Note: in the long implementation, the most digits BCD can have at this point is
+ // 15,
+ // so bubblePos <= 15 and getDigitPos(bubblePos) is safe.
+ for (; getDigitPos(bubblePos) == 9; bubblePos++) {
+ }
+ shiftRight(bubblePos); // shift off the trailing 9s
+ }
+ byte digit0 = getDigitPos(0);
+ assert digit0 != 9;
+ setDigitPos(0, (byte) (digit0 + 1));
+ precision += 1; // in case an extra digit got added
+ }
+
+ compact();
+ }
+ }
+
+ @Override
+ public void roundToInfinity() {
+ if (isApproximate) {
+ convertToAccurateDouble();
+ }
+ }
+
+ /**
+ * Appends a digit, optionally with one or more leading zeros, to the end of the value represented by
+ * this DecimalQuantity.
+ *
+ * <p>
+ * The primary use of this method is to construct numbers during a parsing loop. It allows parsing to
+ * take advantage of the digit list infrastructure primarily designed for formatting.
+ *
+ * @param value
+ * The digit to append.
+ * @param leadingZeros
+ * The number of zeros to append before the digit. For example, if the value in this
+ * instance starts as 12.3, and you append a 4 with 1 leading zero, the value becomes
+ * 12.304.
+ * @param appendAsInteger
+ * If true, increase the magnitude of existing digits to make room for the new digit. If
+ * false, append to the end like a fraction digit. If true, there must not be any fraction
+ * digits already in the number.
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ public void appendDigit(byte value, int leadingZeros, boolean appendAsInteger) {
+ assert leadingZeros >= 0;
+
+ // Zero requires special handling to maintain the invariant that the least-significant digit
+ // in the BCD is nonzero.
+ if (value == 0) {
+ if (appendAsInteger && precision != 0) {
+ scale += leadingZeros + 1;
+ }
+ return;
+ }
+
+ // Deal with trailing zeros
+ if (scale > 0) {
+ leadingZeros += scale;
+ if (appendAsInteger) {
+ scale = 0;
+ }
+ }
+
+ // Append digit
+ shiftLeft(leadingZeros + 1);
+ setDigitPos(0, value);
+
+ // Fix scale if in integer mode
+ if (appendAsInteger) {
+ scale += leadingZeros + 1;
+ }
+ }
+
+ @Override
+ public String toPlainString() {
+ // NOTE: This logic is duplicated between here and DecimalQuantity_SimpleStorage.
+ StringBuilder sb = new StringBuilder();
+ if (isNegative()) {
+ sb.append('-');
+ }
+ for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) {
+ sb.append(getDigit(m));
+ if (m == 0)
+ sb.append('.');
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Returns a single digit from the BCD list. No internal state is changed by calling this method.
+ *
+ * @param position
+ * The position of the digit to pop, counted in BCD units from the least significant
+ * digit. If outside the range supported by the implementation, zero is returned.
+ * @return The digit at the specified location.
+ */
+ protected abstract byte getDigitPos(int position);
+
+ /**
+ * Sets the digit in the BCD list. This method only sets the digit; it is the caller's responsibility
+ * to call {@link #compact} after setting the digit.
+ *
+ * @param position
+ * The position of the digit to pop, counted in BCD units from the least significant
+ * digit. If outside the range supported by the implementation, an AssertionError is
+ * thrown.
+ * @param value
+ * The digit to set at the specified location.
+ */
+ protected abstract void setDigitPos(int position, byte value);
+
+ /**
+ * Adds zeros to the end of the BCD list. This will result in an invalid BCD representation; it is
+ * the caller's responsibility to do further manipulation and then call {@link #compact}.
+ *
+ * @param numDigits
+ * The number of zeros to add.
+ */
+ protected abstract void shiftLeft(int numDigits);
+
+ protected abstract void shiftRight(int numDigits);
+
+ /**
+ * Sets the internal representation to zero. Clears any values stored in scale, precision, hasDouble,
+ * origDouble, origDelta, and BCD data.
+ */
+ protected abstract void setBcdToZero();
+
+ /**
+ * Sets the internal BCD state to represent the value in the given int. The int is guaranteed to be
+ * either positive. The internal state is guaranteed to be empty when this method is called.
+ *
+ * @param n
+ * The value to consume.
+ */
+ protected abstract void readIntToBcd(int input);
+
+ /**
+ * Sets the internal BCD state to represent the value in the given long. The long is guaranteed to be
+ * either positive. The internal state is guaranteed to be empty when this method is called.
+ *
+ * @param n
+ * The value to consume.
+ */
+ protected abstract void readLongToBcd(long input);
+
+ /**
+ * Sets the internal BCD state to represent the value in the given BigInteger. The BigInteger is
+ * guaranteed to be positive, and it is guaranteed to be larger than Long.MAX_VALUE. The internal
+ * state is guaranteed to be empty when this method is called.
+ *
+ * @param n
+ * The value to consume.
+ */
+ protected abstract void readBigIntegerToBcd(BigInteger input);
+
+ /**
+ * Returns a BigDecimal encoding the internal BCD value.
+ *
+ * @return A BigDecimal representation of the internal BCD.
+ */
+ protected abstract BigDecimal bcdToBigDecimal();
+
+ protected abstract void copyBcdFrom(DecimalQuantity _other);
+
+ /**
+ * Removes trailing zeros from the BCD (adjusting the scale as required) and then computes the
+ * precision. The precision is the number of digits in the number up through the greatest nonzero
+ * digit.
+ *
+ * <p>
+ * This method must always be called when bcd changes in order for assumptions to be correct in
+ * methods like {@link #fractionCount()}.
+ */
+ protected abstract void compact();
}
import java.math.BigInteger;
/**
- * A DecimalQuantity with internal storage as a 64-bit BCD, with fallback to a byte array
- * for numbers that don't fit into the standard BCD.
+ * A DecimalQuantity with internal storage as a 64-bit BCD, with fallback to a byte array for numbers
+ * that don't fit into the standard BCD.
*/
public final class DecimalQuantity_DualStorageBCD extends DecimalQuantity_AbstractBCD {
- /**
- * The BCD of the 16 digits of the number represented by this object. Every 4 bits of the long map
- * to one digit. For example, the number "12345" in BCD is "0x12345".
- *
- * <p>Whenever bcd changes internally, {@link #compact()} must be called, except in special cases
- * like setting the digit to zero.
- */
- private byte[] bcdBytes;
-
- private long bcdLong = 0L;
-
- private boolean usingBytes = false;
-
- @Override
- public int maxRepresentableDigits() {
- return Integer.MAX_VALUE;
- }
-
- public DecimalQuantity_DualStorageBCD() {
- setBcdToZero();
- flags = 0;
- }
-
- public DecimalQuantity_DualStorageBCD(long input) {
- setToLong(input);
- }
-
- public DecimalQuantity_DualStorageBCD(int input) {
- setToInt(input);
- }
-
- public DecimalQuantity_DualStorageBCD(double input) {
- setToDouble(input);
- }
-
- public DecimalQuantity_DualStorageBCD(BigInteger input) {
- setToBigInteger(input);
- }
-
- public DecimalQuantity_DualStorageBCD(BigDecimal input) {
- setToBigDecimal(input);
- }
-
- public DecimalQuantity_DualStorageBCD(DecimalQuantity_DualStorageBCD other) {
- copyFrom(other);
- }
-
- public DecimalQuantity_DualStorageBCD(Number number) {
- if (number instanceof Long) {
- setToLong(number.longValue());
- } else if (number instanceof Integer) {
- setToInt(number.intValue());
- } else if (number instanceof Double) {
- setToDouble(number.doubleValue());
- } else if (number instanceof BigInteger) {
- setToBigInteger((BigInteger) number);
- } else if (number instanceof BigDecimal) {
- setToBigDecimal((BigDecimal) number);
- } else if (number instanceof com.ibm.icu.math.BigDecimal) {
- setToBigDecimal(((com.ibm.icu.math.BigDecimal) number).toBigDecimal());
- } else {
- throw new IllegalArgumentException(
- "Number is of an unsupported type: " + number.getClass().getName());
+ /**
+ * The BCD of the 16 digits of the number represented by this object. Every 4 bits of the long map to
+ * one digit. For example, the number "12345" in BCD is "0x12345".
+ *
+ * <p>
+ * Whenever bcd changes internally, {@link #compact()} must be called, except in special cases like
+ * setting the digit to zero.
+ */
+ private byte[] bcdBytes;
+
+ private long bcdLong = 0L;
+
+ private boolean usingBytes = false;
+
+ @Override
+ public int maxRepresentableDigits() {
+ return Integer.MAX_VALUE;
}
- }
-
- @Override
- public DecimalQuantity createCopy() {
- return new DecimalQuantity_DualStorageBCD(this);
- }
-
- @Override
- protected byte getDigitPos(int position) {
- if (usingBytes) {
- if (position < 0 || position > precision) return 0;
- return bcdBytes[position];
- } else {
- if (position < 0 || position >= 16) return 0;
- return (byte) ((bcdLong >>> (position * 4)) & 0xf);
+
+ public DecimalQuantity_DualStorageBCD() {
+ setBcdToZero();
+ flags = 0;
}
- }
-
- @Override
- protected void setDigitPos(int position, byte value) {
- assert position >= 0;
- if (usingBytes) {
- ensureCapacity(position + 1);
- bcdBytes[position] = value;
- } else if (position >= 16) {
- switchStorage();
- ensureCapacity(position + 1);
- bcdBytes[position] = value;
- } else {
- int shift = position * 4;
- bcdLong = bcdLong & ~(0xfL << shift) | ((long) value << shift);
+
+ public DecimalQuantity_DualStorageBCD(long input) {
+ setToLong(input);
}
- }
- @Override
- protected void shiftLeft(int numDigits) {
- if (!usingBytes && precision + numDigits > 16) {
- switchStorage();
+ public DecimalQuantity_DualStorageBCD(int input) {
+ setToInt(input);
}
- if (usingBytes) {
- ensureCapacity(precision + numDigits);
- int i = precision + numDigits - 1;
- for (; i >= numDigits; i--) {
- bcdBytes[i] = bcdBytes[i - numDigits];
- }
- for (; i >= 0; i--) {
- bcdBytes[i] = 0;
- }
- } else {
- bcdLong <<= (numDigits * 4);
+
+ public DecimalQuantity_DualStorageBCD(double input) {
+ setToDouble(input);
}
- scale -= numDigits;
- precision += numDigits;
- }
-
- @Override
- protected void shiftRight(int numDigits) {
- if (usingBytes) {
- int i = 0;
- for (; i < precision - numDigits; i++) {
- bcdBytes[i] = bcdBytes[i + numDigits];
- }
- for (; i < precision; i++) {
- bcdBytes[i] = 0;
- }
- } else {
- bcdLong >>>= (numDigits * 4);
+
+ public DecimalQuantity_DualStorageBCD(BigInteger input) {
+ setToBigInteger(input);
}
- scale += numDigits;
- precision -= numDigits;
- }
-
- @Override
- protected void setBcdToZero() {
- if (usingBytes) {
- bcdBytes = null;
- usingBytes = false;
+
+ public DecimalQuantity_DualStorageBCD(BigDecimal input) {
+ setToBigDecimal(input);
}
- bcdLong = 0L;
- scale = 0;
- precision = 0;
- isApproximate = false;
- origDouble = 0;
- origDelta = 0;
- }
-
- @Override
- protected void readIntToBcd(int n) {
- assert n != 0;
- // ints always fit inside the long implementation.
- long result = 0L;
- int i = 16;
- for (; n != 0; n /= 10, i--) {
- result = (result >>> 4) + (((long) n % 10) << 60);
+
+ public DecimalQuantity_DualStorageBCD(DecimalQuantity_DualStorageBCD other) {
+ copyFrom(other);
}
- assert !usingBytes;
- bcdLong = result >>> (i * 4);
- scale = 0;
- precision = 16 - i;
- }
-
- @Override
- protected void readLongToBcd(long n) {
- assert n != 0;
- if (n >= 10000000000000000L) {
- ensureCapacity();
- int i = 0;
- for (; n != 0L; n /= 10L, i++) {
- bcdBytes[i] = (byte) (n % 10);
- }
- assert usingBytes;
- scale = 0;
- precision = i;
- } else {
- long result = 0L;
- int i = 16;
- for (; n != 0L; n /= 10L, i--) {
- result = (result >>> 4) + ((n % 10) << 60);
- }
- assert i >= 0;
- assert !usingBytes;
- bcdLong = result >>> (i * 4);
- scale = 0;
- precision = 16 - i;
+
+ public DecimalQuantity_DualStorageBCD(Number number) {
+ if (number instanceof Long) {
+ setToLong(number.longValue());
+ } else if (number instanceof Integer) {
+ setToInt(number.intValue());
+ } else if (number instanceof Double) {
+ setToDouble(number.doubleValue());
+ } else if (number instanceof BigInteger) {
+ setToBigInteger((BigInteger) number);
+ } else if (number instanceof BigDecimal) {
+ setToBigDecimal((BigDecimal) number);
+ } else if (number instanceof com.ibm.icu.math.BigDecimal) {
+ setToBigDecimal(((com.ibm.icu.math.BigDecimal) number).toBigDecimal());
+ } else {
+ throw new IllegalArgumentException(
+ "Number is of an unsupported type: " + number.getClass().getName());
+ }
}
- }
-
- @Override
- protected void readBigIntegerToBcd(BigInteger n) {
- assert n.signum() != 0;
- ensureCapacity(); // allocate initial byte array
- int i = 0;
- for (; n.signum() != 0; i++) {
- BigInteger[] temp = n.divideAndRemainder(BigInteger.TEN);
- ensureCapacity(i + 1);
- bcdBytes[i] = temp[1].byteValue();
- n = temp[0];
+
+ @Override
+ public DecimalQuantity createCopy() {
+ return new DecimalQuantity_DualStorageBCD(this);
}
- scale = 0;
- precision = i;
- }
-
- @Override
- protected BigDecimal bcdToBigDecimal() {
- if (usingBytes) {
- // Converting to a string here is faster than doing BigInteger/BigDecimal arithmetic.
- BigDecimal result = new BigDecimal(toNumberString());
- if (isNegative()) {
- result = result.negate();
- }
- return result;
- } else {
- long tempLong = 0L;
- for (int shift = (precision - 1); shift >= 0; shift--) {
- tempLong = tempLong * 10 + getDigitPos(shift);
- }
- BigDecimal result = BigDecimal.valueOf(tempLong);
- result = result.scaleByPowerOfTen(scale);
- if (isNegative()) result = result.negate();
- return result;
+
+ @Override
+ protected byte getDigitPos(int position) {
+ if (usingBytes) {
+ if (position < 0 || position > precision)
+ return 0;
+ return bcdBytes[position];
+ } else {
+ if (position < 0 || position >= 16)
+ return 0;
+ return (byte) ((bcdLong >>> (position * 4)) & 0xf);
+ }
+ }
+
+ @Override
+ protected void setDigitPos(int position, byte value) {
+ assert position >= 0;
+ if (usingBytes) {
+ ensureCapacity(position + 1);
+ bcdBytes[position] = value;
+ } else if (position >= 16) {
+ switchStorage();
+ ensureCapacity(position + 1);
+ bcdBytes[position] = value;
+ } else {
+ int shift = position * 4;
+ bcdLong = bcdLong & ~(0xfL << shift) | ((long) value << shift);
+ }
+ }
+
+ @Override
+ protected void shiftLeft(int numDigits) {
+ if (!usingBytes && precision + numDigits > 16) {
+ switchStorage();
+ }
+ if (usingBytes) {
+ ensureCapacity(precision + numDigits);
+ int i = precision + numDigits - 1;
+ for (; i >= numDigits; i--) {
+ bcdBytes[i] = bcdBytes[i - numDigits];
+ }
+ for (; i >= 0; i--) {
+ bcdBytes[i] = 0;
+ }
+ } else {
+ bcdLong <<= (numDigits * 4);
+ }
+ scale -= numDigits;
+ precision += numDigits;
+ }
+
+ @Override
+ protected void shiftRight(int numDigits) {
+ if (usingBytes) {
+ int i = 0;
+ for (; i < precision - numDigits; i++) {
+ bcdBytes[i] = bcdBytes[i + numDigits];
+ }
+ for (; i < precision; i++) {
+ bcdBytes[i] = 0;
+ }
+ } else {
+ bcdLong >>>= (numDigits * 4);
+ }
+ scale += numDigits;
+ precision -= numDigits;
+ }
+
+ @Override
+ protected void setBcdToZero() {
+ if (usingBytes) {
+ bcdBytes = null;
+ usingBytes = false;
+ }
+ bcdLong = 0L;
+ scale = 0;
+ precision = 0;
+ isApproximate = false;
+ origDouble = 0;
+ origDelta = 0;
}
- }
-
- @Override
- protected void compact() {
- if (usingBytes) {
- int delta = 0;
- for (; delta < precision && bcdBytes[delta] == 0; delta++) ;
- if (delta == precision) {
- // Number is zero
- setBcdToZero();
- return;
- } else {
- // Remove trailing zeros
- shiftRight(delta);
- }
-
- // Compute precision
- int leading = precision - 1;
- for (; leading >= 0 && bcdBytes[leading] == 0; leading--) ;
- precision = leading + 1;
-
- // Switch storage mechanism if possible
- if (precision <= 16) {
- switchStorage();
- }
-
- } else {
- if (bcdLong == 0L) {
- // Number is zero
- setBcdToZero();
- return;
- }
- // Compact the number (remove trailing zeros)
- int delta = Long.numberOfTrailingZeros(bcdLong) / 4;
- bcdLong >>>= delta * 4;
- scale += delta;
+ @Override
+ protected void readIntToBcd(int n) {
+ assert n != 0;
+ // ints always fit inside the long implementation.
+ long result = 0L;
+ int i = 16;
+ for (; n != 0; n /= 10, i--) {
+ result = (result >>> 4) + (((long) n % 10) << 60);
+ }
+ assert !usingBytes;
+ bcdLong = result >>> (i * 4);
+ scale = 0;
+ precision = 16 - i;
+ }
- // Compute precision
- precision = 16 - (Long.numberOfLeadingZeros(bcdLong) / 4);
+ @Override
+ protected void readLongToBcd(long n) {
+ assert n != 0;
+ if (n >= 10000000000000000L) {
+ ensureCapacity();
+ int i = 0;
+ for (; n != 0L; n /= 10L, i++) {
+ bcdBytes[i] = (byte) (n % 10);
+ }
+ assert usingBytes;
+ scale = 0;
+ precision = i;
+ } else {
+ long result = 0L;
+ int i = 16;
+ for (; n != 0L; n /= 10L, i--) {
+ result = (result >>> 4) + ((n % 10) << 60);
+ }
+ assert i >= 0;
+ assert !usingBytes;
+ bcdLong = result >>> (i * 4);
+ scale = 0;
+ precision = 16 - i;
+ }
}
- }
-
- /** Ensure that a byte array of at least 40 digits is allocated. */
- private void ensureCapacity() {
- ensureCapacity(40);
- }
-
- private void ensureCapacity(int capacity) {
- if (capacity == 0) return;
- int oldCapacity = usingBytes ? bcdBytes.length : 0;
- if (!usingBytes) {
- bcdBytes = new byte[capacity];
- } else if (oldCapacity < capacity) {
- byte[] bcd1 = new byte[capacity * 2];
- System.arraycopy(bcdBytes, 0, bcd1, 0, oldCapacity);
- bcdBytes = bcd1;
+
+ @Override
+ protected void readBigIntegerToBcd(BigInteger n) {
+ assert n.signum() != 0;
+ ensureCapacity(); // allocate initial byte array
+ int i = 0;
+ for (; n.signum() != 0; i++) {
+ BigInteger[] temp = n.divideAndRemainder(BigInteger.TEN);
+ ensureCapacity(i + 1);
+ bcdBytes[i] = temp[1].byteValue();
+ n = temp[0];
+ }
+ scale = 0;
+ precision = i;
}
- usingBytes = true;
- }
-
- /** Switches the internal storage mechanism between the 64-bit long and the byte array. */
- private void switchStorage() {
- if (usingBytes) {
- // Change from bytes to long
- bcdLong = 0L;
- for (int i = precision - 1; i >= 0; i--) {
- bcdLong <<= 4;
- bcdLong |= bcdBytes[i];
- }
- bcdBytes = null;
- usingBytes = false;
- } else {
- // Change from long to bytes
- ensureCapacity();
- for (int i = 0; i < precision; i++) {
- bcdBytes[i] = (byte) (bcdLong & 0xf);
- bcdLong >>>= 4;
- }
- assert usingBytes;
+
+ @Override
+ protected BigDecimal bcdToBigDecimal() {
+ if (usingBytes) {
+ // Converting to a string here is faster than doing BigInteger/BigDecimal arithmetic.
+ BigDecimal result = new BigDecimal(toNumberString());
+ if (isNegative()) {
+ result = result.negate();
+ }
+ return result;
+ } else {
+ long tempLong = 0L;
+ for (int shift = (precision - 1); shift >= 0; shift--) {
+ tempLong = tempLong * 10 + getDigitPos(shift);
+ }
+ BigDecimal result = BigDecimal.valueOf(tempLong);
+ result = result.scaleByPowerOfTen(scale);
+ if (isNegative())
+ result = result.negate();
+ return result;
+ }
}
- }
-
- @Override
- protected void copyBcdFrom(DecimalQuantity _other) {
- DecimalQuantity_DualStorageBCD other = (DecimalQuantity_DualStorageBCD) _other;
- setBcdToZero();
- if (other.usingBytes) {
- ensureCapacity(other.precision);
- System.arraycopy(other.bcdBytes, 0, bcdBytes, 0, other.precision);
- } else {
- bcdLong = other.bcdLong;
+
+ @Override
+ protected void compact() {
+ if (usingBytes) {
+ int delta = 0;
+ for (; delta < precision && bcdBytes[delta] == 0; delta++)
+ ;
+ if (delta == precision) {
+ // Number is zero
+ setBcdToZero();
+ return;
+ } else {
+ // Remove trailing zeros
+ shiftRight(delta);
+ }
+
+ // Compute precision
+ int leading = precision - 1;
+ for (; leading >= 0 && bcdBytes[leading] == 0; leading--)
+ ;
+ precision = leading + 1;
+
+ // Switch storage mechanism if possible
+ if (precision <= 16) {
+ switchStorage();
+ }
+
+ } else {
+ if (bcdLong == 0L) {
+ // Number is zero
+ setBcdToZero();
+ return;
+ }
+
+ // Compact the number (remove trailing zeros)
+ int delta = Long.numberOfTrailingZeros(bcdLong) / 4;
+ bcdLong >>>= delta * 4;
+ scale += delta;
+
+ // Compute precision
+ precision = 16 - (Long.numberOfLeadingZeros(bcdLong) / 4);
+ }
+ }
+
+ /** Ensure that a byte array of at least 40 digits is allocated. */
+ private void ensureCapacity() {
+ ensureCapacity(40);
}
- }
-
- /**
- * Checks whether the bytes stored in this instance are all valid. For internal unit testing only.
- *
- * @return An error message if this instance is invalid, or null if this instance is healthy.
- * @internal
- * @deprecated This API is for ICU internal use only.
- */
- @Deprecated
- public String checkHealth() {
- if (usingBytes) {
- if (bcdLong != 0) return "Value in bcdLong but we are in byte mode";
- if (precision == 0) return "Zero precision but we are in byte mode";
- if (precision > bcdBytes.length) return "Precision exceeds length of byte array";
- if (getDigitPos(precision - 1) == 0) return "Most significant digit is zero in byte mode";
- if (getDigitPos(0) == 0) return "Least significant digit is zero in long mode";
- for (int i = 0; i < precision; i++) {
- if (getDigitPos(i) >= 10) return "Digit exceeding 10 in byte array";
- if (getDigitPos(i) < 0) return "Digit below 0 in byte array";
- }
- for (int i = precision; i < bcdBytes.length; i++) {
- if (getDigitPos(i) != 0) return "Nonzero digits outside of range in byte array";
- }
- } else {
- if (bcdBytes != null) {
- for (int i = 0; i < bcdBytes.length; i++) {
- if (bcdBytes[i] != 0) return "Nonzero digits in byte array but we are in long mode";
+
+ private void ensureCapacity(int capacity) {
+ if (capacity == 0)
+ return;
+ int oldCapacity = usingBytes ? bcdBytes.length : 0;
+ if (!usingBytes) {
+ bcdBytes = new byte[capacity];
+ } else if (oldCapacity < capacity) {
+ byte[] bcd1 = new byte[capacity * 2];
+ System.arraycopy(bcdBytes, 0, bcd1, 0, oldCapacity);
+ bcdBytes = bcd1;
}
- }
- if (precision == 0 && bcdLong != 0) return "Value in bcdLong even though precision is zero";
- if (precision > 16) return "Precision exceeds length of long";
- if (precision != 0 && getDigitPos(precision - 1) == 0)
- return "Most significant digit is zero in long mode";
- if (precision != 0 && getDigitPos(0) == 0)
- return "Least significant digit is zero in long mode";
- for (int i = 0; i < precision; i++) {
- if (getDigitPos(i) >= 10) return "Digit exceeding 10 in long";
- if (getDigitPos(i) < 0) return "Digit below 0 in long (?!)";
- }
- for (int i = precision; i < 16; i++) {
- if (getDigitPos(i) != 0) return "Nonzero digits outside of range in long";
- }
+ usingBytes = true;
}
- return null;
- }
-
- /**
- * Checks whether this {@link DecimalQuantity_DualStorageBCD} is using its internal byte array storage mechanism.
- *
- * @return true if an internal byte array is being used; false if a long is being used.
- * @internal
- * @deprecated This API is ICU internal only.
- */
- @Deprecated
- public boolean isUsingBytes() {
- return usingBytes;
- }
-
- @Override
- public String toString() {
- return String.format(
- "<DecimalQuantity %s:%d:%d:%s %s %s>",
- (lOptPos > 1000 ? "999" : String.valueOf(lOptPos)),
- lReqPos,
- rReqPos,
- (rOptPos < -1000 ? "-999" : String.valueOf(rOptPos)),
- (usingBytes ? "bytes" : "long"),
- toNumberString());
- }
-
- public String toNumberString() {
- StringBuilder sb = new StringBuilder();
- if (usingBytes) {
- for (int i = precision - 1; i >= 0; i--) {
- sb.append(bcdBytes[i]);
+ /** Switches the internal storage mechanism between the 64-bit long and the byte array. */
+ private void switchStorage() {
+ if (usingBytes) {
+ // Change from bytes to long
+ bcdLong = 0L;
+ for (int i = precision - 1; i >= 0; i--) {
+ bcdLong <<= 4;
+ bcdLong |= bcdBytes[i];
+ }
+ bcdBytes = null;
+ usingBytes = false;
+ } else {
+ // Change from long to bytes
+ ensureCapacity();
+ for (int i = 0; i < precision; i++) {
+ bcdBytes[i] = (byte) (bcdLong & 0xf);
+ bcdLong >>>= 4;
+ }
+ assert usingBytes;
}
- } else {
- sb.append(Long.toHexString(bcdLong));
- }
- sb.append("E");
- sb.append(scale);
- return sb.toString();
- }
+ }
+
+ @Override
+ protected void copyBcdFrom(DecimalQuantity _other) {
+ DecimalQuantity_DualStorageBCD other = (DecimalQuantity_DualStorageBCD) _other;
+ setBcdToZero();
+ if (other.usingBytes) {
+ ensureCapacity(other.precision);
+ System.arraycopy(other.bcdBytes, 0, bcdBytes, 0, other.precision);
+ } else {
+ bcdLong = other.bcdLong;
+ }
+ }
+
+ /**
+ * Checks whether the bytes stored in this instance are all valid. For internal unit testing only.
+ *
+ * @return An error message if this instance is invalid, or null if this instance is healthy.
+ * @internal
+ * @deprecated This API is for ICU internal use only.
+ */
+ @Deprecated
+ public String checkHealth() {
+ if (usingBytes) {
+ if (bcdLong != 0)
+ return "Value in bcdLong but we are in byte mode";
+ if (precision == 0)
+ return "Zero precision but we are in byte mode";
+ if (precision > bcdBytes.length)
+ return "Precision exceeds length of byte array";
+ if (getDigitPos(precision - 1) == 0)
+ return "Most significant digit is zero in byte mode";
+ if (getDigitPos(0) == 0)
+ return "Least significant digit is zero in long mode";
+ for (int i = 0; i < precision; i++) {
+ if (getDigitPos(i) >= 10)
+ return "Digit exceeding 10 in byte array";
+ if (getDigitPos(i) < 0)
+ return "Digit below 0 in byte array";
+ }
+ for (int i = precision; i < bcdBytes.length; i++) {
+ if (getDigitPos(i) != 0)
+ return "Nonzero digits outside of range in byte array";
+ }
+ } else {
+ if (bcdBytes != null) {
+ for (int i = 0; i < bcdBytes.length; i++) {
+ if (bcdBytes[i] != 0)
+ return "Nonzero digits in byte array but we are in long mode";
+ }
+ }
+ if (precision == 0 && bcdLong != 0)
+ return "Value in bcdLong even though precision is zero";
+ if (precision > 16)
+ return "Precision exceeds length of long";
+ if (precision != 0 && getDigitPos(precision - 1) == 0)
+ return "Most significant digit is zero in long mode";
+ if (precision != 0 && getDigitPos(0) == 0)
+ return "Least significant digit is zero in long mode";
+ for (int i = 0; i < precision; i++) {
+ if (getDigitPos(i) >= 10)
+ return "Digit exceeding 10 in long";
+ if (getDigitPos(i) < 0)
+ return "Digit below 0 in long (?!)";
+ }
+ for (int i = precision; i < 16; i++) {
+ if (getDigitPos(i) != 0)
+ return "Nonzero digits outside of range in long";
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Checks whether this {@link DecimalQuantity_DualStorageBCD} is using its internal byte array
+ * storage mechanism.
+ *
+ * @return true if an internal byte array is being used; false if a long is being used.
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ public boolean isUsingBytes() {
+ return usingBytes;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("<DecimalQuantity %s:%d:%d:%s %s %s>",
+ (lOptPos > 1000 ? "999" : String.valueOf(lOptPos)),
+ lReqPos,
+ rReqPos,
+ (rOptPos < -1000 ? "-999" : String.valueOf(rOptPos)),
+ (usingBytes ? "bytes" : "long"),
+ toNumberString());
+ }
+
+ public String toNumberString() {
+ StringBuilder sb = new StringBuilder();
+ if (usingBytes) {
+ for (int i = precision - 1; i >= 0; i--) {
+ sb.append(bcdBytes[i]);
+ }
+ } else {
+ sb.append(Long.toHexString(bcdLong));
+ }
+ sb.append("E");
+ sb.append(scale);
+ return sb.toString();
+ }
}
import com.ibm.icu.util.ULocale;
public class MacroProps implements Cloneable {
- public Notation notation;
- public MeasureUnit unit;
- public MeasureUnit perUnit;
- public Rounder rounder;
- public Grouper grouper;
- public Padder padder;
- public IntegerWidth integerWidth;
- public Object symbols;
- public UnitWidth unitWidth;
- public SignDisplay sign;
- public DecimalSeparatorDisplay decimal;
- public AffixPatternProvider affixProvider; // not in API; for JDK compatibility mode only
- public MultiplierImpl multiplier; // not in API; for JDK compatibility mode only
- public PluralRules rules; // not in API; could be made public in the future
- public Long threshold; // not in API; controls internal self-regulation threshold
- public ULocale loc;
+ public Notation notation;
+ public MeasureUnit unit;
+ public MeasureUnit perUnit;
+ public Rounder rounder;
+ public Grouper grouper;
+ public Padder padder;
+ public IntegerWidth integerWidth;
+ public Object symbols;
+ public UnitWidth unitWidth;
+ public SignDisplay sign;
+ public DecimalSeparatorDisplay decimal;
+ public AffixPatternProvider affixProvider; // not in API; for JDK compatibility mode only
+ public MultiplierImpl multiplier; // not in API; for JDK compatibility mode only
+ public PluralRules rules; // not in API; could be made public in the future
+ public Long threshold; // not in API; controls internal self-regulation threshold
+ public ULocale loc;
- /**
- * Copies values from fallback into this instance if they are null in this instance.
- *
- * @param fallback The instance to copy from; not modified by this operation.
- */
- public void fallback(MacroProps fallback) {
- if (notation == null) notation = fallback.notation;
- if (unit == null) unit = fallback.unit;
- if (perUnit == null) perUnit = fallback.perUnit;
- if (rounder == null) rounder = fallback.rounder;
- if (grouper == null) grouper = fallback.grouper;
- if (padder == null) padder = fallback.padder;
- if (integerWidth == null) integerWidth = fallback.integerWidth;
- if (symbols == null) symbols = fallback.symbols;
- if (unitWidth == null) unitWidth = fallback.unitWidth;
- if (sign == null) sign = fallback.sign;
- if (decimal == null) decimal = fallback.decimal;
- if (affixProvider == null) affixProvider = fallback.affixProvider;
- if (multiplier == null) multiplier = fallback.multiplier;
- if (rules == null) rules = fallback.rules;
- if (loc == null) loc = fallback.loc;
- }
+ /**
+ * Copies values from fallback into this instance if they are null in this instance.
+ *
+ * @param fallback
+ * The instance to copy from; not modified by this operation.
+ */
+ public void fallback(MacroProps fallback) {
+ if (notation == null)
+ notation = fallback.notation;
+ if (unit == null)
+ unit = fallback.unit;
+ if (perUnit == null)
+ perUnit = fallback.perUnit;
+ if (rounder == null)
+ rounder = fallback.rounder;
+ if (grouper == null)
+ grouper = fallback.grouper;
+ if (padder == null)
+ padder = fallback.padder;
+ if (integerWidth == null)
+ integerWidth = fallback.integerWidth;
+ if (symbols == null)
+ symbols = fallback.symbols;
+ if (unitWidth == null)
+ unitWidth = fallback.unitWidth;
+ if (sign == null)
+ sign = fallback.sign;
+ if (decimal == null)
+ decimal = fallback.decimal;
+ if (affixProvider == null)
+ affixProvider = fallback.affixProvider;
+ if (multiplier == null)
+ multiplier = fallback.multiplier;
+ if (rules == null)
+ rules = fallback.rules;
+ if (loc == null)
+ loc = fallback.loc;
+ }
- @Override
- public int hashCode() {
- return Utility.hash(
- notation,
- unit,
- perUnit,
- rounder,
- grouper,
- padder,
- integerWidth,
- symbols,
- unitWidth,
- sign,
- decimal,
- affixProvider,
- multiplier,
- rules,
- loc);
- }
+ @Override
+ public int hashCode() {
+ return Utility.hash(notation,
+ unit,
+ perUnit,
+ rounder,
+ grouper,
+ padder,
+ integerWidth,
+ symbols,
+ unitWidth,
+ sign,
+ decimal,
+ affixProvider,
+ multiplier,
+ rules,
+ loc);
+ }
- @Override
- public boolean equals(Object _other) {
- if (_other == null) return false;
- if (this == _other) return true;
- if (!(_other instanceof MacroProps)) return false;
- MacroProps other = (MacroProps) _other;
- return Utility.equals(notation, other.notation)
- && Utility.equals(unit, other.unit)
- && Utility.equals(perUnit, other.perUnit)
- && Utility.equals(rounder, other.rounder)
- && Utility.equals(grouper, other.grouper)
- && Utility.equals(padder, other.padder)
- && Utility.equals(integerWidth, other.integerWidth)
- && Utility.equals(symbols, other.symbols)
- && Utility.equals(unitWidth, other.unitWidth)
- && Utility.equals(sign, other.sign)
- && Utility.equals(decimal, other.decimal)
- && Utility.equals(affixProvider, other.affixProvider)
- && Utility.equals(multiplier, other.multiplier)
- && Utility.equals(rules, other.rules)
- && Utility.equals(loc, other.loc);
- }
+ @Override
+ public boolean equals(Object _other) {
+ if (_other == null)
+ return false;
+ if (this == _other)
+ return true;
+ if (!(_other instanceof MacroProps))
+ return false;
+ MacroProps other = (MacroProps) _other;
+ return Utility.equals(notation, other.notation)
+ && Utility.equals(unit, other.unit)
+ && Utility.equals(perUnit, other.perUnit)
+ && Utility.equals(rounder, other.rounder)
+ && Utility.equals(grouper, other.grouper)
+ && Utility.equals(padder, other.padder)
+ && Utility.equals(integerWidth, other.integerWidth)
+ && Utility.equals(symbols, other.symbols)
+ && Utility.equals(unitWidth, other.unitWidth)
+ && Utility.equals(sign, other.sign)
+ && Utility.equals(decimal, other.decimal)
+ && Utility.equals(affixProvider, other.affixProvider)
+ && Utility.equals(multiplier, other.multiplier)
+ && Utility.equals(rules, other.rules)
+ && Utility.equals(loc, other.loc);
+ }
- @Override
- public Object clone() {
- // TODO: Remove this method?
- try {
- return super.clone();
- } catch (CloneNotSupportedException e) {
- throw new AssertionError(e);
+ @Override
+ public Object clone() {
+ // TODO: Remove this method?
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError(e);
+ }
}
- }
}
/**
* @param immutable
- * Whether this MicroProps should behave as an immutable after construction with respect to the quantity
- * chain.
+ * Whether this MicroProps should behave as an immutable after construction with respect
+ * to the quantity chain.
*/
public MicroProps(boolean immutable) {
this.immutable = immutable;
package com.ibm.icu.impl.number;
/**
- * This interface is used when all number formatting settings, including the locale, are known, except for the quantity
- * itself. The {@link #processQuantity} method performs the final step in the number processing pipeline: it uses the
- * quantity to generate a finalized {@link MicroProps}, which can be used to render the number to output.
+ * This interface is used when all number formatting settings, including the locale, are known, except
+ * for the quantity itself. The {@link #processQuantity} method performs the final step in the number
+ * processing pipeline: it uses the quantity to generate a finalized {@link MicroProps}, which can be
+ * used to render the number to output.
*
* <p>
- * In other words, this interface is used for the parts of number processing that are <em>quantity-dependent</em>.
+ * In other words, this interface is used for the parts of number processing that are
+ * <em>quantity-dependent</em>.
*
* <p>
- * In order to allow for multiple different objects to all mutate the same MicroProps, a "chain" of MicroPropsGenerators
- * are linked together, and each one is responsible for manipulating a certain quantity-dependent part of the
- * MicroProps. At the top of the linked list is a base instance of {@link MicroProps} with properties that are not
- * quantity-dependent. Each element in the linked list calls {@link #processQuantity} on its "parent", then does its
- * work, and then returns the result.
+ * In order to allow for multiple different objects to all mutate the same MicroProps, a "chain" of
+ * MicroPropsGenerators are linked together, and each one is responsible for manipulating a certain
+ * quantity-dependent part of the MicroProps. At the top of the linked list is a base instance of
+ * {@link MicroProps} with properties that are not quantity-dependent. Each element in the linked list
+ * calls {@link #processQuantity} on its "parent", then does its work, and then returns the result.
*
* <p>
* A class implementing MicroPropsGenerator looks something like this:
*/
public interface MicroPropsGenerator {
/**
- * Considers the given {@link DecimalQuantity}, optionally mutates it, and returns a {@link MicroProps}.
+ * Considers the given {@link DecimalQuantity}, optionally mutates it, and returns a
+ * {@link MicroProps}.
*
* @param quantity
* The quantity for consideration and optional mutation.
package com.ibm.icu.impl.number;
/**
- * A Modifier is an object that can be passed through the formatting pipeline until it is finally applied to the string
- * builder. A Modifier usually contains a prefix and a suffix that are applied, but it could contain something else,
- * like a {@link com.ibm.icu.text.SimpleFormatter} pattern.
+ * A Modifier is an object that can be passed through the formatting pipeline until it is finally applied
+ * to the string builder. A Modifier usually contains a prefix and a suffix that are applied, but it
+ * could contain something else, like a {@link com.ibm.icu.text.SimpleFormatter} pattern.
*
- * A Modifier is usually immutable, except in cases such as {@link MutablePatternModifier}, which are mutable for performance
- * reasons.
+ * A Modifier is usually immutable, except in cases such as {@link MutablePatternModifier}, which are
+ * mutable for performance reasons.
*/
public interface Modifier {
* @param output
* The string builder to which to apply this modifier.
* @param leftIndex
- * The left index of the string within the builder. Equal to 0 when only one number is being formatted.
+ * The left index of the string within the builder. Equal to 0 when only one number is
+ * being formatted.
* @param rightIndex
- * The right index of the string within the string builder. Equal to length when only one number is being
- * formatted.
+ * The right index of the string within the string builder. Equal to length when only one
+ * number is being formatted.
* @return The number of characters (UTF-16 code units) that were added to the string builder.
*/
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex);
/**
- * Gets the length of the prefix. This information can be used in combination with {@link #apply} to extract the
- * prefix and suffix strings.
+ * Gets the length of the prefix. This information can be used in combination with {@link #apply} to
+ * extract the prefix and suffix strings.
*
* @return The number of characters (UTF-16 code units) in the prefix.
*/
public int getCodePointCount();
/**
- * Whether this modifier is strong. If a modifier is strong, it should always be applied immediately and not allowed
- * to bubble up. With regard to padding, strong modifiers are considered to be on the inside of the prefix and
- * suffix.
+ * Whether this modifier is strong. If a modifier is strong, it should always be applied immediately
+ * and not allowed to bubble up. With regard to padding, strong modifiers are considered to be on the
+ * inside of the prefix and suffix.
*
* @return Whether the modifier is strong.
*/
import java.math.BigDecimal;
public class MultiplierImpl implements MicroPropsGenerator {
- final int magnitudeMultiplier;
- final BigDecimal bigDecimalMultiplier;
- final MicroPropsGenerator parent;
+ final int magnitudeMultiplier;
+ final BigDecimal bigDecimalMultiplier;
+ final MicroPropsGenerator parent;
- public MultiplierImpl(int magnitudeMultiplier) {
- this.magnitudeMultiplier = magnitudeMultiplier;
- this.bigDecimalMultiplier = null;
- parent = null;
- }
+ public MultiplierImpl(int magnitudeMultiplier) {
+ this.magnitudeMultiplier = magnitudeMultiplier;
+ this.bigDecimalMultiplier = null;
+ parent = null;
+ }
- public MultiplierImpl(BigDecimal bigDecimalMultiplier) {
- this.magnitudeMultiplier = 0;
- this.bigDecimalMultiplier = bigDecimalMultiplier;
- parent = null;
- }
+ public MultiplierImpl(BigDecimal bigDecimalMultiplier) {
+ this.magnitudeMultiplier = 0;
+ this.bigDecimalMultiplier = bigDecimalMultiplier;
+ parent = null;
+ }
- private MultiplierImpl(MultiplierImpl base, MicroPropsGenerator parent) {
- this.magnitudeMultiplier = base.magnitudeMultiplier;
- this.bigDecimalMultiplier = base.bigDecimalMultiplier;
- this.parent = parent;
- }
+ private MultiplierImpl(MultiplierImpl base, MicroPropsGenerator parent) {
+ this.magnitudeMultiplier = base.magnitudeMultiplier;
+ this.bigDecimalMultiplier = base.bigDecimalMultiplier;
+ this.parent = parent;
+ }
- public MicroPropsGenerator copyAndChain(MicroPropsGenerator parent) {
- return new MultiplierImpl(this, parent);
- }
+ public MicroPropsGenerator copyAndChain(MicroPropsGenerator parent) {
+ return new MultiplierImpl(this, parent);
+ }
- @Override
- public MicroProps processQuantity(DecimalQuantity quantity) {
- MicroProps micros = parent.processQuantity(quantity);
- quantity.adjustMagnitude(magnitudeMultiplier);
- if (bigDecimalMultiplier != null) {
- quantity.multiplyBy(bigDecimalMultiplier);
+ @Override
+ public MicroProps processQuantity(DecimalQuantity quantity) {
+ MicroProps micros = parent.processQuantity(quantity);
+ quantity.adjustMagnitude(magnitudeMultiplier);
+ if (bigDecimalMultiplier != null) {
+ quantity.multiplyBy(bigDecimalMultiplier);
+ }
+ return micros;
}
- return micros;
- }
}
*/
public interface MultiplierProducer {
/**
- * Maps a magnitude to a multiplier in powers of ten. For example, in compact notation in English, a magnitude of 5
- * (e.g., 100,000) should return a multiplier of -3, since the number is displayed in thousands.
+ * Maps a magnitude to a multiplier in powers of ten. For example, in compact notation in English, a
+ * magnitude of 5 (e.g., 100,000) should return a multiplier of -3, since the number is displayed in
+ * thousands.
*
* @param magnitude
* The power of ten of the input number.
import com.ibm.icu.util.Currency;
/**
- * This class is a {@link Modifier} that wraps a decimal format pattern. It applies the pattern's affixes in
- * {@link Modifier#apply}.
+ * This class is a {@link Modifier} that wraps a decimal format pattern. It applies the pattern's affixes
+ * in {@link Modifier#apply}.
*
* <p>
- * In addition to being a Modifier, this class contains the business logic for substituting the correct locale symbols
- * into the affixes of the decimal format pattern.
+ * In addition to being a Modifier, this class contains the business logic for substituting the correct
+ * locale symbols into the affixes of the decimal format pattern.
*
* <p>
- * In order to use this class, create a new instance and call the following four setters: {@link #setPatternInfo},
- * {@link #setPatternAttributes}, {@link #setSymbols}, and {@link #setNumberProperties}. After calling these four
- * setters, the instance will be ready for use as a Modifier.
+ * In order to use this class, create a new instance and call the following four setters:
+ * {@link #setPatternInfo}, {@link #setPatternAttributes}, {@link #setSymbols}, and
+ * {@link #setNumberProperties}. After calling these four setters, the instance will be ready for use as
+ * a Modifier.
*
* <p>
- * This is a MUTABLE, NON-THREAD-SAFE class designed for performance. Do NOT save references to this or attempt to use
- * it from multiple threads! Instead, you can obtain a safe, immutable decimal format pattern modifier by calling
- * {@link MutablePatternModifier#createImmutable}, in effect treating this instance as a builder for the immutable
- * variant.
+ * This is a MUTABLE, NON-THREAD-SAFE class designed for performance. Do NOT save references to this or
+ * attempt to use it from multiple threads! Instead, you can obtain a safe, immutable decimal format
+ * pattern modifier by calling {@link MutablePatternModifier#createImmutable}, in effect treating this
+ * instance as a builder for the immutable variant.
*/
-public class MutablePatternModifier implements Modifier, SymbolProvider, CharSequence, MicroPropsGenerator {
+public class MutablePatternModifier
+ implements Modifier, SymbolProvider, CharSequence, MicroPropsGenerator {
// Modifier details
final boolean isStrong;
/**
* @param isStrong
* Whether the modifier should be considered strong. For more information, see
- * {@link Modifier#isStrong()}. Most of the time, decimal format pattern modifiers should be considered
- * as non-strong.
+ * {@link Modifier#isStrong()}. Most of the time, decimal format pattern modifiers should
+ * be considered as non-strong.
*/
public MutablePatternModifier(boolean isStrong) {
this.isStrong = isStrong;
/**
* Sets a reference to the parsed decimal format pattern, usually obtained from
- * {@link PatternStringParser#parseToPatternInfo(String)}, but any implementation of {@link AffixPatternProvider} is
- * accepted.
+ * {@link PatternStringParser#parseToPatternInfo(String)}, but any implementation of
+ * {@link AffixPatternProvider} is accepted.
*/
public void setPatternInfo(AffixPatternProvider patternInfo) {
this.patternInfo = patternInfo;
* @param unitWidth
* The width used to render currencies.
* @param rules
- * Required if the triple currency sign, "¤¤¤", appears in the pattern, which can be determined from the
- * convenience method {@link #needsPlurals()}.
+ * Required if the triple currency sign, "¤¤¤", appears in the pattern, which can be
+ * determined from the convenience method {@link #needsPlurals()}.
*/
- public void setSymbols(DecimalFormatSymbols symbols, Currency currency, UnitWidth unitWidth, PluralRules rules) {
+ public void setSymbols(
+ DecimalFormatSymbols symbols,
+ Currency currency,
+ UnitWidth unitWidth,
+ PluralRules rules) {
assert (rules != null) == needsPlurals();
this.symbols = symbols;
this.currency = currency;
* @param isNegative
* Whether the number is negative.
* @param plural
- * The plural form of the number, required only if the pattern contains the triple currency sign, "¤¤¤"
- * (and as indicated by {@link #needsPlurals()}).
+ * The plural form of the number, required only if the pattern contains the triple
+ * currency sign, "¤¤¤" (and as indicated by {@link #needsPlurals()}).
*/
public void setNumberProperties(boolean isNegative, StandardPlural plural) {
assert (plural != null) == needsPlurals();
}
/**
- * Returns true if the pattern represented by this MurkyModifier requires a plural keyword in order to localize.
- * This is currently true only if there is a currency long name placeholder in the pattern ("¤¤¤").
+ * Returns true if the pattern represented by this MurkyModifier requires a plural keyword in order
+ * to localize. This is currently true only if there is a currency long name placeholder in the
+ * pattern ("¤¤¤").
*/
public boolean needsPlurals() {
return patternInfo.containsSymbolType(AffixUtils.TYPE_CURRENCY_TRIPLE);
}
/**
- * Creates a new quantity-dependent Modifier that behaves the same as the current instance, but which is immutable
- * and can be saved for future use. The number properties in the current instance are mutated; all other properties
- * are left untouched.
+ * Creates a new quantity-dependent Modifier that behaves the same as the current instance, but which
+ * is immutable and can be saved for future use. The number properties in the current instance are
+ * mutated; all other properties are left untouched.
*
* <p>
* The resulting modifier cannot be used in a QuantityChain.
}
/**
- * Creates a new quantity-dependent Modifier that behaves the same as the current instance, but which is immutable
- * and can be saved for future use. The number properties in the current instance are mutated; all other properties
- * are left untouched.
+ * Creates a new quantity-dependent Modifier that behaves the same as the current instance, but which
+ * is immutable and can be saved for future use. The number properties in the current instance are
+ * mutated; all other properties are left untouched.
*
* @param parent
* The QuantityChain to which to chain this immutable.
}
/**
- * Uses the current properties to create a single {@link ConstantMultiFieldModifier} with currency spacing support
- * if required.
+ * Uses the current properties to create a single {@link ConstantMultiFieldModifier} with currency
+ * spacing support if required.
*
* @param a
- * A working NumberStringBuilder object; passed from the outside to prevent the need to create many new
- * instances if this method is called in a loop.
+ * A working NumberStringBuilder object; passed from the outside to prevent the need to
+ * create many new instances if this method is called in a loop.
* @param b
* Another working NumberStringBuilder object.
* @return The constant modifier object.
*/
- private ConstantMultiFieldModifier createConstantModifier(NumberStringBuilder a, NumberStringBuilder b) {
+ private ConstantMultiFieldModifier createConstantModifier(
+ NumberStringBuilder a,
+ NumberStringBuilder b) {
insertPrefix(a.clear(), 0);
insertSuffix(b.clear(), 0);
if (patternInfo.hasCurrencySign()) {
final PluralRules rules;
final MicroPropsGenerator parent;
- ImmutablePatternModifier(ParameterizedModifier pm, PluralRules rules, MicroPropsGenerator parent) {
+ ImmutablePatternModifier(
+ ParameterizedModifier pm,
+ PluralRules rules,
+ MicroPropsGenerator parent) {
this.pm = pm;
this.rules = rules;
this.parent = parent;
public int apply(NumberStringBuilder output, int leftIndex, int rightIndex) {
int prefixLen = insertPrefix(output, leftIndex);
int suffixLen = insertSuffix(output, rightIndex + prefixLen);
- CurrencySpacingEnabledModifier.applyCurrencySpacing(output, leftIndex, prefixLen, rightIndex + prefixLen,
- suffixLen, symbols);
+ CurrencySpacingEnabledModifier.applyCurrencySpacing(output,
+ leftIndex,
+ prefixLen,
+ rightIndex + prefixLen,
+ suffixLen,
+ symbols);
return prefixLen + suffixLen;
}
public int getPrefixLength() {
// Enter and exit CharSequence Mode to get the length.
enterCharSequenceMode(true);
- int result = AffixUtils.unescapedCodePointCount(this, this); // prefix length
+ int result = AffixUtils.unescapedCodePointCount(this, this); // prefix length
exitCharSequenceMode();
return result;
}
public int getCodePointCount() {
// Enter and exit CharSequence Mode to get the length.
enterCharSequenceMode(true);
- int result = AffixUtils.unescapedCodePointCount(this, this); // prefix length
+ int result = AffixUtils.unescapedCodePointCount(this, this); // prefix length
exitCharSequenceMode();
enterCharSequenceMode(false);
- result += AffixUtils.unescapedCodePointCount(this, this); // suffix length
+ result += AffixUtils.unescapedCodePointCount(this, this); // suffix length
exitCharSequenceMode();
return result;
}
// Plural currencies set via the API are formatted in LongNameHandler.
// This code path is used by DecimalFormat via CurrencyPluralInfo.
assert plural != null;
- return currency.getName(symbols.getULocale(), Currency.PLURAL_LONG_NAME, plural.getKeyword(), null);
+ return currency
+ .getName(symbols.getULocale(), Currency.PLURAL_LONG_NAME, plural.getKeyword(), null);
case AffixUtils.TYPE_CURRENCY_QUAD:
return "\uFFFD";
case AffixUtils.TYPE_CURRENCY_QUINT:
&& (signDisplay == SignDisplay.ALWAYS || signDisplay == SignDisplay.ACCOUNTING_ALWAYS)
&& patternInfo.positiveHasPlusSign() == false;
- // Should we use the affix from the negative subpattern? (If not, we will use the positive subpattern.)
+ // Should we use the affix from the negative subpattern? (If not, we will use the positive
+ // subpattern.)
boolean useNegativeAffixPattern = patternInfo.hasNegativeSubpattern()
&& (isNegative || (patternInfo.negativeHasMinusSign() && plusReplacesMinusSign));
public static final String FALLBACK_PADDING_STRING = "\u0020"; // i.e. a space
public enum PadPosition {
- BEFORE_PREFIX,
- AFTER_PREFIX,
- BEFORE_SUFFIX,
- AFTER_SUFFIX;
+ BEFORE_PREFIX, AFTER_PREFIX, BEFORE_SUFFIX, AFTER_SUFFIX;
- public static PadPosition fromOld(int old) {
- switch (old) {
- case com.ibm.icu.text.DecimalFormat.PAD_BEFORE_PREFIX:
- return PadPosition.BEFORE_PREFIX;
- case com.ibm.icu.text.DecimalFormat.PAD_AFTER_PREFIX:
- return PadPosition.AFTER_PREFIX;
- case com.ibm.icu.text.DecimalFormat.PAD_BEFORE_SUFFIX:
- return PadPosition.BEFORE_SUFFIX;
- case com.ibm.icu.text.DecimalFormat.PAD_AFTER_SUFFIX:
- return PadPosition.AFTER_SUFFIX;
- default:
- throw new IllegalArgumentException("Don't know how to map " + old);
+ public static PadPosition fromOld(int old) {
+ switch (old) {
+ case com.ibm.icu.text.DecimalFormat.PAD_BEFORE_PREFIX:
+ return PadPosition.BEFORE_PREFIX;
+ case com.ibm.icu.text.DecimalFormat.PAD_AFTER_PREFIX:
+ return PadPosition.AFTER_PREFIX;
+ case com.ibm.icu.text.DecimalFormat.PAD_BEFORE_SUFFIX:
+ return PadPosition.BEFORE_SUFFIX;
+ case com.ibm.icu.text.DecimalFormat.PAD_AFTER_SUFFIX:
+ return PadPosition.AFTER_SUFFIX;
+ default:
+ throw new IllegalArgumentException("Don't know how to map " + old);
+ }
}
- }
- public int toOld() {
- switch (this) {
- case BEFORE_PREFIX:
- return com.ibm.icu.text.DecimalFormat.PAD_BEFORE_PREFIX;
- case AFTER_PREFIX:
- return com.ibm.icu.text.DecimalFormat.PAD_AFTER_PREFIX;
- case BEFORE_SUFFIX:
- return com.ibm.icu.text.DecimalFormat.PAD_BEFORE_SUFFIX;
- case AFTER_SUFFIX:
- return com.ibm.icu.text.DecimalFormat.PAD_AFTER_SUFFIX;
- default:
- return -1; // silence compiler errors
+ public int toOld() {
+ switch (this) {
+ case BEFORE_PREFIX:
+ return com.ibm.icu.text.DecimalFormat.PAD_BEFORE_PREFIX;
+ case AFTER_PREFIX:
+ return com.ibm.icu.text.DecimalFormat.PAD_AFTER_PREFIX;
+ case BEFORE_SUFFIX:
+ return com.ibm.icu.text.DecimalFormat.PAD_BEFORE_SUFFIX;
+ case AFTER_SUFFIX:
+ return com.ibm.icu.text.DecimalFormat.PAD_AFTER_SUFFIX;
+ default:
+ return -1; // silence compiler errors
+ }
}
- }
}
/* like package-private */ public static final Padder NONE = new Padder(null, -1, null);
return targetWidth > 0;
}
- public int padAndApply(Modifier mod1, Modifier mod2, NumberStringBuilder string, int leftIndex, int rightIndex) {
+ public int padAndApply(
+ Modifier mod1,
+ Modifier mod2,
+ NumberStringBuilder string,
+ int leftIndex,
+ int rightIndex) {
int modLength = mod1.getCodePointCount() + mod2.getCodePointCount();
int requiredPadding = targetWidth - modLength - string.codePointCount();
- assert leftIndex == 0 && rightIndex == string.length(); // fix the previous line to remove this assertion
+ assert leftIndex == 0 && rightIndex == string.length(); // fix the previous line to remove this
+ // assertion
int length = 0;
if (requiredPadding <= 0) {
return length;
}
- private static int addPaddingHelper(String paddingString, int requiredPadding, NumberStringBuilder string,
+ private static int addPaddingHelper(
+ String paddingString,
+ int requiredPadding,
+ NumberStringBuilder string,
int index) {
for (int i = 0; i < requiredPadding; i++) {
// TODO: If appending to the end, this will cause actual insertion operations. Improve.
import com.ibm.icu.impl.StandardPlural;
/**
- * A ParameterizedModifier by itself is NOT a Modifier. Rather, it wraps a data structure containing two or more
- * Modifiers and returns the modifier appropriate for the current situation.
+ * A ParameterizedModifier by itself is NOT a Modifier. Rather, it wraps a data structure containing two
+ * or more Modifiers and returns the modifier appropriate for the current situation.
*/
public class ParameterizedModifier {
private final Modifier positive;
}
/**
- * This constructor prepares the ParameterizedModifier to be populated with a positive and negative Modifier for
- * multiple plural forms.
+ * This constructor prepares the ParameterizedModifier to be populated with a positive and negative
+ * Modifier for multiple plural forms.
*
* <p>
* If this constructor is used, a plural form MUST be passed to {@link #getModifier}.
public static final int IGNORE_ROUNDING_ALWAYS = 2;
/**
- * Runs the recursive descent parser on the given pattern string, returning a data structure with raw information
- * about the pattern string.
+ * Runs the recursive descent parser on the given pattern string, returning a data structure with raw
+ * information about the pattern string.
*
* <p>
* To obtain a more useful form of the data, consider using {@link #parseToProperties} instead.
* @param pattern
* The pattern string, like "#,##0.00"
* @param ignoreRounding
- * Whether to leave out rounding information (minFrac, maxFrac, and rounding increment) when parsing the
- * pattern. This may be desirable if a custom rounding mode, such as CurrencyUsage, is to be used
- * instead. One of {@link PatternStringParser#IGNORE_ROUNDING_ALWAYS},
+ * Whether to leave out rounding information (minFrac, maxFrac, and rounding increment)
+ * when parsing the pattern. This may be desirable if a custom rounding mode, such as
+ * CurrencyUsage, is to be used instead. One of
+ * {@link PatternStringParser#IGNORE_ROUNDING_ALWAYS},
* {@link PatternStringParser#IGNORE_ROUNDING_IF_CURRENCY}, or
* {@link PatternStringParser#IGNORE_ROUNDING_NEVER}.
* @return A property bag object.
}
/**
- * Parses a pattern string into an existing property bag. All properties that can be encoded into a pattern string
- * will be overwritten with either their default value or with the value coming from the pattern string. Properties
- * that cannot be encoded into a pattern string, such as rounding mode, are not modified.
+ * Parses a pattern string into an existing property bag. All properties that can be encoded into a
+ * pattern string will be overwritten with either their default value or with the value coming from
+ * the pattern string. Properties that cannot be encoded into a pattern string, such as rounding
+ * mode, are not modified.
*
* @param pattern
* The pattern string, like "#,##0.00"
* @throws IllegalArgumentException
* If there was a syntax error in the pattern string.
*/
- public static void parseToExistingProperties(String pattern, DecimalFormatProperties properties,
+ public static void parseToExistingProperties(
+ String pattern,
+ DecimalFormatProperties properties,
int ignoreRounding) {
parseToExistingPropertiesImpl(pattern, properties, ignoreRounding);
}
consumePadding(state, result, PadPosition.AFTER_SUFFIX);
}
- private static void consumePadding(ParserState state, ParsedSubpatternInfo result, PadPosition paddingLocation) {
+ private static void consumePadding(
+ ParserState state,
+ ParsedSubpatternInfo result,
+ PadPosition paddingLocation) {
if (state.peek() != '*') {
return;
}
/// END RECURSIVE DESCENT PARSER IMPLEMENTATION ///
///////////////////////////////////////////////////
- private static void parseToExistingPropertiesImpl(String pattern, DecimalFormatProperties properties, int ignoreRounding) {
+ private static void parseToExistingPropertiesImpl(
+ String pattern,
+ DecimalFormatProperties properties,
+ int ignoreRounding) {
if (pattern == null || pattern.length() == 0) {
// Backwards compatibility requires that we reset to the default values.
// TODO: Only overwrite the properties that "saveToProperties" normally touches?
}
/** Finalizes the temporary data stored in the ParsedPatternInfo to the Properties. */
- private static void patternInfoToProperties(DecimalFormatProperties properties, ParsedPatternInfo patternInfo,
+ private static void patternInfoToProperties(
+ DecimalFormatProperties properties,
+ ParsedPatternInfo patternInfo,
int _ignoreRounding) {
// Translate from PatternParseResult to Properties.
// Note that most data from "negative" is ignored per the specification of DecimalFormat.
properties.setMaximumFractionDigits(-1);
properties.setRoundingIncrement(null);
properties.setMinimumSignificantDigits(positive.integerAtSigns);
- properties.setMaximumSignificantDigits(positive.integerAtSigns + positive.integerTrailingHashSigns);
+ properties.setMaximumSignificantDigits(
+ positive.integerAtSigns + positive.integerTrailingHashSigns);
} else if (positive.rounding != null) {
if (!ignoreRounding) {
properties.setMinimumFractionDigits(minFrac);
properties.setMaximumFractionDigits(positive.fractionTotal);
- properties.setRoundingIncrement(positive.rounding.toBigDecimal().setScale(positive.fractionNumerals));
+ properties.setRoundingIncrement(
+ positive.rounding.toBigDecimal().setScale(positive.fractionNumerals));
} else {
properties.setMinimumFractionDigits(-1);
properties.setMaximumFractionDigits(-1);
// Padding settings
if (positive.paddingLocation != null) {
// The width of the positive prefix and suffix templates are included in the padding
- int paddingWidth = positive.widthExceptAffixes + AffixUtils.estimateLength(posPrefix)
+ int paddingWidth = positive.widthExceptAffixes
+ + AffixUtils.estimateLength(posPrefix)
+ AffixUtils.estimateLength(posSuffix);
properties.setFormatWidth(paddingWidth);
String rawPaddingString = patternInfo.getString(AffixPatternProvider.Flags.PADDING);
properties.setPositivePrefixPattern(posPrefix);
properties.setPositiveSuffixPattern(posSuffix);
if (patternInfo.negative != null) {
- properties.setNegativePrefixPattern(patternInfo
- .getString(AffixPatternProvider.Flags.NEGATIVE_SUBPATTERN | AffixPatternProvider.Flags.PREFIX));
- properties.setNegativeSuffixPattern(patternInfo.getString(AffixPatternProvider.Flags.NEGATIVE_SUBPATTERN));
+ properties.setNegativePrefixPattern(patternInfo.getString(
+ AffixPatternProvider.Flags.NEGATIVE_SUBPATTERN | AffixPatternProvider.Flags.PREFIX));
+ properties.setNegativeSuffixPattern(
+ patternInfo.getString(AffixPatternProvider.Flags.NEGATIVE_SUBPATTERN));
} else {
properties.setNegativePrefixPattern(null);
properties.setNegativeSuffixPattern(null);
* Creates a pattern string from a property bag.
*
* <p>
- * Since pattern strings support only a subset of the functionality available in a property bag, a new property bag
- * created from the string returned by this function may not be the same as the original property bag.
+ * Since pattern strings support only a subset of the functionality available in a property bag, a
+ * new property bag created from the string returned by this function may not be the same as the
+ * original property bag.
*
* @param properties
* The property bag to serialize.
// Figure out the grouping sizes.
int grouping1, grouping2, grouping;
- if (groupingSize != Math.min(dosMax, -1) && firstGroupingSize != Math.min(dosMax, -1)
+ if (groupingSize != Math.min(dosMax, -1)
+ && firstGroupingSize != Math.min(dosMax, -1)
&& groupingSize != firstGroupingSize) {
grouping = groupingSize;
grouping1 = groupingSize;
// Negative affixes
// Ignore if the negative prefix pattern is "-" and the negative suffix is empty
- if (np != null || ns != null || (npp == null && nsp != null)
+ if (np != null
+ || ns != null
+ || (npp == null && nsp != null)
|| (npp != null && (npp.length() != 1 || npp.charAt(0) != '-' || nsp.length() != 0))) {
sb.append(';');
if (npp != null)
}
/**
- * Converts a pattern between standard notation and localized notation. Localized notation means that instead of
- * using generic placeholders in the pattern, you use the corresponding locale-specific characters instead. For
- * example, in locale <em>fr-FR</em>, the period in the pattern "0.000" means "decimal" in standard notation (as it
- * does in every other locale), but it means "grouping" in localized notation.
+ * Converts a pattern between standard notation and localized notation. Localized notation means that
+ * instead of using generic placeholders in the pattern, you use the corresponding locale-specific
+ * characters instead. For example, in locale <em>fr-FR</em>, the period in the pattern "0.000" means
+ * "decimal" in standard notation (as it does in every other locale), but it means "grouping" in
+ * localized notation.
*
* <p>
- * A greedy string-substitution strategy is used to substitute locale symbols. If two symbols are ambiguous or have
- * the same prefix, the result is not well-defined.
+ * A greedy string-substitution strategy is used to substitute locale symbols. If two symbols are
+ * ambiguous or have the same prefix, the result is not well-defined.
*
* <p>
* Locale symbols are not allowed to contain the ASCII quote character.
* @param symbols
* The symbols corresponding to the localized pattern.
* @param toLocalized
- * true to convert from standard to localized notation; false to convert from localized to standard
- * notation.
+ * true to convert from standard to localized notation; false to convert from localized to
+ * standard notation.
* @return The pattern expressed in the other notation.
*/
- public static String convertLocalized(String input, DecimalFormatSymbols symbols, boolean toLocalized) {
+ public static String convertLocalized(
+ String input,
+ DecimalFormatSymbols symbols,
+ boolean toLocalized) {
if (input == null)
return null;
import java.io.Serializable;
/**
- * ICU 59 called the class DecimalFormatProperties as just Properties. We need to keep a thin implementation for the
- * purposes of serialization.
+ * ICU 59 called the class DecimalFormatProperties as just Properties. We need to keep a thin
+ * implementation for the purposes of serialization.
*/
public class Properties implements Serializable {
/** @author sffc */
public class RoundingUtils {
- public static final int SECTION_LOWER = 1;
- public static final int SECTION_MIDPOINT = 2;
- public static final int SECTION_UPPER = 3;
-
- /**
- * The default rounding mode.
- */
- public static final RoundingMode DEFAULT_ROUNDING_MODE = RoundingMode.HALF_EVEN;
-
- /**
- * The maximum number of fraction places, integer numerals, or significant digits.
- * TODO: This does not feel like the best home for this value.
- */
- public static final int MAX_INT_FRAC_SIG = 100;
-
- /**
- * Converts a rounding mode and metadata about the quantity being rounded to a boolean determining
- * whether the value should be rounded toward infinity or toward zero.
- *
- * <p>The parameters are of type int because benchmarks on an x86-64 processor against OpenJDK
- * showed that ints were demonstrably faster than enums in switch statements.
- *
- * @param isEven Whether the digit immediately before the rounding magnitude is even.
- * @param isNegative Whether the quantity is negative.
- * @param section Whether the part of the quantity to the right of the rounding magnitude is
- * exactly halfway between two digits, whether it is in the lower part (closer to zero), or
- * whether it is in the upper part (closer to infinity). See {@link #SECTION_LOWER}, {@link
- * #SECTION_MIDPOINT}, and {@link #SECTION_UPPER}.
- * @param roundingMode The integer version of the {@link RoundingMode}, which you can get via
- * {@link RoundingMode#ordinal}.
- * @param reference A reference object to be used when throwing an ArithmeticException.
- * @return true if the number should be rounded toward zero; false if it should be rounded toward
- * infinity.
- */
- public static boolean getRoundingDirection(
- boolean isEven, boolean isNegative, int section, int roundingMode, Object reference) {
- switch (roundingMode) {
- case BigDecimal.ROUND_UP:
- // round away from zero
- return false;
-
- case BigDecimal.ROUND_DOWN:
- // round toward zero
- return true;
-
- case BigDecimal.ROUND_CEILING:
- // round toward positive infinity
- return isNegative;
-
- case BigDecimal.ROUND_FLOOR:
- // round toward negative infinity
- return !isNegative;
-
- case BigDecimal.ROUND_HALF_UP:
- switch (section) {
- case SECTION_MIDPOINT:
+ public static final int SECTION_LOWER = 1;
+ public static final int SECTION_MIDPOINT = 2;
+ public static final int SECTION_UPPER = 3;
+
+ /**
+ * The default rounding mode.
+ */
+ public static final RoundingMode DEFAULT_ROUNDING_MODE = RoundingMode.HALF_EVEN;
+
+ /**
+ * The maximum number of fraction places, integer numerals, or significant digits. TODO: This does
+ * not feel like the best home for this value.
+ */
+ public static final int MAX_INT_FRAC_SIG = 100;
+
+ /**
+ * Converts a rounding mode and metadata about the quantity being rounded to a boolean determining
+ * whether the value should be rounded toward infinity or toward zero.
+ *
+ * <p>
+ * The parameters are of type int because benchmarks on an x86-64 processor against OpenJDK showed
+ * that ints were demonstrably faster than enums in switch statements.
+ *
+ * @param isEven
+ * Whether the digit immediately before the rounding magnitude is even.
+ * @param isNegative
+ * Whether the quantity is negative.
+ * @param section
+ * Whether the part of the quantity to the right of the rounding magnitude is exactly
+ * halfway between two digits, whether it is in the lower part (closer to zero), or
+ * whether it is in the upper part (closer to infinity). See {@link #SECTION_LOWER},
+ * {@link #SECTION_MIDPOINT}, and {@link #SECTION_UPPER}.
+ * @param roundingMode
+ * The integer version of the {@link RoundingMode}, which you can get via
+ * {@link RoundingMode#ordinal}.
+ * @param reference
+ * A reference object to be used when throwing an ArithmeticException.
+ * @return true if the number should be rounded toward zero; false if it should be rounded toward
+ * infinity.
+ */
+ public static boolean getRoundingDirection(
+ boolean isEven,
+ boolean isNegative,
+ int section,
+ int roundingMode,
+ Object reference) {
+ switch (roundingMode) {
+ case BigDecimal.ROUND_UP:
+ // round away from zero
return false;
- case SECTION_LOWER:
+
+ case BigDecimal.ROUND_DOWN:
+ // round toward zero
return true;
- case SECTION_UPPER:
- return false;
+
+ case BigDecimal.ROUND_CEILING:
+ // round toward positive infinity
+ return isNegative;
+
+ case BigDecimal.ROUND_FLOOR:
+ // round toward negative infinity
+ return !isNegative;
+
+ case BigDecimal.ROUND_HALF_UP:
+ switch (section) {
+ case SECTION_MIDPOINT:
+ return false;
+ case SECTION_LOWER:
+ return true;
+ case SECTION_UPPER:
+ return false;
+ }
+ break;
+
+ case BigDecimal.ROUND_HALF_DOWN:
+ switch (section) {
+ case SECTION_MIDPOINT:
+ return true;
+ case SECTION_LOWER:
+ return true;
+ case SECTION_UPPER:
+ return false;
+ }
+ break;
+
+ case BigDecimal.ROUND_HALF_EVEN:
+ switch (section) {
+ case SECTION_MIDPOINT:
+ return isEven;
+ case SECTION_LOWER:
+ return true;
+ case SECTION_UPPER:
+ return false;
+ }
+ break;
}
- break;
- case BigDecimal.ROUND_HALF_DOWN:
- switch (section) {
- case SECTION_MIDPOINT:
- return true;
- case SECTION_LOWER:
- return true;
- case SECTION_UPPER:
+ // Rounding mode UNNECESSARY
+ throw new ArithmeticException("Rounding is required on " + reference.toString());
+ }
+
+ /**
+ * Gets whether the given rounding mode's rounding boundary is at the midpoint. The rounding boundary
+ * is the point at which a number switches from being rounded down to being rounded up. For example,
+ * with rounding mode HALF_EVEN, HALF_UP, or HALF_DOWN, the rounding boundary is at the midpoint, and
+ * this function would return true. However, for UP, DOWN, CEILING, and FLOOR, the rounding boundary
+ * is at the "edge", and this function would return false.
+ *
+ * @param roundingMode
+ * The integer version of the {@link RoundingMode}.
+ * @return true if rounding mode is HALF_EVEN, HALF_UP, or HALF_DOWN; false otherwise.
+ */
+ public static boolean roundsAtMidpoint(int roundingMode) {
+ switch (roundingMode) {
+ case BigDecimal.ROUND_UP:
+ case BigDecimal.ROUND_DOWN:
+ case BigDecimal.ROUND_CEILING:
+ case BigDecimal.ROUND_FLOOR:
return false;
- }
- break;
- case BigDecimal.ROUND_HALF_EVEN:
- switch (section) {
- case SECTION_MIDPOINT:
- return isEven;
- case SECTION_LOWER:
+ default:
return true;
- case SECTION_UPPER:
- return false;
}
- break;
}
- // Rounding mode UNNECESSARY
- throw new ArithmeticException("Rounding is required on " + reference.toString());
- }
-
- /**
- * Gets whether the given rounding mode's rounding boundary is at the midpoint. The rounding
- * boundary is the point at which a number switches from being rounded down to being rounded up.
- * For example, with rounding mode HALF_EVEN, HALF_UP, or HALF_DOWN, the rounding boundary is at
- * the midpoint, and this function would return true. However, for UP, DOWN, CEILING, and FLOOR,
- * the rounding boundary is at the "edge", and this function would return false.
- *
- * @param roundingMode The integer version of the {@link RoundingMode}.
- * @return true if rounding mode is HALF_EVEN, HALF_UP, or HALF_DOWN; false otherwise.
- */
- public static boolean roundsAtMidpoint(int roundingMode) {
- switch (roundingMode) {
- case BigDecimal.ROUND_UP:
- case BigDecimal.ROUND_DOWN:
- case BigDecimal.ROUND_CEILING:
- case BigDecimal.ROUND_FLOOR:
- return false;
-
- default:
- return true;
- }
- }
+ private static final MathContext[] MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED = new MathContext[RoundingMode
+ .values().length];
- private static final MathContext[] MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED =
- new MathContext[RoundingMode.values().length];
+ private static final MathContext[] MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS = new MathContext[RoundingMode
+ .values().length];
- private static final MathContext[] MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS =
- new MathContext[RoundingMode.values().length];
+ static {
+ for (int i = 0; i < MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS.length; i++) {
+ MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[i] = new MathContext(0, RoundingMode.valueOf(i));
+ MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS[i] = new MathContext(34);
+ }
+ }
- static {
- for (int i = 0; i < MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS.length; i++) {
- MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[i] = new MathContext(0, RoundingMode.valueOf(i));
- MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS[i] = new MathContext(34);
+ /**
+ * Gets the user-specified math context out of the property bag. If there is none, falls back to a
+ * math context with unlimited precision and the user-specified rounding mode, which defaults to
+ * HALF_EVEN (the IEEE 754R default).
+ *
+ * @param properties
+ * The property bag.
+ * @return A {@link MathContext}. Never null.
+ */
+ public static MathContext getMathContextOrUnlimited(DecimalFormatProperties properties) {
+ MathContext mathContext = properties.getMathContext();
+ if (mathContext == null) {
+ RoundingMode roundingMode = properties.getRoundingMode();
+ if (roundingMode == null)
+ roundingMode = RoundingMode.HALF_EVEN;
+ mathContext = MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[roundingMode.ordinal()];
+ }
+ return mathContext;
}
- }
-
- /**
- * Gets the user-specified math context out of the property bag. If there is none, falls back to a
- * math context with unlimited precision and the user-specified rounding mode, which defaults to
- * HALF_EVEN (the IEEE 754R default).
- *
- * @param properties The property bag.
- * @return A {@link MathContext}. Never null.
- */
- public static MathContext getMathContextOrUnlimited(DecimalFormatProperties properties) {
- MathContext mathContext = properties.getMathContext();
- if (mathContext == null) {
- RoundingMode roundingMode = properties.getRoundingMode();
- if (roundingMode == null) roundingMode = RoundingMode.HALF_EVEN;
- mathContext = MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[roundingMode.ordinal()];
+
+ /**
+ * Gets the user-specified math context out of the property bag. If there is none, falls back to a
+ * math context with 34 digits of precision (the 128-bit IEEE 754R default) and the user-specified
+ * rounding mode, which defaults to HALF_EVEN (the IEEE 754R default).
+ *
+ * @param properties
+ * The property bag.
+ * @return A {@link MathContext}. Never null.
+ */
+ public static MathContext getMathContextOr34Digits(DecimalFormatProperties properties) {
+ MathContext mathContext = properties.getMathContext();
+ if (mathContext == null) {
+ RoundingMode roundingMode = properties.getRoundingMode();
+ if (roundingMode == null)
+ roundingMode = RoundingMode.HALF_EVEN;
+ mathContext = MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS[roundingMode.ordinal()];
+ }
+ return mathContext;
}
- return mathContext;
- }
-
- /**
- * Gets the user-specified math context out of the property bag. If there is none, falls back to a
- * math context with 34 digits of precision (the 128-bit IEEE 754R default) and the user-specified
- * rounding mode, which defaults to HALF_EVEN (the IEEE 754R default).
- *
- * @param properties The property bag.
- * @return A {@link MathContext}. Never null.
- */
- public static MathContext getMathContextOr34Digits(DecimalFormatProperties properties) {
- MathContext mathContext = properties.getMathContext();
- if (mathContext == null) {
- RoundingMode roundingMode = properties.getRoundingMode();
- if (roundingMode == null) roundingMode = RoundingMode.HALF_EVEN;
- mathContext = MATH_CONTEXT_BY_ROUNDING_MODE_34_DIGITS[roundingMode.ordinal()];
+
+ /**
+ * Gets a MathContext with unlimited precision and the specified RoundingMode. Equivalent to "new
+ * MathContext(0, roundingMode)", but pulls from a singleton to prevent object thrashing.
+ *
+ * @param roundingMode
+ * The {@link RoundingMode} to use.
+ * @return The corresponding {@link MathContext}.
+ */
+ public static MathContext mathContextUnlimited(RoundingMode roundingMode) {
+ return MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[roundingMode.ordinal()];
}
- return mathContext;
- }
-
- /**
- * Gets a MathContext with unlimited precision and the specified RoundingMode. Equivalent to "new
- * MathContext(0, roundingMode)", but pulls from a singleton to prevent object thrashing.
- *
- * @param roundingMode The {@link RoundingMode} to use.
- * @return The corresponding {@link MathContext}.
- */
- public static MathContext mathContextUnlimited(RoundingMode roundingMode) {
- return MATH_CONTEXT_BY_ROUNDING_MODE_UNLIMITED[roundingMode.ordinal()];
- }
}
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.util.ULocale;
-
/**
- * A class that defines the scientific notation style to be used when formatting numbers in NumberFormatter.
+ * A class that defines the scientific notation style to be used when formatting numbers in
+ * NumberFormatter.
*
* <p>
- * This class exposes no public functionality. To create a CompactNotation, use one of the factory methods in
- * {@link Notation}.
+ * This class exposes no public functionality. To create a CompactNotation, use one of the factory
+ * methods in {@link Notation}.
*
* @draft ICU 60
* @provisional This API might change or be removed in a future release.
this.compactCustomData = compactCustomData;
}
- /* package-private */ MicroPropsGenerator withLocaleData(ULocale locale, String nsName, CompactType compactType,
- PluralRules rules, MutablePatternModifier buildReference, MicroPropsGenerator parent) {
+ /* package-private */ MicroPropsGenerator withLocaleData(
+ ULocale locale,
+ String nsName,
+ CompactType compactType,
+ PluralRules rules,
+ MutablePatternModifier buildReference,
+ MicroPropsGenerator parent) {
// TODO: Add a data cache? It would be keyed by locale, nsName, compact type, and compact style.
return new CompactHandler(this, locale, nsName, compactType, rules, buildReference, parent);
}
final Map<String, CompactModInfo> precomputedMods;
final CompactData data;
- private CompactHandler(CompactNotation notation, ULocale locale, String nsName, CompactType compactType,
- PluralRules rules, MutablePatternModifier buildReference, MicroPropsGenerator parent) {
+ private CompactHandler(
+ CompactNotation notation,
+ ULocale locale,
+ String nsName,
+ CompactType compactType,
+ PluralRules rules,
+ MutablePatternModifier buildReference,
+ MicroPropsGenerator parent) {
this.rules = rules;
this.parent = parent;
this.data = new CompactData();
import com.ibm.icu.util.Currency;
/**
- * A class that defines a rounding strategy parameterized by a currency to be used when formatting numbers in
- * NumberFormatter.
+ * A class that defines a rounding strategy parameterized by a currency to be used when formatting
+ * numbers in NumberFormatter.
*
* <p>
* To create a CurrencyRounder, use one of the factory methods on Rounder.
* Associates a currency with this rounding strategy.
*
* <p>
- * <strong>Calling this method is <em>not required</em></strong>, because the currency specified in unit() or via a
- * CurrencyAmount passed into format(Measure) is automatically applied to currency rounding strategies. However,
- * this method enables you to override that automatic association.
+ * <strong>Calling this method is <em>not required</em></strong>, because the currency specified in
+ * unit() or via a CurrencyAmount passed into format(Measure) is automatically applied to currency
+ * rounding strategies. However, this method enables you to override that automatic association.
*
* <p>
- * This method also enables numbers to be formatted using currency rounding rules without explicitly using a
- * currency format.
+ * This method also enables numbers to be formatted using currency rounding rules without explicitly
+ * using a currency format.
*
* @param currency
* The currency to associate with this rounding strategy.
import com.ibm.icu.impl.number.RoundingUtils;
/**
- * A class that defines a rounding strategy based on a number of fraction places and optionally significant digits to be
- * used when formatting numbers in NumberFormatter.
+ * A class that defines a rounding strategy based on a number of fraction places and optionally
+ * significant digits to be used when formatting numbers in NumberFormatter.
*
* <p>
* To create a FractionRounder, use one of the factory methods on Rounder.
}
/**
- * Ensure that no less than this number of significant digits are retained when rounding according to fraction
- * rules.
+ * Ensure that no less than this number of significant digits are retained when rounding according to
+ * fraction rules.
*
* <p>
- * For example, with integer rounding, the number 3.141 becomes "3". However, with minimum figures set to 2, 3.141
- * becomes "3.1" instead.
+ * For example, with integer rounding, the number 3.141 becomes "3". However, with minimum figures
+ * set to 2, 3.141 becomes "3.1" instead.
*
* <p>
- * This setting does not affect the number of trailing zeros. For example, 3.01 would print as "3", not "3.0".
+ * This setting does not affect the number of trailing zeros. For example, 3.01 would print as "3",
+ * not "3.0".
*
* @param minSignificantDigits
* The number of significant figures to guarantee.
if (minSignificantDigits >= 1 && minSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
return constructFractionSignificant(this, minSignificantDigits, -1);
} else {
- throw new IllegalArgumentException(
- "Significant digits must be between 1 and " + RoundingUtils.MAX_INT_FRAC_SIG + " (inclusive)");
+ throw new IllegalArgumentException("Significant digits must be between 1 and "
+ + RoundingUtils.MAX_INT_FRAC_SIG
+ + " (inclusive)");
}
}
/**
- * Ensure that no more than this number of significant digits are retained when rounding according to fraction
- * rules.
+ * Ensure that no more than this number of significant digits are retained when rounding according to
+ * fraction rules.
*
* <p>
- * For example, with integer rounding, the number 123.4 becomes "123". However, with maximum figures set to 2, 123.4
- * becomes "120" instead.
+ * For example, with integer rounding, the number 123.4 becomes "123". However, with maximum figures
+ * set to 2, 123.4 becomes "120" instead.
*
* <p>
- * This setting does not affect the number of trailing zeros. For example, with fixed fraction of 2, 123.4 would
- * become "120.00".
+ * This setting does not affect the number of trailing zeros. For example, with fixed fraction of 2,
+ * 123.4 would become "120.00".
*
* @param maxSignificantDigits
* Round the number to no more than this number of significant figures.
if (maxSignificantDigits >= 1 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
return constructFractionSignificant(this, -1, maxSignificantDigits);
} else {
- throw new IllegalArgumentException(
- "Significant digits must be between 1 and " + RoundingUtils.MAX_INT_FRAC_SIG + " (inclusive)");
+ throw new IllegalArgumentException("Significant digits must be between 1 and "
+ + RoundingUtils.MAX_INT_FRAC_SIG
+ + " (inclusive)");
}
}
}
\ No newline at end of file
return false;
}
position -= grouping1;
- return position >= 0 && (position % grouping2) == 0
+ return position >= 0
+ && (position % grouping2) == 0
&& value.getUpperDisplayMagnitude() - grouping1 + 1 >= (min2 ? 2 : 1);
}
}
\ No newline at end of file
}
/**
- * Pad numbers at the beginning with zeros to guarantee a certain number of numerals before the decimal separator.
+ * Pad numbers at the beginning with zeros to guarantee a certain number of numerals before the
+ * decimal separator.
*
* <p>
* For example, with minInt=3, the number 55 will get printed as "055".
} else if (minInt >= 0 && minInt <= RoundingUtils.MAX_INT_FRAC_SIG) {
return new IntegerWidth(minInt, -1);
} else {
- throw new IllegalArgumentException(
- "Integer digits must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG + " (inclusive)");
+ throw new IllegalArgumentException("Integer digits must be between 0 and "
+ + RoundingUtils.MAX_INT_FRAC_SIG
+ + " (inclusive)");
}
}
* For example, with maxInt=3, the number 1234 will get printed as "234".
*
* @param maxInt
- * The maximum number of places before the decimal separator. maxInt == -1 means no truncation.
+ * The maximum number of places before the decimal separator. maxInt == -1 means no
+ * truncation.
* @return An IntegerWidth for passing to the NumberFormatter integerWidth() setter.
* @draft ICU 60
* @provisional This API might change or be removed in a future release.
} else if (maxInt == -1) {
return new IntegerWidth(minInt, -1);
} else {
- throw new IllegalArgumentException(
- "Integer digits must be between -1 and " + RoundingUtils.MAX_INT_FRAC_SIG + " (inclusive)");
+ throw new IllegalArgumentException("Integer digits must be between -1 and "
+ + RoundingUtils.MAX_INT_FRAC_SIG
+ + " (inclusive)");
}
}
}
\ No newline at end of file
}
/**
- * Format the given byte, short, int, or long to a string using the settings specified in the NumberFormatter fluent
- * setting chain.
+ * Format the given byte, short, int, or long to a string using the settings specified in the
+ * NumberFormatter fluent setting chain.
*
* @param input
* The number to format.
}
/**
- * Format the given float or double to a string using the settings specified in the NumberFormatter fluent setting
- * chain.
+ * Format the given float or double to a string using the settings specified in the NumberFormatter
+ * fluent setting chain.
*
* @param input
* The number to format.
}
/**
- * Format the given {@link BigInteger}, {@link BigDecimal}, or other {@link Number} to a string using the settings
- * specified in the NumberFormatter fluent setting chain.
+ * Format the given {@link BigInteger}, {@link BigDecimal}, or other {@link Number} to a string using
+ * the settings specified in the NumberFormatter fluent setting chain.
*
* @param input
* The number to format.
}
/**
- * Format the given {@link Measure} or {@link CurrencyAmount} to a string using the settings specified in the
- * NumberFormatter fluent setting chain.
+ * Format the given {@link Measure} or {@link CurrencyAmount} to a string using the settings
+ * specified in the NumberFormatter fluent setting chain.
*
* <p>
- * The unit specified here overrides any unit that may have been specified in the setter chain. This method is
- * intended for cases when each input to the number formatter has a different unit.
+ * The unit specified here overrides any unit that may have been specified in the setter chain. This
+ * method is intended for cases when each input to the number formatter has a different unit.
*
* @param input
* The number to format.
}
/**
- * This is the core entrypoint to the number formatting pipeline. It performs self-regulation: a static code path
- * for the first few calls, and compiling a more efficient data structure if called repeatedly.
+ * This is the core entrypoint to the number formatting pipeline. It performs self-regulation: a
+ * static code path for the first few calls, and compiling a more efficient data structure if called
+ * repeatedly.
*
* <p>
* This function is very hot, being called in every call to the number formatting pipeline.
public class Notation {
// TODO: Support engineering intervals other than 3?
- private static final ScientificNotation SCIENTIFIC = new ScientificNotation(1, false, 1, SignDisplay.AUTO);
- private static final ScientificNotation ENGINEERING = new ScientificNotation(3, false, 1, SignDisplay.AUTO);
+ private static final ScientificNotation SCIENTIFIC = new ScientificNotation(1,
+ false,
+ 1,
+ SignDisplay.AUTO);
+ private static final ScientificNotation ENGINEERING = new ScientificNotation(3,
+ false,
+ 1,
+ SignDisplay.AUTO);
private static final CompactNotation COMPACT_SHORT = new CompactNotation(CompactStyle.SHORT);
private static final CompactNotation COMPACT_LONG = new CompactNotation(CompactStyle.LONG);
private static final SimpleNotation SIMPLE = new SimpleNotation();
}
/**
- * Print the number using scientific notation (also known as scientific form, standard index form, or standard form
- * in the UK). The format for scientific notation varies by locale; for example, many Western locales display the
- * number in the form "#E0", where the number is displayed with one digit before the decimal separator, zero or more
- * digits after the decimal separator, and the corresponding power of 10 displayed after the "E".
+ * Print the number using scientific notation (also known as scientific form, standard index form, or
+ * standard form in the UK). The format for scientific notation varies by locale; for example, many
+ * Western locales display the number in the form "#E0", where the number is displayed with one digit
+ * before the decimal separator, zero or more digits after the decimal separator, and the
+ * corresponding power of 10 displayed after the "E".
*
* <p>
* Example outputs in <em>en-US</em> when printing 8.765E4 through 8.765E-3:
}
/**
- * Print the number using engineering notation, a variant of scientific notation in which the exponent must be
- * divisible by 3.
+ * Print the number using engineering notation, a variant of scientific notation in which the
+ * exponent must be divisible by 3.
*
* <p>
* Example outputs in <em>en-US</em> when printing 8.765E4 through 8.765E-3:
* Print the number using short-form compact notation.
*
* <p>
- * <em>Compact notation</em>, defined in Unicode Technical Standard #35 Part 3 Section 2.4.1, prints numbers with
- * localized prefixes or suffixes corresponding to different powers of ten. Compact notation is similar to
- * engineering notation in how it scales numbers.
+ * <em>Compact notation</em>, defined in Unicode Technical Standard #35 Part 3 Section 2.4.1, prints
+ * numbers with localized prefixes or suffixes corresponding to different powers of ten. Compact
+ * notation is similar to engineering notation in how it scales numbers.
*
* <p>
- * Compact notation is ideal for displaying large numbers (over ~1000) to humans while at the same time minimizing
- * screen real estate.
+ * Compact notation is ideal for displaying large numbers (over ~1000) to humans while at the same
+ * time minimizing screen real estate.
*
* <p>
- * In short form, the powers of ten are abbreviated. In <em>en-US</em>, the abbreviations are "K" for thousands, "M"
- * for millions, "B" for billions, and "T" for trillions. Example outputs in <em>en-US</em> when printing 8.765E7
- * through 8.765E0:
+ * In short form, the powers of ten are abbreviated. In <em>en-US</em>, the abbreviations are "K" for
+ * thousands, "M" for millions, "B" for billions, and "T" for trillions. Example outputs in
+ * <em>en-US</em> when printing 8.765E7 through 8.765E0:
*
* <pre>
* 88M
* </pre>
*
* <p>
- * When compact notation is specified without an explicit rounding strategy, numbers are rounded off to the closest
- * integer after scaling the number by the corresponding power of 10, but with a digit shown after the decimal
- * separator if there is only one digit before the decimal separator. The default compact notation rounding strategy
- * is equivalent to:
+ * When compact notation is specified without an explicit rounding strategy, numbers are rounded off
+ * to the closest integer after scaling the number by the corresponding power of 10, but with a digit
+ * shown after the decimal separator if there is only one digit before the decimal separator. The
+ * default compact notation rounding strategy is equivalent to:
*
* <pre>
* Rounder.integer().withMinDigits(2)
* {@link #compactShort}.
*
* <p>
- * In long form, the powers of ten are spelled out fully. Example outputs in <em>en-US</em> when printing 8.765E7
- * through 8.765E0:
+ * In long form, the powers of ten are spelled out fully. Example outputs in <em>en-US</em> when
+ * printing 8.765E7 through 8.765E0:
*
* <pre>
* 88 million
}
/**
- * Print the number using simple notation without any scaling by powers of ten. This is the default behavior.
+ * Print the number using simple notation without any scaling by powers of ten. This is the default
+ * behavior.
*
* <p>
- * Since this is the default behavior, this method needs to be called only when it is necessary to override a
- * previous setting.
+ * Since this is the default behavior, this method needs to be called only when it is necessary to
+ * override a previous setting.
*
* <p>
* Example outputs in <em>en-US</em> when printing 8.765E7 through 8.765E0:
import com.ibm.icu.util.ULocale;
/**
- * The main entrypoint to the localized number formatting library introduced in ICU 60. Basic usage examples:
+ * The main entrypoint to the localized number formatting library introduced in ICU 60. Basic usage
+ * examples:
*
* <pre>
* // Most basic usage:
* </pre>
*
* <p>
- * This API offers more features than {@link com.ibm.icu.text.DecimalFormat} and is geared toward new users of ICU.
+ * This API offers more features than {@link com.ibm.icu.text.DecimalFormat} and is geared toward new
+ * users of ICU.
*
* <p>
- * NumberFormatter instances are immutable and thread safe. This means that invoking a configuration method has no
- * effect on the receiving instance; you must store and use the new number formatter instance it returns instead.
+ * NumberFormatter instances are immutable and thread safe. This means that invoking a configuration
+ * method has no effect on the receiving instance; you must store and use the new number formatter
+ * instance it returns instead.
*
* <pre>
- * UnlocalizedNumberFormatter formatter = UnlocalizedNumberFormatter.with().notation(Notation.scientific());
+ * UnlocalizedNumberFormatter formatter = UnlocalizedNumberFormatter.with()
+ * .notation(Notation.scientific());
* formatter.rounding(Rounder.maxFraction(2)); // does nothing!
* formatter.locale(ULocale.ENGLISH).format(9.8765).toString(); // prints "9.8765E0", not "9.88E0"
* </pre>
*
* <p>
- * This API is based on the <em>fluent</em> design pattern popularized by libraries such as Google's Guava. For
- * extensive details on the design of this API, read <a href="https://goo.gl/szi5VB">the design doc</a>.
+ * This API is based on the <em>fluent</em> design pattern popularized by libraries such as Google's
+ * Guava. For extensive details on the design of this API, read <a href="https://goo.gl/szi5VB">the
+ * design doc</a>.
*
* @author Shane Carr
* @draft ICU 60
private static final UnlocalizedNumberFormatter BASE = new UnlocalizedNumberFormatter();
/**
- * An enum declaring how to render units, including currencies. Example outputs when formatting 123 USD and 123
- * meters in <em>en-CA</em>:
+ * An enum declaring how to render units, including currencies. Example outputs when formatting 123
+ * USD and 123 meters in <em>en-CA</em>:
*
* <ul>
* <li>NARROW: "$123.00" and "123 m"
*/
public static enum UnitWidth {
/**
- * Print an abbreviated version of the unit name. Similar to SHORT, but always use the shortest available
- * abbreviation or symbol. This option can be used when the context hints at the identity of the unit. For more
- * information on the difference between NARROW and SHORT, see SHORT.
+ * Print an abbreviated version of the unit name. Similar to SHORT, but always use the shortest
+ * available abbreviation or symbol. This option can be used when the context hints at the
+ * identity of the unit. For more information on the difference between NARROW and SHORT, see
+ * SHORT.
*
* <p>
- * In CLDR, this option corresponds to the "Narrow" format for measure units and the "¤¤¤¤¤" placeholder for
- * currencies.
+ * In CLDR, this option corresponds to the "Narrow" format for measure units and the "¤¤¤¤¤"
+ * placeholder for currencies.
*
* @draft ICU 60
* @provisional This API might change or be removed in a future release.
NARROW,
/**
- * Print an abbreviated version of the unit name. Similar to NARROW, but use a slightly wider abbreviation or
- * symbol when there may be ambiguity. This is the default behavior.
+ * Print an abbreviated version of the unit name. Similar to NARROW, but use a slightly wider
+ * abbreviation or symbol when there may be ambiguity. This is the default behavior.
*
* <p>
- * For example, in <em>es-US</em>, the SHORT form for Fahrenheit is "{0} °F", but the NARROW form is "{0}°",
- * since Fahrenheit is the customary unit for temperature in that locale.
+ * For example, in <em>es-US</em>, the SHORT form for Fahrenheit is "{0} °F", but the NARROW form
+ * is "{0}°", since Fahrenheit is the customary unit for temperature in that locale.
*
* <p>
- * In CLDR, this option corresponds to the "Short" format for measure units and the "¤" placeholder for
- * currencies.
+ * In CLDR, this option corresponds to the "Short" format for measure units and the "¤"
+ * placeholder for currencies.
*
* @draft ICU 60
* @provisional This API might change or be removed in a future release.
* Print the full name of the unit, without any abbreviations.
*
* <p>
- * In CLDR, this option corresponds to the default format for measure units and the "¤¤¤" placeholder for
- * currencies.
+ * In CLDR, this option corresponds to the default format for measure units and the "¤¤¤"
+ * placeholder for currencies.
*
* @draft ICU 60
* @provisional This API might change or be removed in a future release.
FULL_NAME,
/**
- * Use the three-digit ISO XXX code in place of the symbol for displaying currencies. The behavior of this
- * option is currently undefined for use with measure units.
+ * Use the three-digit ISO XXX code in place of the symbol for displaying currencies. The
+ * behavior of this option is currently undefined for use with measure units.
*
* <p>
* In CLDR, this option corresponds to the "¤¤" placeholder for currencies.
ISO_CODE,
/**
- * Format the number according to the specified unit, but do not display the unit. For currencies, apply
- * monetary symbols and formats as with SHORT, but omit the currency symbol. For measure units, the behavior is
- * equivalent to not specifying the unit at all.
+ * Format the number according to the specified unit, but do not display the unit. For
+ * currencies, apply monetary symbols and formats as with SHORT, but omit the currency symbol.
+ * For measure units, the behavior is equivalent to not specifying the unit at all.
*
* @draft ICU 60
* @provisional This API might change or be removed in a future release.
}
/**
- * An enum declaring how to denote positive and negative numbers. Example outputs when formatting 123 and -123 in
- * <em>en-US</em>:
+ * An enum declaring how to denote positive and negative numbers. Example outputs when formatting 123
+ * and -123 in <em>en-US</em>:
*
* <ul>
* <li>AUTO: "123" and "-123"
*/
public static enum SignDisplay {
/**
- * Show the minus sign on negative numbers, and do not show the sign on positive numbers. This is the default
- * behavior.
+ * Show the minus sign on negative numbers, and do not show the sign on positive numbers. This is
+ * the default behavior.
*
* @draft ICU 60
* @provisional This API might change or be removed in a future release.
NEVER,
/**
- * Use the locale-dependent accounting format on negative numbers, and do not show the sign on positive numbers.
+ * Use the locale-dependent accounting format on negative numbers, and do not show the sign on
+ * positive numbers.
*
* <p>
- * The accounting format is defined in CLDR and varies by locale; in many Western locales, the format is a pair
- * of parentheses around the number.
+ * The accounting format is defined in CLDR and varies by locale; in many Western locales, the
+ * format is a pair of parentheses around the number.
*
* <p>
- * Note: Since CLDR defines the accounting format in the monetary context only, this option falls back to the
- * AUTO sign display strategy when formatting without a currency unit. This limitation may be lifted in the
- * future.
+ * Note: Since CLDR defines the accounting format in the monetary context only, this option falls
+ * back to the AUTO sign display strategy when formatting without a currency unit. This
+ * limitation may be lifted in the future.
*
* @draft ICU 60
* @provisional This API might change or be removed in a future release.
ACCOUNTING,
/**
- * Use the locale-dependent accounting format on negative numbers, and show the plus sign on positive numbers.
- * For more information on the accounting format, see the ACCOUNTING sign display strategy.
+ * Use the locale-dependent accounting format on negative numbers, and show the plus sign on
+ * positive numbers. For more information on the accounting format, see the ACCOUNTING sign
+ * display strategy.
*
* @draft ICU 60
* @provisional This API might change or be removed in a future release.
}
/**
- * An enum declaring how to render the decimal separator. Example outputs when formatting 1 and 1.1 in
- * <em>en-US</em>:
+ * An enum declaring how to render the decimal separator. Example outputs when formatting 1 and 1.1
+ * in <em>en-US</em>:
*
* <ul>
* <li>AUTO: "1" and "1.1"
*/
public static enum DecimalSeparatorDisplay {
/**
- * Show the decimal separator when there are one or more digits to display after the separator, and do not show
- * it otherwise. This is the default behavior.
+ * Show the decimal separator when there are one or more digits to display after the separator,
+ * and do not show it otherwise. This is the default behavior.
*
* @draft ICU 60
* @provisional This API might change or be removed in a future release.
}
/**
- * Use a default threshold of 3. This means that the third time .format() is called, the data structures get built
- * using the "safe" code path. The first two calls to .format() will trigger the unsafe code path.
+ * Use a default threshold of 3. This means that the third time .format() is called, the data
+ * structures get built using the "safe" code path. The first two calls to .format() will trigger the
+ * unsafe code path.
*/
static final long DEFAULT_THRESHOLD = 3;
}
/**
- * Call this method at the beginning of a NumberFormatter fluent chain in which the locale is not currently known at
- * the call site.
+ * Call this method at the beginning of a NumberFormatter fluent chain in which the locale is not
+ * currently known at the call site.
*
* @return An {@link UnlocalizedNumberFormatter}, to be used for chaining.
* @draft ICU 60
}
/**
- * Call this method at the beginning of a NumberFormatter fluent chain in which the locale is known at the call
- * site.
+ * Call this method at the beginning of a NumberFormatter fluent chain in which the locale is known
+ * at the call site.
*
* @param locale
* The locale from which to load formats and symbols for number formatting.
}
/**
- * Call this method at the beginning of a NumberFormatter fluent chain in which the locale is known at the call
- * site.
+ * Call this method at the beginning of a NumberFormatter fluent chain in which the locale is known
+ * at the call site.
*
* @param locale
* The locale from which to load formats and symbols for number formatting.
* @deprecated ICU 60 This API is ICU internal only.
*/
@Deprecated
- public static UnlocalizedNumberFormatter fromDecimalFormat(DecimalFormatProperties properties,
- DecimalFormatSymbols symbols, DecimalFormatProperties exportedProperties) {
+ public static UnlocalizedNumberFormatter fromDecimalFormat(
+ DecimalFormatProperties properties,
+ DecimalFormatSymbols symbols,
+ DecimalFormatProperties exportedProperties) {
MacroProps macros = NumberPropertyMapper.oldToNew(properties, symbols, exportedProperties);
return NumberFormatter.with().macros(macros);
}
import com.ibm.icu.util.MeasureUnit;
/**
- * This is the "brain" of the number formatting pipeline. It ties all the pieces together, taking in a MacroProps and a
- * DecimalQuantity and outputting a properly formatted number string.
+ * This is the "brain" of the number formatting pipeline. It ties all the pieces together, taking in a
+ * MacroProps and a DecimalQuantity and outputting a properly formatted number string.
*
* <p>
- * This class, as well as NumberPropertyMapper, could go into the impl package, but they depend on too many
- * package-private members of the public APIs.
+ * This class, as well as NumberPropertyMapper, could go into the impl package, but they depend on too
+ * many package-private members of the public APIs.
*/
class NumberFormatterImpl {
return new NumberFormatterImpl(microPropsGenerator);
}
- /** Builds and evaluates an "unsafe" MicroPropsGenerator, which is cheaper but can be used only once. */
- public static MicroProps applyStatic(MacroProps macros, DecimalQuantity inValue, NumberStringBuilder outString) {
+ /**
+ * Builds and evaluates an "unsafe" MicroPropsGenerator, which is cheaper but can be used only once.
+ */
+ public static MicroProps applyStatic(
+ MacroProps macros,
+ DecimalQuantity inValue,
+ NumberStringBuilder outString) {
MicroPropsGenerator microPropsGenerator = macrosToMicroGenerator(macros, false);
MicroProps micros = microPropsGenerator.processQuantity(inValue);
microsToString(micros, inValue, outString);
}
/**
- * Synthesizes the MacroProps into a MicroPropsGenerator. All information, including the locale, is encoded into the
- * MicroPropsGenerator, except for the quantity itself, which is left abstract and must be provided to the returned
- * MicroPropsGenerator instance.
+ * Synthesizes the MacroProps into a MicroPropsGenerator. All information, including the locale, is
+ * encoded into the MicroPropsGenerator, except for the quantity itself, which is left abstract and
+ * must be provided to the returned MicroPropsGenerator instance.
*
* @see MicroPropsGenerator
* @param macros
* The {@link MacroProps} to consume. This method does not mutate the MacroProps instance.
* @param safe
- * If true, the returned MicroPropsGenerator will be thread-safe. If false, the returned value will
- * <em>not</em> be thread-safe, intended for a single "one-shot" use only. Building the thread-safe
- * object is more expensive.
+ * If true, the returned MicroPropsGenerator will be thread-safe. If false, the returned
+ * value will <em>not</em> be thread-safe, intended for a single "one-shot" use only.
+ * Building the thread-safe object is more expensive.
*/
private static MicroPropsGenerator macrosToMicroGenerator(MacroProps macros, boolean safe) {
MicroProps micros = new MicroProps(safe);
boolean isPercent = isNoUnit && unitIsPercent(macros.unit);
boolean isPermille = isNoUnit && unitIsPermille(macros.unit);
boolean isCldrUnit = !isCurrency && !isNoUnit;
- boolean isAccounting = macros.sign == SignDisplay.ACCOUNTING || macros.sign == SignDisplay.ACCOUNTING_ALWAYS;
+ boolean isAccounting = macros.sign == SignDisplay.ACCOUNTING
+ || macros.sign == SignDisplay.ACCOUNTING_ALWAYS;
Currency currency = isCurrency ? (Currency) macros.unit : DEFAULT_CURRENCY;
UnitWidth unitWidth = UnitWidth.SHORT;
if (macros.unitWidth != null) {
} else if (!isCurrency || unitWidth == UnitWidth.FULL_NAME) {
patternStyle = NumberFormat.NUMBERSTYLE;
} else if (isAccounting) {
- // NOTE: Although ACCOUNTING and ACCOUNTING_ALWAYS are only supported in currencies right now,
+ // NOTE: Although ACCOUNTING and ACCOUNTING_ALWAYS are only supported in currencies right
+ // now,
// the API contract allows us to add support to other units in the future.
patternStyle = NumberFormat.ACCOUNTINGCURRENCYSTYLE;
} else {
patternStyle = NumberFormat.CURRENCYSTYLE;
}
- String pattern = NumberFormat.getPatternForStyleAndNumberingSystem(macros.loc, nsName, patternStyle);
+ String pattern = NumberFormat
+ .getPatternForStyleAndNumberingSystem(macros.loc, nsName, patternStyle);
ParsedPatternInfo patternInfo = PatternStringParser.parseToPatternInfo(pattern);
/////////////////////////////////////////////////////////////////////////////////////
// Lazily create PluralRules
rules = PluralRules.forLocale(macros.loc);
}
- chain = LongNameHandler.forMeasureUnit(macros.loc, macros.unit, macros.perUnit, unitWidth, rules, chain);
+ chain = LongNameHandler
+ .forMeasureUnit(macros.loc, macros.unit, macros.perUnit, unitWidth, rules, chain);
} else if (isCurrency && unitWidth == UnitWidth.FULL_NAME) {
if (rules == null) {
// Lazily create PluralRules
// Lazily create PluralRules
rules = PluralRules.forLocale(macros.loc);
}
- CompactType compactType = (macros.unit instanceof Currency && macros.unitWidth != UnitWidth.FULL_NAME)
- ? CompactType.CURRENCY
- : CompactType.DECIMAL;
- chain = ((CompactNotation) macros.notation).withLocaleData(macros.loc, nsName, compactType, rules,
- safe ? patternMod : null, chain);
+ CompactType compactType = (macros.unit instanceof Currency
+ && macros.unitWidth != UnitWidth.FULL_NAME) ? CompactType.CURRENCY
+ : CompactType.DECIMAL;
+ chain = ((CompactNotation) macros.notation).withLocaleData(macros.loc,
+ nsName,
+ compactType,
+ rules,
+ safe ? patternMod : null,
+ chain);
}
return chain;
* @param string
* The output string. Will be mutated.
*/
- private static void microsToString(MicroProps micros, DecimalQuantity quantity, NumberStringBuilder string) {
+ private static void microsToString(
+ MicroProps micros,
+ DecimalQuantity quantity,
+ NumberStringBuilder string) {
micros.rounding.apply(quantity);
if (micros.integerWidth.maxInt == -1) {
quantity.setIntegerLength(micros.integerWidth.minInt, Integer.MAX_VALUE);
}
}
- private static int writeNumber(MicroProps micros, DecimalQuantity quantity, NumberStringBuilder string) {
+ private static int writeNumber(
+ MicroProps micros,
+ DecimalQuantity quantity,
+ NumberStringBuilder string) {
int length = 0;
if (quantity.isInfinite()) {
length += string.insert(length, micros.symbols.getInfinity(), NumberFormat.Field.INTEGER);
length += writeIntegerDigits(micros, quantity, string);
// Add the decimal point
- if (quantity.getLowerDisplayMagnitude() < 0 || micros.decimal == DecimalSeparatorDisplay.ALWAYS) {
- length += string.insert(length, micros.useCurrency ? micros.symbols.getMonetaryDecimalSeparatorString()
- : micros.symbols.getDecimalSeparatorString(), NumberFormat.Field.DECIMAL_SEPARATOR);
+ if (quantity.getLowerDisplayMagnitude() < 0
+ || micros.decimal == DecimalSeparatorDisplay.ALWAYS) {
+ length += string.insert(length,
+ micros.useCurrency ? micros.symbols.getMonetaryDecimalSeparatorString()
+ : micros.symbols.getDecimalSeparatorString(),
+ NumberFormat.Field.DECIMAL_SEPARATOR);
}
// Add the fraction digits
return length;
}
- private static int writeIntegerDigits(MicroProps micros, DecimalQuantity quantity, NumberStringBuilder string) {
+ private static int writeIntegerDigits(
+ MicroProps micros,
+ DecimalQuantity quantity,
+ NumberStringBuilder string) {
int length = 0;
int integerCount = quantity.getUpperDisplayMagnitude() + 1;
for (int i = 0; i < integerCount; i++) {
// Add grouping separator
if (micros.grouping.groupAtPosition(i, quantity)) {
- length += string.insert(0, micros.useCurrency ? micros.symbols.getMonetaryGroupingSeparatorString()
- : micros.symbols.getGroupingSeparatorString(), NumberFormat.Field.GROUPING_SEPARATOR);
+ length += string.insert(0,
+ micros.useCurrency ? micros.symbols.getMonetaryGroupingSeparatorString()
+ : micros.symbols.getGroupingSeparatorString(),
+ NumberFormat.Field.GROUPING_SEPARATOR);
}
// Get and append the next digit value
byte nextDigit = quantity.getDigit(i);
if (micros.symbols.getCodePointZero() != -1) {
- length += string.insertCodePoint(0, micros.symbols.getCodePointZero() + nextDigit,
+ length += string.insertCodePoint(0,
+ micros.symbols.getCodePointZero() + nextDigit,
NumberFormat.Field.INTEGER);
} else {
- length += string.insert(0, micros.symbols.getDigitStringsLocal()[nextDigit],
+ length += string.insert(0,
+ micros.symbols.getDigitStringsLocal()[nextDigit],
NumberFormat.Field.INTEGER);
}
}
return length;
}
- private static int writeFractionDigits(MicroProps micros, DecimalQuantity quantity, NumberStringBuilder string) {
+ private static int writeFractionDigits(
+ MicroProps micros,
+ DecimalQuantity quantity,
+ NumberStringBuilder string) {
int length = 0;
int fractionCount = -quantity.getLowerDisplayMagnitude();
for (int i = 0; i < fractionCount; i++) {
length += string.appendCodePoint(micros.symbols.getCodePointZero() + nextDigit,
NumberFormat.Field.FRACTION);
} else {
- length += string.append(micros.symbols.getDigitStringsLocal()[nextDigit], NumberFormat.Field.FRACTION);
+ length += string.append(micros.symbols.getDigitStringsLocal()[nextDigit],
+ NumberFormat.Field.FRACTION);
}
}
return length;
import com.ibm.icu.util.ULocale;
/**
- * An abstract base class for specifying settings related to number formatting. This class is implemented by
- * {@link UnlocalizedNumberFormatter} and {@link LocalizedNumberFormatter}. This class is not intended for public
- * subclassing.
+ * An abstract base class for specifying settings related to number formatting. This class is implemented
+ * by {@link UnlocalizedNumberFormatter} and {@link LocalizedNumberFormatter}. This class is not intended
+ * for public subclassing.
*
* @draft ICU 60
* @provisional This API might change or be removed in a future release.
* </ul>
*
* <p>
- * All notation styles will be properly localized with locale data, and all notation styles are compatible with
- * units, rounding strategies, and other number formatter settings.
+ * All notation styles will be properly localized with locale data, and all notation styles are
+ * compatible with units, rounding strategies, and other number formatter settings.
*
* <p>
* Pass this method the return value of a {@link Notation} factory method. For example:
*
* <p>
* <strong>Note:</strong> The unit can also be specified by passing a {@link Measure} to
- * {@link LocalizedNumberFormatter#format(Measure)}. Units specified via the format method take precedence over
- * units specified here. This setter is designed for situations when the unit is constant for the duration of the
- * number formatting process.
+ * {@link LocalizedNumberFormatter#format(Measure)}. Units specified via the format method take
+ * precedence over units specified here. This setter is designed for situations when the unit is
+ * constant for the duration of the number formatting process.
*
* <p>
- * All units will be properly localized with locale data, and all units are compatible with notation styles,
- * rounding strategies, and other number formatter settings.
+ * All units will be properly localized with locale data, and all units are compatible with notation
+ * styles, rounding strategies, and other number formatter settings.
*
* <p>
* Pass this method any instance of {@link MeasureUnit}. For units of measure:
}
/**
- * Sets a unit to be used in the denominator. For example, to format "3 m/s", pass METER to the unit and SECOND to
- * the perUnit.
+ * Sets a unit to be used in the denominator. For example, to format "3 m/s", pass METER to the unit
+ * and SECOND to the perUnit.
*
* <p>
* Pass this method any instance of {@link MeasureUnit}. For example:
*
* <p>
* In most cases, the default rounding strategy is to round to 6 fraction places; i.e.,
- * <code>Rounder.maxFraction(6)</code>. The exceptions are if compact notation is being used, then the compact
- * notation rounding strategy is used (see {@link Notation#compactShort} for details), or if the unit is a currency,
- * then standard currency rounding is used, which varies from currency to currency (see {@link Rounder#currency} for
- * details).
+ * <code>Rounder.maxFraction(6)</code>. The exceptions are if compact notation is being used, then
+ * the compact notation rounding strategy is used (see {@link Notation#compactShort} for details), or
+ * if the unit is a currency, then standard currency rounding is used, which varies from currency to
+ * currency (see {@link Rounder#currency} for details).
*
* @param rounder
* The rounding strategy to use.
}
/**
- * Specifies the symbols (decimal separator, grouping separator, percent sign, numerals, etc.) to use when rendering
- * numbers.
+ * Specifies the symbols (decimal separator, grouping separator, percent sign, numerals, etc.) to use
+ * when rendering numbers.
*
* <ul>
* <li><em>en_US</em> symbols: "12,345.67"
* </pre>
*
* <p>
- * <strong>Note:</strong> DecimalFormatSymbols automatically chooses the best numbering system based on the locale.
- * In the examples above, the first three are using the Latin numbering system, and the fourth is using the Myanmar
- * numbering system.
+ * <strong>Note:</strong> DecimalFormatSymbols automatically chooses the best numbering system based
+ * on the locale. In the examples above, the first three are using the Latin numbering system, and
+ * the fourth is using the Myanmar numbering system.
*
* <p>
- * <strong>Note:</strong> The instance of DecimalFormatSymbols will be copied: changes made to the symbols object
- * after passing it into the fluent chain will not be seen.
+ * <strong>Note:</strong> The instance of DecimalFormatSymbols will be copied: changes made to the
+ * symbols object after passing it into the fluent chain will not be seen.
*
* <p>
- * <strong>Note:</strong> Calling this method will override the NumberingSystem previously specified in
- * {@link #symbols(NumberingSystem)}.
+ * <strong>Note:</strong> Calling this method will override the NumberingSystem previously specified
+ * in {@link #symbols(NumberingSystem)}.
*
* <p>
* The default is to choose the symbols based on the locale specified in the fluent chain.
* </ul>
*
* <p>
- * Pass this method an instance of {@link NumberingSystem}. For example, to force the locale to always use the Latin
- * alphabet numbering system (ASCII digits):
+ * Pass this method an instance of {@link NumberingSystem}. For example, to force the locale to
+ * always use the Latin alphabet numbering system (ASCII digits):
*
* <pre>
* NumberFormatter.with().symbols(NumberingSystem.LATIN)
* </pre>
*
* <p>
- * <strong>Note:</strong> Calling this method will override the DecimalFormatSymbols previously specified in
- * {@link #symbols(DecimalFormatSymbols)}.
+ * <strong>Note:</strong> Calling this method will override the DecimalFormatSymbols previously
+ * specified in {@link #symbols(DecimalFormatSymbols)}.
*
* <p>
* The default is to choose the best numbering system for the locale.
}
/**
- * Sets the decimal separator display strategy. This affects integer numbers with no fraction part. Most common
- * values:
+ * Sets the decimal separator display strategy. This affects integer numbers with no fraction part.
+ * Most common values:
*
* <ul>
* <li>Auto: "1"
}
/**
- * Internal fluent setter to support a custom regulation threshold. A threshold of 1 causes the data structures to
- * be built right away. A threshold of 0 prevents the data structures from being built.
+ * Internal fluent setter to support a custom regulation threshold. A threshold of 1 causes the data
+ * structures to be built right away. A threshold of 0 prevents the data structures from being built.
*
* @internal
* @deprecated ICU 60 This API is ICU internal only.
/**
* <p>
- * This class, as well as NumberFormatterImpl, could go into the impl package, but they depend on too many
- * package-private members of the public APIs.
+ * This class, as well as NumberFormatterImpl, could go into the impl package, but they depend on too
+ * many package-private members of the public APIs.
*/
final class NumberPropertyMapper {
/** Convenience method to create a NumberFormatter directly from Properties. */
- public static UnlocalizedNumberFormatter create(DecimalFormatProperties properties, DecimalFormatSymbols symbols) {
+ public static UnlocalizedNumberFormatter create(
+ DecimalFormatProperties properties,
+ DecimalFormatSymbols symbols) {
MacroProps macros = oldToNew(properties, symbols, null);
return NumberFormatter.with().macros(macros);
}
/**
- * Convenience method to create a NumberFormatter directly from a pattern string. Something like this could become
- * public API if there is demand.
+ * Convenience method to create a NumberFormatter directly from a pattern string. Something like this
+ * could become public API if there is demand.
*/
public static UnlocalizedNumberFormatter create(String pattern, DecimalFormatSymbols symbols) {
DecimalFormatProperties properties = PatternStringParser.parseToProperties(pattern);
}
/**
- * Creates a new {@link MacroProps} object based on the content of a {@link DecimalFormatProperties} object. In
- * other words, maps Properties to MacroProps. This function is used by the JDK-compatibility API to call into the
- * ICU 60 fluent number formatting pipeline.
+ * Creates a new {@link MacroProps} object based on the content of a {@link DecimalFormatProperties}
+ * object. In other words, maps Properties to MacroProps. This function is used by the
+ * JDK-compatibility API to call into the ICU 60 fluent number formatting pipeline.
*
* @param properties
* The property bag to be mapped.
* A property bag in which to store validated properties.
* @return A new MacroProps containing all of the information in the Properties.
*/
- public static MacroProps oldToNew(DecimalFormatProperties properties, DecimalFormatSymbols symbols,
+ public static MacroProps oldToNew(
+ DecimalFormatProperties properties,
+ DecimalFormatSymbols symbols,
DecimalFormatProperties exportedProperties) {
MacroProps macros = new MacroProps();
ULocale locale = symbols.getULocale();
// UNITS //
///////////
- boolean useCurrency = ((properties.getCurrency() != null) || properties.getCurrencyPluralInfo() != null
- || properties.getCurrencyUsage() != null || affixProvider.hasCurrencySign());
+ boolean useCurrency = ((properties.getCurrency() != null)
+ || properties.getCurrencyPluralInfo() != null
+ || properties.getCurrencyUsage() != null
+ || affixProvider.hasCurrencySign());
Currency currency = CustomSymbolCurrency.resolve(properties.getCurrency(), locale, symbols);
CurrencyUsage currencyUsage = properties.getCurrencyUsage();
boolean explicitCurrencyUsage = currencyUsage != null;
MathContext mathContext = RoundingUtils.getMathContextOrUnlimited(properties);
boolean explicitMinMaxFrac = minFrac != -1 || maxFrac != -1;
boolean explicitMinMaxSig = minSig != -1 || maxSig != -1;
- // Resolve min/max frac for currencies, required for the validation logic and for when minFrac or maxFrac was
+ // Resolve min/max frac for currencies, required for the validation logic and for when minFrac or
+ // maxFrac was
// set (but not both) on a currency instance.
// NOTE: Increments are handled in "Rounder.constructCurrency()".
if (useCurrency) {
minFrac = minFrac < 0 ? 0 : minFrac;
maxFrac = maxFrac < 0 ? Integer.MAX_VALUE : maxFrac < minFrac ? minFrac : maxFrac;
minInt = minInt <= 0 ? 1 : minInt > RoundingUtils.MAX_INT_FRAC_SIG ? 1 : minInt;
- maxInt = maxInt < 0 ? -1 : maxInt < minInt ? minInt : maxInt > RoundingUtils.MAX_INT_FRAC_SIG ? -1 : maxInt;
+ maxInt = maxInt < 0 ? -1
+ : maxInt < minInt ? minInt : maxInt > RoundingUtils.MAX_INT_FRAC_SIG ? -1 : maxInt;
}
Rounder rounding = null;
if (explicitCurrencyUsage) {
} else if (roundingIncrement != null) {
rounding = Rounder.constructIncrement(roundingIncrement);
} else if (explicitMinMaxSig) {
- minSig = minSig < 1 ? 1 : minSig > RoundingUtils.MAX_INT_FRAC_SIG ? RoundingUtils.MAX_INT_FRAC_SIG : minSig;
+ minSig = minSig < 1 ? 1
+ : minSig > RoundingUtils.MAX_INT_FRAC_SIG ? RoundingUtils.MAX_INT_FRAC_SIG : minSig;
maxSig = maxSig < 0 ? RoundingUtils.MAX_INT_FRAC_SIG
: maxSig < minSig ? minSig
- : maxSig > RoundingUtils.MAX_INT_FRAC_SIG ? RoundingUtils.MAX_INT_FRAC_SIG : maxSig;
+ : maxSig > RoundingUtils.MAX_INT_FRAC_SIG ? RoundingUtils.MAX_INT_FRAC_SIG
+ : maxSig;
rounding = Rounder.constructSignificant(minSig, maxSig);
} else if (explicitMinMaxFrac) {
rounding = Rounder.constructFraction(minFrac, maxFrac);
/////////////
if (properties.getFormatWidth() != -1) {
- macros.padder = new Padder(properties.getPadString(), properties.getFormatWidth(),
+ macros.padder = new Padder(properties.getPadString(),
+ properties.getFormatWidth(),
properties.getPadPosition());
}
// Scientific notation also involves overriding the rounding mode.
// TODO: Overriding here is a bit of a hack. Should this logic go earlier?
if (macros.rounder instanceof FractionRounder) {
- // For the purposes of rounding, get the original min/max int/frac, since the local variables
+ // For the purposes of rounding, get the original min/max int/frac, since the local
+ // variables
// have been manipulated for display purposes.
int minInt_ = properties.getMinimumIntegerDigits();
int minFrac_ = properties.getMinimumFractionDigits();
private final String negSuffix;
public PropertiesAffixPatternProvider(DecimalFormatProperties properties) {
- // There are two ways to set affixes in DecimalFormat: via the pattern string (applyPattern), and via the
- // explicit setters (setPositivePrefix and friends). The way to resolve the settings is as follows:
+ // There are two ways to set affixes in DecimalFormat: via the pattern string (applyPattern),
+ // and via the
+ // explicit setters (setPositivePrefix and friends). The way to resolve the settings is as
+ // follows:
//
// 1) If the explicit setting is present for the field, use it.
// 2) Otherwise, follows UTS 35 rules based on the pattern string.
//
- // Importantly, the explicit setters affect only the one field they override. If you set the positive
- // prefix, that should not affect the negative prefix. Since it is impossible for the user of this class
- // to know whether the origin for a string was the override or the pattern, we have to say that we always
+ // Importantly, the explicit setters affect only the one field they override. If you set the
+ // positive
+ // prefix, that should not affect the negative prefix. Since it is impossible for the user of
+ // this class
+ // to know whether the origin for a string was the override or the pattern, we have to say
+ // that we always
// have a negative subpattern and perform all resolution logic here.
// Convenience: Extract the properties into local variables.
@Override
public boolean hasCurrencySign() {
- return AffixUtils.hasCurrencySymbols(posPrefix) || AffixUtils.hasCurrencySymbols(posSuffix)
- || AffixUtils.hasCurrencySymbols(negPrefix) || AffixUtils.hasCurrencySymbols(negSuffix);
+ return AffixUtils.hasCurrencySymbols(posPrefix)
+ || AffixUtils.hasCurrencySymbols(posSuffix)
+ || AffixUtils.hasCurrencySymbols(negPrefix)
+ || AffixUtils.hasCurrencySymbols(negSuffix);
}
@Override
public boolean containsSymbolType(int type) {
- return AffixUtils.containsType(posPrefix, type) || AffixUtils.containsType(posSuffix, type)
- || AffixUtils.containsType(negPrefix, type) || AffixUtils.containsType(negSuffix, type);
+ return AffixUtils.containsType(posPrefix, type)
+ || AffixUtils.containsType(posSuffix, type)
+ || AffixUtils.containsType(negPrefix, type)
+ || AffixUtils.containsType(negSuffix, type);
}
}
* Show all available digits to full precision.
*
* <p>
- * <strong>NOTE:</strong> When formatting a <em>double</em>, this method, along with {@link #minFraction} and
- * {@link #minDigits}, will trigger complex algorithm similar to <em>Dragon4</em> to determine the low-order digits
- * and the number of digits to display based on the value of the double. If the number of fraction places or
- * significant digits can be bounded, consider using {@link #maxFraction} or {@link #maxDigits} instead to maximize
- * performance. For more information, read the following blog post.
+ * <strong>NOTE:</strong> When formatting a <em>double</em>, this method, along with
+ * {@link #minFraction} and {@link #minDigits}, will trigger complex algorithm similar to
+ * <em>Dragon4</em> to determine the low-order digits and the number of digits to display based on
+ * the value of the double. If the number of fraction places or significant digits can be bounded,
+ * consider using {@link #maxFraction} or {@link #maxDigits} instead to maximize performance. For
+ * more information, read the following blog post.
*
* <p>
* http://www.serpentine.com/blog/2011/06/29/here-be-dragons-advances-in-problems-you-didnt-even-know-you-had/
}
/**
- * Show numbers rounded if necessary to a certain number of fraction places (numerals after the decimal separator).
- * Additionally, pad with zeros to ensure that this number of places are always shown.
+ * Show numbers rounded if necessary to a certain number of fraction places (numerals after the
+ * decimal separator). Additionally, pad with zeros to ensure that this number of places are always
+ * shown.
*
* <p>
* Example output with minMaxFractionPlaces = 3:
* This method is equivalent to {@link #minMaxFraction} with both arguments equal.
*
* @param minMaxFractionPlaces
- * The minimum and maximum number of numerals to display after the decimal separator (rounding if too
- * long or padding with zeros if too short).
+ * The minimum and maximum number of numerals to display after the decimal separator
+ * (rounding if too long or padding with zeros if too short).
* @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter.
* @draft ICU 60
* @provisional This API might change or be removed in a future release.
if (minMaxFractionPlaces >= 0 && minMaxFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG) {
return constructFraction(minMaxFractionPlaces, minMaxFractionPlaces);
} else {
- throw new IllegalArgumentException(
- "Fraction length must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG + " (inclusive)");
+ throw new IllegalArgumentException("Fraction length must be between 0 and "
+ + RoundingUtils.MAX_INT_FRAC_SIG
+ + " (inclusive)");
}
}
/**
- * Always show at least a certain number of fraction places after the decimal separator, padding with zeros if
- * necessary. Do not perform rounding (display numbers to their full precision).
+ * Always show at least a certain number of fraction places after the decimal separator, padding with
+ * zeros if necessary. Do not perform rounding (display numbers to their full precision).
*
* <p>
- * <strong>NOTE:</strong> If you are formatting <em>doubles</em>, see the performance note in {@link #unlimited}.
+ * <strong>NOTE:</strong> If you are formatting <em>doubles</em>, see the performance note in
+ * {@link #unlimited}.
*
* @param minFractionPlaces
- * The minimum number of numerals to display after the decimal separator (padding with zeros if
- * necessary).
+ * The minimum number of numerals to display after the decimal separator (padding with
+ * zeros if necessary).
* @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter.
* @draft ICU 60
* @provisional This API might change or be removed in a future release.
if (minFractionPlaces >= 0 && minFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG) {
return constructFraction(minFractionPlaces, -1);
} else {
- throw new IllegalArgumentException(
- "Fraction length must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG + " (inclusive)");
+ throw new IllegalArgumentException("Fraction length must be between 0 and "
+ + RoundingUtils.MAX_INT_FRAC_SIG
+ + " (inclusive)");
}
}
/**
- * Show numbers rounded if necessary to a certain number of fraction places (numerals after the decimal separator).
- * Unlike the other fraction rounding strategies, this strategy does <em>not</em> pad zeros to the end of the
- * number.
+ * Show numbers rounded if necessary to a certain number of fraction places (numerals after the
+ * decimal separator). Unlike the other fraction rounding strategies, this strategy does <em>not</em>
+ * pad zeros to the end of the number.
*
* @param maxFractionPlaces
- * The maximum number of numerals to display after the decimal mark (rounding if necessary).
+ * The maximum number of numerals to display after the decimal mark (rounding if
+ * necessary).
* @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter.
* @draft ICU 60
* @provisional This API might change or be removed in a future release.
if (maxFractionPlaces >= 0 && maxFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG) {
return constructFraction(0, maxFractionPlaces);
} else {
- throw new IllegalArgumentException(
- "Fraction length must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG + " (inclusive)");
+ throw new IllegalArgumentException("Fraction length must be between 0 and "
+ + RoundingUtils.MAX_INT_FRAC_SIG
+ + " (inclusive)");
}
}
/**
- * Show numbers rounded if necessary to a certain number of fraction places (numerals after the decimal separator);
- * in addition, always show at least a certain number of places after the decimal separator, padding with zeros if
- * necessary.
+ * Show numbers rounded if necessary to a certain number of fraction places (numerals after the
+ * decimal separator); in addition, always show at least a certain number of places after the decimal
+ * separator, padding with zeros if necessary.
*
* @param minFractionPlaces
- * The minimum number of numerals to display after the decimal separator (padding with zeros if
- * necessary).
+ * The minimum number of numerals to display after the decimal separator (padding with
+ * zeros if necessary).
* @param maxFractionPlaces
- * The maximum number of numerals to display after the decimal separator (rounding if necessary).
+ * The maximum number of numerals to display after the decimal separator (rounding if
+ * necessary).
* @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter.
* @draft ICU 60
* @provisional This API might change or be removed in a future release.
* @see NumberFormatter
*/
public static FractionRounder minMaxFraction(int minFractionPlaces, int maxFractionPlaces) {
- if (minFractionPlaces >= 0 && maxFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG
+ if (minFractionPlaces >= 0
+ && maxFractionPlaces <= RoundingUtils.MAX_INT_FRAC_SIG
&& minFractionPlaces <= maxFractionPlaces) {
return constructFraction(minFractionPlaces, maxFractionPlaces);
} else {
- throw new IllegalArgumentException(
- "Fraction length must be between 0 and " + RoundingUtils.MAX_INT_FRAC_SIG + " (inclusive)");
+ throw new IllegalArgumentException("Fraction length must be between 0 and "
+ + RoundingUtils.MAX_INT_FRAC_SIG
+ + " (inclusive)");
}
}
/**
- * Show numbers rounded if necessary to a certain number of significant digits or significant figures. Additionally,
- * pad with zeros to ensure that this number of significant digits/figures are always shown.
+ * Show numbers rounded if necessary to a certain number of significant digits or significant
+ * figures. Additionally, pad with zeros to ensure that this number of significant digits/figures are
+ * always shown.
*
* <p>
* This method is equivalent to {@link #minMaxDigits} with both arguments equal.
*
* @param minMaxSignificantDigits
- * The minimum and maximum number of significant digits to display (rounding if too long or padding with
- * zeros if too short).
+ * The minimum and maximum number of significant digits to display (rounding if too long
+ * or padding with zeros if too short).
* @return A Rounder for chaining or passing to the NumberFormatter rounding() setter.
* @draft ICU 60
* @provisional This API might change or be removed in a future release.
if (minMaxSignificantDigits >= 1 && minMaxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
return constructSignificant(minMaxSignificantDigits, minMaxSignificantDigits);
} else {
- throw new IllegalArgumentException(
- "Significant digits must be between 1 and " + RoundingUtils.MAX_INT_FRAC_SIG + " (inclusive)");
+ throw new IllegalArgumentException("Significant digits must be between 1 and "
+ + RoundingUtils.MAX_INT_FRAC_SIG
+ + " (inclusive)");
}
}
/**
- * Always show at least a certain number of significant digits/figures, padding with zeros if necessary. Do not
- * perform rounding (display numbers to their full precision).
+ * Always show at least a certain number of significant digits/figures, padding with zeros if
+ * necessary. Do not perform rounding (display numbers to their full precision).
*
* <p>
- * <strong>NOTE:</strong> If you are formatting <em>doubles</em>, see the performance note in {@link #unlimited}.
+ * <strong>NOTE:</strong> If you are formatting <em>doubles</em>, see the performance note in
+ * {@link #unlimited}.
*
* @param minSignificantDigits
* The minimum number of significant digits to display (padding with zeros if too short).
if (minSignificantDigits >= 1 && minSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
return constructSignificant(minSignificantDigits, -1);
} else {
- throw new IllegalArgumentException(
- "Significant digits must be between 1 and " + RoundingUtils.MAX_INT_FRAC_SIG + " (inclusive)");
+ throw new IllegalArgumentException("Significant digits must be between 1 and "
+ + RoundingUtils.MAX_INT_FRAC_SIG
+ + " (inclusive)");
}
}
if (maxSignificantDigits >= 1 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG) {
return constructSignificant(0, maxSignificantDigits);
} else {
- throw new IllegalArgumentException(
- "Significant digits must be between 1 and " + RoundingUtils.MAX_INT_FRAC_SIG + " (inclusive)");
+ throw new IllegalArgumentException("Significant digits must be between 1 and "
+ + RoundingUtils.MAX_INT_FRAC_SIG
+ + " (inclusive)");
}
}
/**
- * Show numbers rounded if necessary to a certain number of significant digits/figures; in addition, always show at
- * least a certain number of significant digits, padding with zeros if necessary.
+ * Show numbers rounded if necessary to a certain number of significant digits/figures; in addition,
+ * always show at least a certain number of significant digits, padding with zeros if necessary.
*
* @param minSignificantDigits
* The minimum number of significant digits to display (padding with zeros if necessary).
* @see NumberFormatter
*/
public static Rounder minMaxDigits(int minSignificantDigits, int maxSignificantDigits) {
- if (minSignificantDigits >= 1 && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG
+ if (minSignificantDigits >= 1
+ && maxSignificantDigits <= RoundingUtils.MAX_INT_FRAC_SIG
&& minSignificantDigits <= maxSignificantDigits) {
return constructSignificant(minSignificantDigits, maxSignificantDigits);
} else {
- throw new IllegalArgumentException(
- "Significant digits must be between 1 and " + RoundingUtils.MAX_INT_FRAC_SIG + " (inclusive)");
+ throw new IllegalArgumentException("Significant digits must be between 1 and "
+ + RoundingUtils.MAX_INT_FRAC_SIG
+ + " (inclusive)");
}
}
/**
- * Show numbers rounded if necessary to the closest multiple of a certain rounding increment. For example, if the
- * rounding increment is 0.5, then round 1.2 to 1 and round 1.3 to 1.5.
+ * Show numbers rounded if necessary to the closest multiple of a certain rounding increment. For
+ * example, if the rounding increment is 0.5, then round 1.2 to 1 and round 1.3 to 1.5.
*
* <p>
- * In order to ensure that numbers are padded to the appropriate number of fraction places, set the scale on the
- * rounding increment BigDecimal. For example, to round to the nearest 0.5 and always display 2 numerals after the
- * decimal separator (to display 1.2 as "1.00" and 1.3 as "1.50"), you can run:
+ * In order to ensure that numbers are padded to the appropriate number of fraction places, set the
+ * scale on the rounding increment BigDecimal. For example, to round to the nearest 0.5 and always
+ * display 2 numerals after the decimal separator (to display 1.2 as "1.00" and 1.3 as "1.50"), you
+ * can run:
*
* <pre>
* Rounder.increment(new BigDecimal("0.50"))
}
/**
- * Show numbers rounded and padded according to the rules for the currency unit. The most common rounding settings
- * for currencies include <code>Rounder.fixedFraction(2)</code>, <code>Rounder.integer()</code>, and
- * <code>Rounder.increment(0.05)</code> for cash transactions ("nickel rounding").
+ * Show numbers rounded and padded according to the rules for the currency unit. The most common
+ * rounding settings for currencies include <code>Rounder.fixedFraction(2)</code>,
+ * <code>Rounder.integer()</code>, and <code>Rounder.increment(0.05)</code> for cash transactions
+ * ("nickel rounding").
*
* <p>
* The exact rounding details will be resolved at runtime based on the currency unit specified in the
- * NumberFormatter chain. To round according to the rules for one currency while displaying the symbol for another
- * currency, the withCurrency() method can be called on the return value of this method.
+ * NumberFormatter chain. To round according to the rules for one currency while displaying the
+ * symbol for another currency, the withCurrency() method can be called on the return value of this
+ * method.
*
* @param currencyUsage
- * Either STANDARD (for digital transactions) or CASH (for transactions where the rounding increment may
- * be limited by the available denominations of cash or coins).
+ * Either STANDARD (for digital transactions) or CASH (for transactions where the rounding
+ * increment may be limited by the available denominations of cash or coins).
* @return A CurrencyRounder for chaining or passing to the NumberFormatter rounding() setter.
* @draft ICU 60
* @provisional This API might change or be removed in a future release.
}
/**
- * Sets the {@link java.math.RoundingMode} to use when picking the direction to round (up or down). Common values
- * include HALF_EVEN, HALF_UP, and FLOOR. The default is HALF_EVEN.
+ * Sets the {@link java.math.RoundingMode} to use when picking the direction to round (up or down).
+ * Common values include HALF_EVEN, HALF_UP, and FLOOR. The default is HALF_EVEN.
*
* @param roundingMode
* The RoundingMode to use.
}
/**
- * Returns a valid working Rounder. If the Rounder is a CurrencyRounder, applies the given currency. Otherwise,
- * simply passes through the argument.
+ * Returns a valid working Rounder. If the Rounder is a CurrencyRounder, applies the given currency.
+ * Otherwise, simply passes through the argument.
*
* @param currency
* A currency object to use in case the input object needs it.
}
/**
- * Rounding endpoint used by Engineering and Compact notation. Chooses the most appropriate multiplier (magnitude
- * adjustment), applies the adjustment, rounds, and returns the chosen multiplier.
+ * Rounding endpoint used by Engineering and Compact notation. Chooses the most appropriate
+ * multiplier (magnitude adjustment), applies the adjustment, rounds, and returns the chosen
+ * multiplier.
*
* <p>
- * In most cases, this is simple. However, when rounding the number causes it to cross a multiplier boundary, we
- * need to re-do the rounding. For example, to display 999,999 in Engineering notation with 2 sigfigs, first you
- * guess the multiplier to be -3. However, then you end up getting 1000E3, which is not the correct output. You then
- * change your multiplier to be -6, and you get 1.0E6, which is correct.
+ * In most cases, this is simple. However, when rounding the number causes it to cross a multiplier
+ * boundary, we need to re-do the rounding. For example, to display 999,999 in Engineering notation
+ * with 2 sigfigs, first you guess the multiplier to be -3. However, then you end up getting 1000E3,
+ * which is not the correct output. You then change your multiplier to be -6, and you get 1.0E6,
+ * which is correct.
*
- * @param input The quantity to process.
- * @param producer Function to call to return a multiplier based on a magnitude.
+ * @param input
+ * The quantity to process.
+ * @param producer
+ * Function to call to return a multiplier based on a magnitude.
* @return The number of orders of magnitude the input was adjusted by this method.
*/
int chooseMultiplierAndApply(DecimalQuantity input, MultiplierProducer producer) {
@Override
public void apply(DecimalQuantity value) {
value.roundToMagnitude(getRoundingMagnitudeFraction(maxFrac), mathContext);
- value.setFractionLength(Math.max(0, -getDisplayMagnitudeFraction(minFrac)), Integer.MAX_VALUE);
+ value.setFractionLength(Math.max(0, -getDisplayMagnitudeFraction(minFrac)),
+ Integer.MAX_VALUE);
}
}
@Override
public void apply(DecimalQuantity value) {
value.roundToMagnitude(getRoundingMagnitudeSignificant(value, maxSig), mathContext);
- value.setFractionLength(Math.max(0, -getDisplayMagnitudeSignificant(value, minSig)), Integer.MAX_VALUE);
+ value.setFractionLength(Math.max(0, -getDisplayMagnitudeSignificant(value, minSig)),
+ Integer.MAX_VALUE);
}
- /** Version of {@link #apply} that obeys minInt constraints. Used for scientific notation compatibility mode. */
+ /**
+ * Version of {@link #apply} that obeys minInt constraints. Used for scientific notation
+ * compatibility mode.
+ */
public void apply(DecimalQuantity quantity, int minInt) {
assert quantity.isZero();
quantity.setFractionLength(minSig - minInt, Integer.MAX_VALUE);
import com.ibm.icu.text.NumberFormat;
/**
- * A class that defines the scientific notation style to be used when formatting numbers in NumberFormatter.
+ * A class that defines the scientific notation style to be used when formatting numbers in
+ * NumberFormatter.
*
* <p>
* To create a ScientificNotation, use one of the factory methods in {@link Notation}.
int minExponentDigits;
SignDisplay exponentSignDisplay;
- /* package-private */ ScientificNotation(int engineeringInterval, boolean requireMinInt, int minExponentDigits,
+ /* package-private */ ScientificNotation(
+ int engineeringInterval,
+ boolean requireMinInt,
+ int minExponentDigits,
SignDisplay exponentSignDisplay) {
this.engineeringInterval = engineeringInterval;
this.requireMinInt = requireMinInt;
}
/**
- * Sets the minimum number of digits to show in the exponent of scientific notation, padding with zeros if
- * necessary. Useful for fixed-width display.
+ * Sets the minimum number of digits to show in the exponent of scientific notation, padding with
+ * zeros if necessary. Useful for fixed-width display.
*
* <p>
- * For example, with minExponentDigits=2, the number 123 will be printed as "1.23E02" in <em>en-US</em> instead of
- * the default "1.23E2".
+ * For example, with minExponentDigits=2, the number 123 will be printed as "1.23E02" in
+ * <em>en-US</em> instead of the default "1.23E2".
*
* @param minExponentDigits
* The minimum number of digits to show in the exponent.
other.minExponentDigits = minExponentDigits;
return other;
} else {
- throw new IllegalArgumentException(
- "Integer digits must be between 1 and " + RoundingUtils.MAX_INT_FRAC_SIG + " (inclusive)");
+ throw new IllegalArgumentException("Integer digits must be between 1 and "
+ + RoundingUtils.MAX_INT_FRAC_SIG
+ + " (inclusive)");
}
}
/**
- * Sets whether to show the sign on positive and negative exponents in scientific notation. The default is AUTO,
- * showing the minus sign but not the plus sign.
+ * Sets whether to show the sign on positive and negative exponents in scientific notation. The
+ * default is AUTO, showing the minus sign but not the plus sign.
*
* <p>
- * For example, with exponentSignDisplay=ALWAYS, the number 123 will be printed as "1.23E+2" in <em>en-US</em>
- * instead of the default "1.23E2".
+ * For example, with exponentSignDisplay=ALWAYS, the number 123 will be printed as "1.23E+2" in
+ * <em>en-US</em> instead of the default "1.23E2".
*
* @param exponentSignDisplay
* The strategy for displaying the sign in the exponent.
}
}
- /* package-private */ MicroPropsGenerator withLocaleData(DecimalFormatSymbols symbols, boolean build,
+ /* package-private */ MicroPropsGenerator withLocaleData(
+ DecimalFormatSymbols symbols,
+ boolean build,
MicroPropsGenerator parent) {
return new ScientificHandler(this, symbols, build, parent);
}
- // NOTE: The object lifecycle of ScientificModifier and ScientificHandler differ greatly in Java and C++.
+ // NOTE: The object lifecycle of ScientificModifier and ScientificHandler differ greatly in Java and
+ // C++.
//
// During formatting, we need to provide an object with state (the exponent) as the inner modifier.
//
// In Java, where the priority is put on reducing object creations, the unsafe code path re-uses the
- // ScientificHandler as a ScientificModifier, and the safe code path pre-computes 25 ScientificModifier
+ // ScientificHandler as a ScientificModifier, and the safe code path pre-computes 25
+ // ScientificModifier
// instances. This scheme reduces the number of object creations by 1 in both safe and unsafe.
//
- // In C++, MicroProps provides a pre-allocated ScientificModifier, and ScientificHandler simply populates
- // the state (the exponent) into that ScientificModifier. There is no difference between safe and unsafe.
+ // In C++, MicroProps provides a pre-allocated ScientificModifier, and ScientificHandler simply
+ // populates
+ // the state (the exponent) into that ScientificModifier. There is no difference between safe and
+ // unsafe.
private static class ScientificHandler implements MicroPropsGenerator, MultiplierProducer, Modifier {
final MicroPropsGenerator parent;
/* unsafe */ int exponent;
- private ScientificHandler(ScientificNotation notation, DecimalFormatSymbols symbols, boolean safe,
+ private ScientificHandler(
+ ScientificNotation notation,
+ DecimalFormatSymbols symbols,
+ boolean safe,
MicroPropsGenerator parent) {
this.notation = notation;
this.symbols = symbols;
if (quantity.isZero()) {
if (notation.requireMinInt && micros.rounding instanceof SignificantRounderImpl) {
// Show "00.000E0" on pattern "00.000E0"
- ((SignificantRounderImpl) micros.rounding).apply(quantity, notation.engineeringInterval);
+ ((SignificantRounderImpl) micros.rounding).apply(quantity,
+ notation.engineeringInterval);
exponent = 0;
} else {
micros.rounding.apply(quantity);
* A class that defines the simple notation style to be used when formatting numbers in NumberFormatter.
*
* <p>
- * This class exposes no public functionality. To create a SimpleNotation, use one of the factory methods in
- * {@link Notation}.
+ * This class exposes no public functionality. To create a SimpleNotation, use one of the factory methods
+ * in {@link Notation}.
*
* @draft ICU 60
* @provisional This API might change or be removed in a future release.
import com.ibm.icu.util.ULocale;
/**
- * A NumberFormatter that does not yet have a locale. In order to format numbers, a locale must be specified.
+ * A NumberFormatter that does not yet have a locale. In order to format numbers, a locale must be
+ * specified.
*
* @see NumberFormatter
* @draft ICU 60
}
/**
- * Associate the given locale with the number formatter. The locale is used for picking the appropriate symbols,
- * formats, and other data for number display.
+ * Associate the given locale with the number formatter. The locale is used for picking the
+ * appropriate symbols, formats, and other data for number display.
*
* <p>
* To use the Java default locale, call Locale.getDefault():
public class AffixUtilsTest {
- private static final SymbolProvider DEFAULT_SYMBOL_PROVIDER =
- new SymbolProvider() {
- @Override
- public CharSequence getSymbol(int type) {
+ private static final SymbolProvider DEFAULT_SYMBOL_PROVIDER = new SymbolProvider() {
+ @Override
+ public CharSequence getSymbol(int type) {
// Use interesting symbols where possible. The symbols are from ar_SA but are hard-coded
// here to make the test independent of locale data changes.
switch (type) {
- case AffixUtils.TYPE_MINUS_SIGN:
+ case AffixUtils.TYPE_MINUS_SIGN:
return "−";
- case AffixUtils.TYPE_PLUS_SIGN:
+ case AffixUtils.TYPE_PLUS_SIGN:
return "\u061C+";
- case AffixUtils.TYPE_PERCENT:
+ case AffixUtils.TYPE_PERCENT:
return "٪\u061C";
- case AffixUtils.TYPE_PERMILLE:
+ case AffixUtils.TYPE_PERMILLE:
return "؉";
- case AffixUtils.TYPE_CURRENCY_SINGLE:
+ case AffixUtils.TYPE_CURRENCY_SINGLE:
return "$";
- case AffixUtils.TYPE_CURRENCY_DOUBLE:
+ case AffixUtils.TYPE_CURRENCY_DOUBLE:
return "XXX";
- case AffixUtils.TYPE_CURRENCY_TRIPLE:
+ case AffixUtils.TYPE_CURRENCY_TRIPLE:
return "long name";
- case AffixUtils.TYPE_CURRENCY_QUAD:
+ case AffixUtils.TYPE_CURRENCY_QUAD:
return "\uFFFD";
- case AffixUtils.TYPE_CURRENCY_QUINT:
+ case AffixUtils.TYPE_CURRENCY_QUINT:
return "@";
- case AffixUtils.TYPE_CURRENCY_OVERFLOW:
+ case AffixUtils.TYPE_CURRENCY_OVERFLOW:
return "\uFFFD";
- default:
+ default:
throw new AssertionError();
}
- }
- };
-
- @Test
- public void testEscape() {
- Object[][] cases = {
- {"", ""},
- {"abc", "abc"},
- {"-", "'-'"},
- {"-!", "'-'!"},
- {"−", "−"},
- {"---", "'---'"},
- {"-%-", "'-%-'"},
- {"'", "''"},
- {"-'", "'-'''"},
- {"-'-", "'-''-'"},
- {"a-'-", "a'-''-'"}
+ }
};
- StringBuilder sb = new StringBuilder();
- for (Object[] cas : cases) {
- String input = (String) cas[0];
- String expected = (String) cas[1];
- sb.setLength(0);
- AffixUtils.escape(input, sb);
- assertEquals(expected, sb.toString());
+ @Test
+ public void testEscape() {
+ Object[][] cases = {
+ { "", "" },
+ { "abc", "abc" },
+ { "-", "'-'" },
+ { "-!", "'-'!" },
+ { "−", "−" },
+ { "---", "'---'" },
+ { "-%-", "'-%-'" },
+ { "'", "''" },
+ { "-'", "'-'''" },
+ { "-'-", "'-''-'" },
+ { "a-'-", "a'-''-'" } };
+
+ StringBuilder sb = new StringBuilder();
+ for (Object[] cas : cases) {
+ String input = (String) cas[0];
+ String expected = (String) cas[1];
+ sb.setLength(0);
+ AffixUtils.escape(input, sb);
+ assertEquals(expected, sb.toString());
+ }
}
- }
-
- @Test
- public void testUnescape() {
- Object[][] cases = {
- {"", false, 0, ""},
- {"abc", false, 3, "abc"},
- {"-", false, 1, "−"},
- {"-!", false, 2, "−!"},
- {"+", false, 1, "\u061C+"},
- {"+!", false, 2, "\u061C+!"},
- {"‰", false, 1, "؉"},
- {"‰!", false, 2, "؉!"},
- {"-x", false, 2, "−x"},
- {"'-'x", false, 2, "-x"},
- {"'--''-'-x", false, 6, "--'-−x"},
- {"''", false, 1, "'"},
- {"''''", false, 2, "''"},
- {"''''''", false, 3, "'''"},
- {"''x''", false, 3, "'x'"},
- {"¤", true, 1, "$"},
- {"¤¤", true, 2, "XXX"},
- {"¤¤¤", true, 3, "long name"},
- {"¤¤¤¤", true, 4, "\uFFFD"},
- {"¤¤¤¤¤", true, 5, "@"},
- {"¤¤¤¤¤¤", true, 6, "\uFFFD"},
- {"¤¤¤a¤¤¤¤", true, 8, "long namea\uFFFD"},
- {"a¤¤¤¤b¤¤¤¤¤c", true, 12, "a\uFFFDb@c"},
- {"¤!", true, 2, "$!"},
- {"¤¤!", true, 3, "XXX!"},
- {"¤¤¤!", true, 4, "long name!"},
- {"-¤¤", true, 3, "−XXX"},
- {"¤¤-", true, 3, "XXX−"},
- {"'¤'", false, 1, "¤"},
- {"%", false, 1, "٪\u061C"},
- {"'%'", false, 1, "%"},
- {"¤'-'%", true, 3, "$-٪\u061C"},
- {"#0#@#*#;#", false, 9, "#0#@#*#;#"}
- };
-
- for (Object[] cas : cases) {
- String input = (String) cas[0];
- boolean curr = (Boolean) cas[1];
- int length = (Integer) cas[2];
- String output = (String) cas[3];
- assertEquals(
- "Currency on <" + input + ">", curr, AffixUtils.hasCurrencySymbols(input));
- assertEquals("Length on <" + input + ">", length, AffixUtils.estimateLength(input));
-
- String actual = unescapeWithDefaults(input);
- assertEquals("Output on <" + input + ">", output, actual);
-
- int ulength = AffixUtils.unescapedCodePointCount(input, DEFAULT_SYMBOL_PROVIDER);
- assertEquals("Unescaped length on <" + input + ">", output.length(), ulength);
+ @Test
+ public void testUnescape() {
+ Object[][] cases = {
+ { "", false, 0, "" },
+ { "abc", false, 3, "abc" },
+ { "-", false, 1, "−" },
+ { "-!", false, 2, "−!" },
+ { "+", false, 1, "\u061C+" },
+ { "+!", false, 2, "\u061C+!" },
+ { "‰", false, 1, "؉" },
+ { "‰!", false, 2, "؉!" },
+ { "-x", false, 2, "−x" },
+ { "'-'x", false, 2, "-x" },
+ { "'--''-'-x", false, 6, "--'-−x" },
+ { "''", false, 1, "'" },
+ { "''''", false, 2, "''" },
+ { "''''''", false, 3, "'''" },
+ { "''x''", false, 3, "'x'" },
+ { "¤", true, 1, "$" },
+ { "¤¤", true, 2, "XXX" },
+ { "¤¤¤", true, 3, "long name" },
+ { "¤¤¤¤", true, 4, "\uFFFD" },
+ { "¤¤¤¤¤", true, 5, "@" },
+ { "¤¤¤¤¤¤", true, 6, "\uFFFD" },
+ { "¤¤¤a¤¤¤¤", true, 8, "long namea\uFFFD" },
+ { "a¤¤¤¤b¤¤¤¤¤c", true, 12, "a\uFFFDb@c" },
+ { "¤!", true, 2, "$!" },
+ { "¤¤!", true, 3, "XXX!" },
+ { "¤¤¤!", true, 4, "long name!" },
+ { "-¤¤", true, 3, "−XXX" },
+ { "¤¤-", true, 3, "XXX−" },
+ { "'¤'", false, 1, "¤" },
+ { "%", false, 1, "٪\u061C" },
+ { "'%'", false, 1, "%" },
+ { "¤'-'%", true, 3, "$-٪\u061C" },
+ { "#0#@#*#;#", false, 9, "#0#@#*#;#" } };
+
+ for (Object[] cas : cases) {
+ String input = (String) cas[0];
+ boolean curr = (Boolean) cas[1];
+ int length = (Integer) cas[2];
+ String output = (String) cas[3];
+
+ assertEquals("Currency on <" + input + ">", curr, AffixUtils.hasCurrencySymbols(input));
+ assertEquals("Length on <" + input + ">", length, AffixUtils.estimateLength(input));
+
+ String actual = unescapeWithDefaults(input);
+ assertEquals("Output on <" + input + ">", output, actual);
+
+ int ulength = AffixUtils.unescapedCodePointCount(input, DEFAULT_SYMBOL_PROVIDER);
+ assertEquals("Unescaped length on <" + input + ">", output.length(), ulength);
+ }
}
- }
-
- @Test
- public void testContainsReplaceType() {
- Object[][] cases = {
- {"", false, ""},
- {"-", true, "+"},
- {"-a", true, "+a"},
- {"a-", true, "a+"},
- {"a-b", true, "a+b"},
- {"--", true, "++"},
- {"x", false, "x"}
- };
- for (Object[] cas : cases) {
- String input = (String) cas[0];
- boolean hasMinusSign = (Boolean) cas[1];
- String output = (String) cas[2];
-
- assertEquals(
- "Contains on input " + input,
- hasMinusSign,
- AffixUtils.containsType(input, AffixUtils.TYPE_MINUS_SIGN));
- assertEquals(
- "Replace on input" + input,
- output,
- AffixUtils.replaceType(input, AffixUtils.TYPE_MINUS_SIGN, '+'));
+ @Test
+ public void testContainsReplaceType() {
+ Object[][] cases = {
+ { "", false, "" },
+ { "-", true, "+" },
+ { "-a", true, "+a" },
+ { "a-", true, "a+" },
+ { "a-b", true, "a+b" },
+ { "--", true, "++" },
+ { "x", false, "x" } };
+
+ for (Object[] cas : cases) {
+ String input = (String) cas[0];
+ boolean hasMinusSign = (Boolean) cas[1];
+ String output = (String) cas[2];
+
+ assertEquals("Contains on input " + input,
+ hasMinusSign,
+ AffixUtils.containsType(input, AffixUtils.TYPE_MINUS_SIGN));
+ assertEquals("Replace on input" + input,
+ output,
+ AffixUtils.replaceType(input, AffixUtils.TYPE_MINUS_SIGN, '+'));
+ }
}
- }
-
- @Test
- public void testInvalid() {
- String[] invalidExamples = {"'", "x'", "'x", "'x''", "''x'"};
-
- for (String str : invalidExamples) {
- try {
- AffixUtils.hasCurrencySymbols(str);
- fail("No exception was thrown on an invalid string");
- } catch (IllegalArgumentException e) {
- // OK
- }
- try {
- AffixUtils.estimateLength(str);
- fail("No exception was thrown on an invalid string");
- } catch (IllegalArgumentException e) {
- // OK
- }
- try {
- unescapeWithDefaults(str);
- fail("No exception was thrown on an invalid string");
- } catch (IllegalArgumentException e) {
- // OK
- }
+
+ @Test
+ public void testInvalid() {
+ String[] invalidExamples = { "'", "x'", "'x", "'x''", "''x'" };
+
+ for (String str : invalidExamples) {
+ try {
+ AffixUtils.hasCurrencySymbols(str);
+ fail("No exception was thrown on an invalid string");
+ } catch (IllegalArgumentException e) {
+ // OK
+ }
+ try {
+ AffixUtils.estimateLength(str);
+ fail("No exception was thrown on an invalid string");
+ } catch (IllegalArgumentException e) {
+ // OK
+ }
+ try {
+ unescapeWithDefaults(str);
+ fail("No exception was thrown on an invalid string");
+ } catch (IllegalArgumentException e) {
+ // OK
+ }
+ }
}
- }
-
- @Test
- public void testUnescapeWithSymbolProvider() {
- String[][] cases = {
- {"", ""},
- {"-", "1"},
- {"'-'", "-"},
- {"- + % ‰ ¤ ¤¤ ¤¤¤ ¤¤¤¤ ¤¤¤¤¤", "1 2 3 4 5 6 7 8 9"},
- {"'¤¤¤¤¤¤'", "¤¤¤¤¤¤"},
- {"¤¤¤¤¤¤", "\uFFFD"}
- };
- SymbolProvider provider =
- new SymbolProvider() {
- @Override
- public CharSequence getSymbol(int type) {
- return Integer.toString(Math.abs(type));
- }
+ @Test
+ public void testUnescapeWithSymbolProvider() {
+ String[][] cases = {
+ { "", "" },
+ { "-", "1" },
+ { "'-'", "-" },
+ { "- + % ‰ ¤ ¤¤ ¤¤¤ ¤¤¤¤ ¤¤¤¤¤", "1 2 3 4 5 6 7 8 9" },
+ { "'¤¤¤¤¤¤'", "¤¤¤¤¤¤" },
+ { "¤¤¤¤¤¤", "\uFFFD" } };
+
+ SymbolProvider provider = new SymbolProvider() {
+ @Override
+ public CharSequence getSymbol(int type) {
+ return Integer.toString(Math.abs(type));
+ }
};
- NumberStringBuilder sb = new NumberStringBuilder();
- for (String[] cas : cases) {
- String input = cas[0];
- String expected = cas[1];
- sb.clear();
- AffixUtils.unescape(input, sb, 0, provider);
- assertEquals("With symbol provider on <" + input + ">", expected, sb.toString());
+ NumberStringBuilder sb = new NumberStringBuilder();
+ for (String[] cas : cases) {
+ String input = cas[0];
+ String expected = cas[1];
+ sb.clear();
+ AffixUtils.unescape(input, sb, 0, provider);
+ assertEquals("With symbol provider on <" + input + ">", expected, sb.toString());
+ }
+
+ // Test insertion position
+ sb.clear();
+ sb.append("abcdefg", null);
+ AffixUtils.unescape("-+%", sb, 4, provider);
+ assertEquals("Symbol provider into middle", "abcd123efg", sb.toString());
}
- // Test insertion position
- sb.clear();
- sb.append("abcdefg", null);
- AffixUtils.unescape("-+%", sb, 4, provider);
- assertEquals("Symbol provider into middle", "abcd123efg", sb.toString());
- }
-
- private static String unescapeWithDefaults(String input) {
- NumberStringBuilder nsb = new NumberStringBuilder();
- int length = AffixUtils.unescape(input, nsb, 0, DEFAULT_SYMBOL_PROVIDER);
- assertEquals("Return value of unescape", nsb.length(), length);
- return nsb.toString();
- }
+ private static String unescapeWithDefaults(String input) {
+ NumberStringBuilder nsb = new NumberStringBuilder();
+ int length = AffixUtils.unescape(input, nsb, 0, DEFAULT_SYMBOL_PROVIDER);
+ assertEquals("Return value of unescape", nsb.length(), length);
+ return nsb.toString();
+ }
}
@RunWith(JUnit4.class)
public class DecimalQuantityTest extends TestFmwk {
- @Ignore
- @Test
- public void testBehavior() throws ParseException {
-
- // Make a list of several formatters to test the behavior of DecimalQuantity.
- List<LocalizedNumberFormatter> formats = new ArrayList<LocalizedNumberFormatter>();
-
- DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
-
- DecimalFormatProperties properties = new DecimalFormatProperties();
- formats.add(NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
-
- properties =
- new DecimalFormatProperties()
- .setMinimumSignificantDigits(3)
- .setMaximumSignificantDigits(3)
- .setCompactStyle(CompactStyle.LONG);
- formats.add(NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
-
- properties =
- new DecimalFormatProperties()
- .setMinimumExponentDigits(1)
- .setMaximumIntegerDigits(3)
- .setMaximumFractionDigits(1);
- formats.add(NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
-
- properties = new DecimalFormatProperties().setRoundingIncrement(new BigDecimal("0.5"));
- formats.add(NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
-
- String[] cases = {
- "1.0",
- "2.01",
- "1234.56",
- "3000.0",
- "0.00026418",
- "0.01789261",
- "468160.0",
- "999000.0",
- "999900.0",
- "999990.0",
- "0.0",
- "12345678901.0",
- "-5193.48",
- };
-
- String[] hardCases = {
- "9999999999999900.0",
- "789000000000000000000000.0",
- "789123123567853156372158.0",
- "987654321987654321987654321987654321987654311987654321.0",
- };
-
- String[] doubleCases = {
- "512.0000000000017",
- "4095.9999999999977",
- "4095.999999999998",
- "4095.9999999999986",
- "4095.999999999999",
- "4095.9999999999995",
- "4096.000000000001",
- "4096.000000000002",
- "4096.000000000003",
- "4096.000000000004",
- "4096.000000000005",
- "4096.0000000000055",
- "4096.000000000006",
- "4096.000000000007",
- };
-
- int i = 0;
- for (String str : cases) {
- testDecimalQuantity(i++, str, formats, 0);
+ @Ignore
+ @Test
+ public void testBehavior() throws ParseException {
+
+ // Make a list of several formatters to test the behavior of DecimalQuantity.
+ List<LocalizedNumberFormatter> formats = new ArrayList<LocalizedNumberFormatter>();
+
+ DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
+
+ DecimalFormatProperties properties = new DecimalFormatProperties();
+ formats.add(
+ NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
+
+ properties = new DecimalFormatProperties().setMinimumSignificantDigits(3)
+ .setMaximumSignificantDigits(3).setCompactStyle(CompactStyle.LONG);
+ formats.add(
+ NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
+
+ properties = new DecimalFormatProperties().setMinimumExponentDigits(1).setMaximumIntegerDigits(3)
+ .setMaximumFractionDigits(1);
+ formats.add(
+ NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
+
+ properties = new DecimalFormatProperties().setRoundingIncrement(new BigDecimal("0.5"));
+ formats.add(
+ NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
+
+ String[] cases = {
+ "1.0",
+ "2.01",
+ "1234.56",
+ "3000.0",
+ "0.00026418",
+ "0.01789261",
+ "468160.0",
+ "999000.0",
+ "999900.0",
+ "999990.0",
+ "0.0",
+ "12345678901.0",
+ "-5193.48", };
+
+ String[] hardCases = {
+ "9999999999999900.0",
+ "789000000000000000000000.0",
+ "789123123567853156372158.0",
+ "987654321987654321987654321987654321987654311987654321.0", };
+
+ String[] doubleCases = {
+ "512.0000000000017",
+ "4095.9999999999977",
+ "4095.999999999998",
+ "4095.9999999999986",
+ "4095.999999999999",
+ "4095.9999999999995",
+ "4096.000000000001",
+ "4096.000000000002",
+ "4096.000000000003",
+ "4096.000000000004",
+ "4096.000000000005",
+ "4096.0000000000055",
+ "4096.000000000006",
+ "4096.000000000007", };
+
+ int i = 0;
+ for (String str : cases) {
+ testDecimalQuantity(i++, str, formats, 0);
+ }
+
+ i = 0;
+ for (String str : hardCases) {
+ testDecimalQuantity(i++, str, formats, 1);
+ }
+
+ i = 0;
+ for (String str : doubleCases) {
+ testDecimalQuantity(i++, str, formats, 2);
+ }
}
- i = 0;
- for (String str : hardCases) {
- testDecimalQuantity(i++, str, formats, 1);
+ static void testDecimalQuantity(
+ int t,
+ String str,
+ List<LocalizedNumberFormatter> formats,
+ int mode) {
+ if (mode == 2) {
+ assertEquals("Double is not valid", Double.toString(Double.parseDouble(str)), str);
+ }
+
+ List<DecimalQuantity> qs = new ArrayList<DecimalQuantity>();
+ BigDecimal d = new BigDecimal(str);
+ qs.add(new DecimalQuantity_SimpleStorage(d));
+ if (mode == 0)
+ qs.add(new DecimalQuantity_64BitBCD(d));
+ qs.add(new DecimalQuantity_ByteArrayBCD(d));
+ qs.add(new DecimalQuantity_DualStorageBCD(d));
+
+ if (new BigDecimal(Double.toString(d.doubleValue())).compareTo(d) == 0) {
+ double dv = d.doubleValue();
+ qs.add(new DecimalQuantity_SimpleStorage(dv));
+ if (mode == 0)
+ qs.add(new DecimalQuantity_64BitBCD(dv));
+ qs.add(new DecimalQuantity_ByteArrayBCD(dv));
+ qs.add(new DecimalQuantity_DualStorageBCD(dv));
+ }
+
+ if (new BigDecimal(Long.toString(d.longValue())).compareTo(d) == 0) {
+ double lv = d.longValue();
+ qs.add(new DecimalQuantity_SimpleStorage(lv));
+ if (mode == 0)
+ qs.add(new DecimalQuantity_64BitBCD(lv));
+ qs.add(new DecimalQuantity_ByteArrayBCD(lv));
+ qs.add(new DecimalQuantity_DualStorageBCD(lv));
+ }
+
+ testDecimalQuantityExpectedOutput(qs.get(0), str);
+
+ if (qs.size() == 1) {
+ return;
+ }
+
+ for (int i = 1; i < qs.size(); i++) {
+ DecimalQuantity q0 = qs.get(0);
+ DecimalQuantity q1 = qs.get(i);
+ testDecimalQuantityExpectedOutput(q1, str);
+ testDecimalQuantityRounding(q0, q1);
+ testDecimalQuantityRoundingInterval(q0, q1);
+ testDecimalQuantityMath(q0, q1);
+ testDecimalQuantityWithFormats(q0, q1, formats);
+ }
}
- i = 0;
- for (String str : doubleCases) {
- testDecimalQuantity(i++, str, formats, 2);
+ private static void testDecimalQuantityExpectedOutput(DecimalQuantity rq, String expected) {
+ DecimalQuantity q0 = rq.createCopy();
+ // Force an accurate double
+ q0.roundToInfinity();
+ q0.setIntegerLength(1, Integer.MAX_VALUE);
+ q0.setFractionLength(1, Integer.MAX_VALUE);
+ String actual = q0.toPlainString();
+ assertEquals("Unexpected output from simple string conversion (" + q0 + ")", expected, actual);
}
- }
- static void testDecimalQuantity(int t, String str, List<LocalizedNumberFormatter> formats, int mode) {
- if (mode == 2) {
- assertEquals("Double is not valid", Double.toString(Double.parseDouble(str)), str);
+ private static final MathContext MATH_CONTEXT_HALF_EVEN = new MathContext(0, RoundingMode.HALF_EVEN);
+ private static final MathContext MATH_CONTEXT_CEILING = new MathContext(0, RoundingMode.CEILING);
+ private static final MathContext MATH_CONTEXT_PRECISION = new MathContext(3, RoundingMode.HALF_UP);
+
+ private static void testDecimalQuantityRounding(DecimalQuantity rq0, DecimalQuantity rq1) {
+ DecimalQuantity q0 = rq0.createCopy();
+ DecimalQuantity q1 = rq1.createCopy();
+ q0.roundToMagnitude(-1, MATH_CONTEXT_HALF_EVEN);
+ q1.roundToMagnitude(-1, MATH_CONTEXT_HALF_EVEN);
+ testDecimalQuantityBehavior(q0, q1);
+
+ q0 = rq0.createCopy();
+ q1 = rq1.createCopy();
+ q0.roundToMagnitude(-1, MATH_CONTEXT_CEILING);
+ q1.roundToMagnitude(-1, MATH_CONTEXT_CEILING);
+ testDecimalQuantityBehavior(q0, q1);
+
+ q0 = rq0.createCopy();
+ q1 = rq1.createCopy();
+ q0.roundToMagnitude(-1, MATH_CONTEXT_PRECISION);
+ q1.roundToMagnitude(-1, MATH_CONTEXT_PRECISION);
+ testDecimalQuantityBehavior(q0, q1);
}
- List<DecimalQuantity> qs = new ArrayList<DecimalQuantity>();
- BigDecimal d = new BigDecimal(str);
- qs.add(new DecimalQuantity_SimpleStorage(d));
- if (mode == 0) qs.add(new DecimalQuantity_64BitBCD(d));
- qs.add(new DecimalQuantity_ByteArrayBCD(d));
- qs.add(new DecimalQuantity_DualStorageBCD(d));
-
- if (new BigDecimal(Double.toString(d.doubleValue())).compareTo(d) == 0) {
- double dv = d.doubleValue();
- qs.add(new DecimalQuantity_SimpleStorage(dv));
- if (mode == 0) qs.add(new DecimalQuantity_64BitBCD(dv));
- qs.add(new DecimalQuantity_ByteArrayBCD(dv));
- qs.add(new DecimalQuantity_DualStorageBCD(dv));
+ private static void testDecimalQuantityRoundingInterval(DecimalQuantity rq0, DecimalQuantity rq1) {
+ DecimalQuantity q0 = rq0.createCopy();
+ DecimalQuantity q1 = rq1.createCopy();
+ q0.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_HALF_EVEN);
+ q1.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_HALF_EVEN);
+ testDecimalQuantityBehavior(q0, q1);
+
+ q0 = rq0.createCopy();
+ q1 = rq1.createCopy();
+ q0.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_CEILING);
+ q1.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_CEILING);
+ testDecimalQuantityBehavior(q0, q1);
}
- if (new BigDecimal(Long.toString(d.longValue())).compareTo(d) == 0) {
- double lv = d.longValue();
- qs.add(new DecimalQuantity_SimpleStorage(lv));
- if (mode == 0) qs.add(new DecimalQuantity_64BitBCD(lv));
- qs.add(new DecimalQuantity_ByteArrayBCD(lv));
- qs.add(new DecimalQuantity_DualStorageBCD(lv));
+ private static void testDecimalQuantityMath(DecimalQuantity rq0, DecimalQuantity rq1) {
+ DecimalQuantity q0 = rq0.createCopy();
+ DecimalQuantity q1 = rq1.createCopy();
+ q0.adjustMagnitude(-3);
+ q1.adjustMagnitude(-3);
+ testDecimalQuantityBehavior(q0, q1);
+
+ q0 = rq0.createCopy();
+ q1 = rq1.createCopy();
+ q0.multiplyBy(new BigDecimal("3.14159"));
+ q1.multiplyBy(new BigDecimal("3.14159"));
+ testDecimalQuantityBehavior(q0, q1);
}
- testDecimalQuantityExpectedOutput(qs.get(0), str);
-
- if (qs.size() == 1) {
- return;
+ private static void testDecimalQuantityWithFormats(
+ DecimalQuantity rq0,
+ DecimalQuantity rq1,
+ List<LocalizedNumberFormatter> formats) {
+ for (LocalizedNumberFormatter format : formats) {
+ DecimalQuantity q0 = rq0.createCopy();
+ DecimalQuantity q1 = rq1.createCopy();
+ String s1 = format.format(q0).toString();
+ String s2 = format.format(q1).toString();
+ assertEquals("Different output from formatter (" + q0 + ", " + q1 + ")", s1, s2);
+ }
}
- for (int i = 1; i < qs.size(); i++) {
- DecimalQuantity q0 = qs.get(0);
- DecimalQuantity q1 = qs.get(i);
- testDecimalQuantityExpectedOutput(q1, str);
- testDecimalQuantityRounding(q0, q1);
- testDecimalQuantityRoundingInterval(q0, q1);
- testDecimalQuantityMath(q0, q1);
- testDecimalQuantityWithFormats(q0, q1, formats);
- }
- }
-
- private static void testDecimalQuantityExpectedOutput(DecimalQuantity rq, String expected) {
- DecimalQuantity q0 = rq.createCopy();
- // Force an accurate double
- q0.roundToInfinity();
- q0.setIntegerLength(1, Integer.MAX_VALUE);
- q0.setFractionLength(1, Integer.MAX_VALUE);
- String actual = q0.toPlainString();
- assertEquals("Unexpected output from simple string conversion (" + q0 + ")", expected, actual);
- }
-
- private static final MathContext MATH_CONTEXT_HALF_EVEN =
- new MathContext(0, RoundingMode.HALF_EVEN);
- private static final MathContext MATH_CONTEXT_CEILING = new MathContext(0, RoundingMode.CEILING);
- private static final MathContext MATH_CONTEXT_PRECISION =
- new MathContext(3, RoundingMode.HALF_UP);
-
- private static void testDecimalQuantityRounding(DecimalQuantity rq0, DecimalQuantity rq1) {
- DecimalQuantity q0 = rq0.createCopy();
- DecimalQuantity q1 = rq1.createCopy();
- q0.roundToMagnitude(-1, MATH_CONTEXT_HALF_EVEN);
- q1.roundToMagnitude(-1, MATH_CONTEXT_HALF_EVEN);
- testDecimalQuantityBehavior(q0, q1);
-
- q0 = rq0.createCopy();
- q1 = rq1.createCopy();
- q0.roundToMagnitude(-1, MATH_CONTEXT_CEILING);
- q1.roundToMagnitude(-1, MATH_CONTEXT_CEILING);
- testDecimalQuantityBehavior(q0, q1);
-
- q0 = rq0.createCopy();
- q1 = rq1.createCopy();
- q0.roundToMagnitude(-1, MATH_CONTEXT_PRECISION);
- q1.roundToMagnitude(-1, MATH_CONTEXT_PRECISION);
- testDecimalQuantityBehavior(q0, q1);
- }
-
- private static void testDecimalQuantityRoundingInterval(DecimalQuantity rq0, DecimalQuantity rq1) {
- DecimalQuantity q0 = rq0.createCopy();
- DecimalQuantity q1 = rq1.createCopy();
- q0.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_HALF_EVEN);
- q1.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_HALF_EVEN);
- testDecimalQuantityBehavior(q0, q1);
-
- q0 = rq0.createCopy();
- q1 = rq1.createCopy();
- q0.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_CEILING);
- q1.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_CEILING);
- testDecimalQuantityBehavior(q0, q1);
- }
-
- private static void testDecimalQuantityMath(DecimalQuantity rq0, DecimalQuantity rq1) {
- DecimalQuantity q0 = rq0.createCopy();
- DecimalQuantity q1 = rq1.createCopy();
- q0.adjustMagnitude(-3);
- q1.adjustMagnitude(-3);
- testDecimalQuantityBehavior(q0, q1);
-
- q0 = rq0.createCopy();
- q1 = rq1.createCopy();
- q0.multiplyBy(new BigDecimal("3.14159"));
- q1.multiplyBy(new BigDecimal("3.14159"));
- testDecimalQuantityBehavior(q0, q1);
- }
-
- private static void testDecimalQuantityWithFormats(
- DecimalQuantity rq0, DecimalQuantity rq1, List<LocalizedNumberFormatter> formats) {
- for (LocalizedNumberFormatter format : formats) {
- DecimalQuantity q0 = rq0.createCopy();
- DecimalQuantity q1 = rq1.createCopy();
- String s1 = format.format(q0).toString();
- String s2 = format.format(q1).toString();
- assertEquals("Different output from formatter (" + q0 + ", " + q1 + ")", s1, s2);
+ private static void testDecimalQuantityBehavior(DecimalQuantity rq0, DecimalQuantity rq1) {
+ DecimalQuantity q0 = rq0.createCopy();
+ DecimalQuantity q1 = rq1.createCopy();
+
+ assertEquals("Different sign (" + q0 + ", " + q1 + ")", q0.isNegative(), q1.isNegative());
+
+ assertEquals("Different fingerprint (" + q0 + ", " + q1 + ")",
+ q0.getPositionFingerprint(),
+ q1.getPositionFingerprint());
+
+ assertDoubleEquals("Different double values (" + q0 + ", " + q1 + ")",
+ q0.toDouble(),
+ q1.toDouble());
+
+ assertBigDecimalEquals("Different BigDecimal values (" + q0 + ", " + q1 + ")",
+ q0.toBigDecimal(),
+ q1.toBigDecimal());
+
+ q0.roundToInfinity();
+ q1.roundToInfinity();
+
+ assertEquals("Different lower display magnitude",
+ q0.getLowerDisplayMagnitude(),
+ q1.getLowerDisplayMagnitude());
+ assertEquals("Different upper display magnitude",
+ q0.getUpperDisplayMagnitude(),
+ q1.getUpperDisplayMagnitude());
+
+ for (int m = q0.getUpperDisplayMagnitude(); m >= q0.getLowerDisplayMagnitude(); m--) {
+ assertEquals("Different digit at magnitude " + m + " (" + q0 + ", " + q1 + ")",
+ q0.getDigit(m),
+ q1.getDigit(m));
+ }
+
+ if (rq0 instanceof DecimalQuantity_DualStorageBCD) {
+ String message = ((DecimalQuantity_DualStorageBCD) rq0).checkHealth();
+ if (message != null)
+ errln(message);
+ }
+ if (rq1 instanceof DecimalQuantity_DualStorageBCD) {
+ String message = ((DecimalQuantity_DualStorageBCD) rq1).checkHealth();
+ if (message != null)
+ errln(message);
+ }
}
- }
-
- private static void testDecimalQuantityBehavior(DecimalQuantity rq0, DecimalQuantity rq1) {
- DecimalQuantity q0 = rq0.createCopy();
- DecimalQuantity q1 = rq1.createCopy();
-
- assertEquals("Different sign (" + q0 + ", " + q1 + ")", q0.isNegative(), q1.isNegative());
-
- assertEquals(
- "Different fingerprint (" + q0 + ", " + q1 + ")",
- q0.getPositionFingerprint(),
- q1.getPositionFingerprint());
-
- assertDoubleEquals(
- "Different double values (" + q0 + ", " + q1 + ")", q0.toDouble(), q1.toDouble());
-
- assertBigDecimalEquals(
- "Different BigDecimal values (" + q0 + ", " + q1 + ")",
- q0.toBigDecimal(),
- q1.toBigDecimal());
-
- q0.roundToInfinity();
- q1.roundToInfinity();
-
- assertEquals(
- "Different lower display magnitude",
- q0.getLowerDisplayMagnitude(),
- q1.getLowerDisplayMagnitude());
- assertEquals(
- "Different upper display magnitude",
- q0.getUpperDisplayMagnitude(),
- q1.getUpperDisplayMagnitude());
-
- for (int m = q0.getUpperDisplayMagnitude(); m >= q0.getLowerDisplayMagnitude(); m--) {
- assertEquals(
- "Different digit at magnitude " + m + " (" + q0 + ", " + q1 + ")",
- q0.getDigit(m),
- q1.getDigit(m));
+
+ @Test
+ public void testSwitchStorage() {
+ DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD();
+
+ fq.setToLong(1234123412341234L);
+ assertFalse("Should not be using byte array", fq.isUsingBytes());
+ assertEquals("Failed on initialize", "1234123412341234E0", fq.toNumberString());
+ assertNull("Failed health check", fq.checkHealth());
+ // Long -> Bytes
+ fq.appendDigit((byte) 5, 0, true);
+ assertTrue("Should be using byte array", fq.isUsingBytes());
+ assertEquals("Failed on multiply", "12341234123412345E0", fq.toNumberString());
+ assertNull("Failed health check", fq.checkHealth());
+ // Bytes -> Long
+ fq.roundToMagnitude(5, MATH_CONTEXT_HALF_EVEN);
+ assertFalse("Should not be using byte array", fq.isUsingBytes());
+ assertEquals("Failed on round", "123412341234E5", fq.toNumberString());
+ assertNull("Failed health check", fq.checkHealth());
}
- if (rq0 instanceof DecimalQuantity_DualStorageBCD) {
- String message = ((DecimalQuantity_DualStorageBCD) rq0).checkHealth();
- if (message != null) errln(message);
+ @Test
+ public void testAppend() {
+ DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD();
+ fq.appendDigit((byte) 1, 0, true);
+ assertEquals("Failed on append", "1E0", fq.toNumberString());
+ assertNull("Failed health check", fq.checkHealth());
+ fq.appendDigit((byte) 2, 0, true);
+ assertEquals("Failed on append", "12E0", fq.toNumberString());
+ assertNull("Failed health check", fq.checkHealth());
+ fq.appendDigit((byte) 3, 1, true);
+ assertEquals("Failed on append", "1203E0", fq.toNumberString());
+ assertNull("Failed health check", fq.checkHealth());
+ fq.appendDigit((byte) 0, 1, true);
+ assertEquals("Failed on append", "1203E2", fq.toNumberString());
+ assertNull("Failed health check", fq.checkHealth());
+ fq.appendDigit((byte) 4, 0, true);
+ assertEquals("Failed on append", "1203004E0", fq.toNumberString());
+ assertNull("Failed health check", fq.checkHealth());
+ fq.appendDigit((byte) 0, 0, true);
+ assertEquals("Failed on append", "1203004E1", fq.toNumberString());
+ assertNull("Failed health check", fq.checkHealth());
+ fq.appendDigit((byte) 5, 0, false);
+ assertEquals("Failed on append", "120300405E-1", fq.toNumberString());
+ assertNull("Failed health check", fq.checkHealth());
+ fq.appendDigit((byte) 6, 0, false);
+ assertEquals("Failed on append", "1203004056E-2", fq.toNumberString());
+ assertNull("Failed health check", fq.checkHealth());
+ fq.appendDigit((byte) 7, 3, false);
+ assertEquals("Failed on append", "12030040560007E-6", fq.toNumberString());
+ assertNull("Failed health check", fq.checkHealth());
+ StringBuilder baseExpected = new StringBuilder("12030040560007");
+ for (int i = 0; i < 10; i++) {
+ fq.appendDigit((byte) 8, 0, false);
+ baseExpected.append('8');
+ StringBuilder expected = new StringBuilder(baseExpected);
+ expected.append("E");
+ expected.append(-7 - i);
+ assertEquals("Failed on append", expected.toString(), fq.toNumberString());
+ assertNull("Failed health check", fq.checkHealth());
+ }
+ fq.appendDigit((byte) 9, 2, false);
+ baseExpected.append("009");
+ StringBuilder expected = new StringBuilder(baseExpected);
+ expected.append('E');
+ expected.append("-19");
+ assertEquals("Failed on append", expected.toString(), fq.toNumberString());
+ assertNull("Failed health check", fq.checkHealth());
}
- if (rq1 instanceof DecimalQuantity_DualStorageBCD) {
- String message = ((DecimalQuantity_DualStorageBCD) rq1).checkHealth();
- if (message != null) errln(message);
+
+ @Ignore
+ @Test
+ public void testConvertToAccurateDouble() {
+ // based on https://github.com/google/double-conversion/issues/28
+ double[] hardDoubles = {
+ 1651087494906221570.0,
+ -5074790912492772E-327,
+ 83602530019752571E-327,
+ 2.207817077636718750000000000000,
+ 1.818351745605468750000000000000,
+ 3.941719055175781250000000000000,
+ 3.738609313964843750000000000000,
+ 3.967735290527343750000000000000,
+ 1.328025817871093750000000000000,
+ 3.920967102050781250000000000000,
+ 1.015235900878906250000000000000,
+ 1.335227966308593750000000000000,
+ 1.344520568847656250000000000000,
+ 2.879127502441406250000000000000,
+ 3.695838928222656250000000000000,
+ 1.845344543457031250000000000000,
+ 3.793952941894531250000000000000,
+ 3.211402893066406250000000000000,
+ 2.565971374511718750000000000000,
+ 0.965156555175781250000000000000,
+ 2.700004577636718750000000000000,
+ 0.767097473144531250000000000000,
+ 1.780448913574218750000000000000,
+ 2.624839782714843750000000000000,
+ 1.305290222167968750000000000000,
+ 3.834922790527343750000000000000, };
+
+ double[] integerDoubles = {
+ 51423,
+ 51423e10,
+ 4.503599627370496E15,
+ 6.789512076111555E15,
+ 9.007199254740991E15,
+ 9.007199254740992E15 };
+
+ for (double d : hardDoubles) {
+ checkDoubleBehavior(d, true, "");
+ }
+
+ for (double d : integerDoubles) {
+ checkDoubleBehavior(d, false, "");
+ }
+
+ assertEquals("NaN check failed",
+ Double.NaN,
+ new DecimalQuantity_DualStorageBCD(Double.NaN).toDouble());
+ assertEquals("Inf check failed",
+ Double.POSITIVE_INFINITY,
+ new DecimalQuantity_DualStorageBCD(Double.POSITIVE_INFINITY).toDouble());
+ assertEquals("-Inf check failed",
+ Double.NEGATIVE_INFINITY,
+ new DecimalQuantity_DualStorageBCD(Double.NEGATIVE_INFINITY).toDouble());
+
+ // Generate random doubles
+ String alert = "UNEXPECTED FAILURE: PLEASE REPORT THIS MESSAGE TO THE ICU TEAM: ";
+ Random rnd = new Random();
+ for (int i = 0; i < 10000; i++) {
+ double d = Double.longBitsToDouble(rnd.nextLong());
+ if (Double.isNaN(d) || Double.isInfinite(d))
+ continue;
+ checkDoubleBehavior(d, false, alert);
+ }
}
- }
-
- @Test
- public void testSwitchStorage() {
- DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD();
-
- fq.setToLong(1234123412341234L);
- assertFalse("Should not be using byte array", fq.isUsingBytes());
- assertEquals("Failed on initialize", "1234123412341234E0", fq.toNumberString());
- assertNull("Failed health check", fq.checkHealth());
- // Long -> Bytes
- fq.appendDigit((byte) 5, 0, true);
- assertTrue("Should be using byte array", fq.isUsingBytes());
- assertEquals("Failed on multiply", "12341234123412345E0", fq.toNumberString());
- assertNull("Failed health check", fq.checkHealth());
- // Bytes -> Long
- fq.roundToMagnitude(5, MATH_CONTEXT_HALF_EVEN);
- assertFalse("Should not be using byte array", fq.isUsingBytes());
- assertEquals("Failed on round", "123412341234E5", fq.toNumberString());
- assertNull("Failed health check", fq.checkHealth());
- }
-
- @Test
- public void testAppend() {
- DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD();
- fq.appendDigit((byte) 1, 0, true);
- assertEquals("Failed on append", "1E0", fq.toNumberString());
- assertNull("Failed health check", fq.checkHealth());
- fq.appendDigit((byte) 2, 0, true);
- assertEquals("Failed on append", "12E0", fq.toNumberString());
- assertNull("Failed health check", fq.checkHealth());
- fq.appendDigit((byte) 3, 1, true);
- assertEquals("Failed on append", "1203E0", fq.toNumberString());
- assertNull("Failed health check", fq.checkHealth());
- fq.appendDigit((byte) 0, 1, true);
- assertEquals("Failed on append", "1203E2", fq.toNumberString());
- assertNull("Failed health check", fq.checkHealth());
- fq.appendDigit((byte) 4, 0, true);
- assertEquals("Failed on append", "1203004E0", fq.toNumberString());
- assertNull("Failed health check", fq.checkHealth());
- fq.appendDigit((byte) 0, 0, true);
- assertEquals("Failed on append", "1203004E1", fq.toNumberString());
- assertNull("Failed health check", fq.checkHealth());
- fq.appendDigit((byte) 5, 0, false);
- assertEquals("Failed on append", "120300405E-1", fq.toNumberString());
- assertNull("Failed health check", fq.checkHealth());
- fq.appendDigit((byte) 6, 0, false);
- assertEquals("Failed on append", "1203004056E-2", fq.toNumberString());
- assertNull("Failed health check", fq.checkHealth());
- fq.appendDigit((byte) 7, 3, false);
- assertEquals("Failed on append", "12030040560007E-6", fq.toNumberString());
- assertNull("Failed health check", fq.checkHealth());
- StringBuilder baseExpected = new StringBuilder("12030040560007");
- for (int i = 0; i < 10; i++) {
- fq.appendDigit((byte) 8, 0, false);
- baseExpected.append('8');
- StringBuilder expected = new StringBuilder(baseExpected);
- expected.append("E");
- expected.append(-7 - i);
- assertEquals("Failed on append", expected.toString(), fq.toNumberString());
- assertNull("Failed health check", fq.checkHealth());
+
+ private static void checkDoubleBehavior(double d, boolean explicitRequired, String alert) {
+ DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD(d);
+ if (explicitRequired) {
+ assertTrue(alert + "Should be using approximate double", !fq.explicitExactDouble);
+ }
+ assertEquals(alert + "Initial construction from hard double", d, fq.toDouble());
+ fq.roundToInfinity();
+ if (explicitRequired) {
+ assertTrue(alert + "Should not be using approximate double", fq.explicitExactDouble);
+ }
+ assertDoubleEquals(alert + "After conversion to exact BCD (double)", d, fq.toDouble());
+ assertBigDecimalEquals(alert + "After conversion to exact BCD (BigDecimal)",
+ new BigDecimal(Double.toString(d)),
+ fq.toBigDecimal());
}
- fq.appendDigit((byte) 9, 2, false);
- baseExpected.append("009");
- StringBuilder expected = new StringBuilder(baseExpected);
- expected.append('E');
- expected.append("-19");
- assertEquals("Failed on append", expected.toString(), fq.toNumberString());
- assertNull("Failed health check", fq.checkHealth());
- }
-
- @Ignore
- @Test
- public void testConvertToAccurateDouble() {
- // based on https://github.com/google/double-conversion/issues/28
- double[] hardDoubles = {
- 1651087494906221570.0,
- -5074790912492772E-327,
- 83602530019752571E-327,
- 2.207817077636718750000000000000,
- 1.818351745605468750000000000000,
- 3.941719055175781250000000000000,
- 3.738609313964843750000000000000,
- 3.967735290527343750000000000000,
- 1.328025817871093750000000000000,
- 3.920967102050781250000000000000,
- 1.015235900878906250000000000000,
- 1.335227966308593750000000000000,
- 1.344520568847656250000000000000,
- 2.879127502441406250000000000000,
- 3.695838928222656250000000000000,
- 1.845344543457031250000000000000,
- 3.793952941894531250000000000000,
- 3.211402893066406250000000000000,
- 2.565971374511718750000000000000,
- 0.965156555175781250000000000000,
- 2.700004577636718750000000000000,
- 0.767097473144531250000000000000,
- 1.780448913574218750000000000000,
- 2.624839782714843750000000000000,
- 1.305290222167968750000000000000,
- 3.834922790527343750000000000000,
- };
-
- double[] integerDoubles = {
- 51423,
- 51423e10,
- 4.503599627370496E15,
- 6.789512076111555E15,
- 9.007199254740991E15,
- 9.007199254740992E15
- };
-
- for (double d : hardDoubles) {
- checkDoubleBehavior(d, true, "");
+
+ @Test
+ public void testUseApproximateDoubleWhenAble() {
+ Object[][] cases = {
+ { 1.2345678, 1, MATH_CONTEXT_HALF_EVEN, false },
+ { 1.2345678, 7, MATH_CONTEXT_HALF_EVEN, false },
+ { 1.2345678, 12, MATH_CONTEXT_HALF_EVEN, false },
+ { 1.2345678, 13, MATH_CONTEXT_HALF_EVEN, true },
+ { 1.235, 1, MATH_CONTEXT_HALF_EVEN, false },
+ { 1.235, 2, MATH_CONTEXT_HALF_EVEN, true },
+ { 1.235, 3, MATH_CONTEXT_HALF_EVEN, false },
+ { 1.000000000000001, 0, MATH_CONTEXT_HALF_EVEN, false },
+ { 1.000000000000001, 0, MATH_CONTEXT_CEILING, true },
+ { 1.235, 1, MATH_CONTEXT_CEILING, false },
+ { 1.235, 2, MATH_CONTEXT_CEILING, false },
+ { 1.235, 3, MATH_CONTEXT_CEILING, true } };
+
+ for (Object[] cas : cases) {
+ double d = (Double) cas[0];
+ int maxFrac = (Integer) cas[1];
+ MathContext mc = (MathContext) cas[2];
+ boolean usesExact = (Boolean) cas[3];
+
+ DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD(d);
+ assertTrue("Should be using approximate double", !fq.explicitExactDouble);
+ fq.roundToMagnitude(-maxFrac, mc);
+ assertEquals(
+ "Using approximate double after rounding: " + d + " maxFrac=" + maxFrac + " " + mc,
+ usesExact,
+ fq.explicitExactDouble);
+ }
}
- for (double d : integerDoubles) {
- checkDoubleBehavior(d, false, "");
+ @Test
+ public void testDecimalQuantityBehaviorStandalone() {
+ DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD();
+ assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 0E0>");
+ fq.setToInt(51423);
+ assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 51423E0>");
+ fq.adjustMagnitude(-3);
+ assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 51423E-3>");
+ fq.setToLong(999999999999000L);
+ assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 999999999999E3>");
+ fq.setIntegerLength(2, 5);
+ assertToStringAndHealth(fq, "<DecimalQuantity 5:2:0:-999 long 999999999999E3>");
+ fq.setFractionLength(3, 6);
+ assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 999999999999E3>");
+ fq.setToDouble(987.654321);
+ assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
+ fq.roundToInfinity();
+ assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
+ fq.roundToIncrement(new BigDecimal("0.005"), MATH_CONTEXT_HALF_EVEN);
+ assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987655E-3>");
+ fq.roundToMagnitude(-2, MATH_CONTEXT_HALF_EVEN);
+ assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 98766E-2>");
}
- assertEquals("NaN check failed", Double.NaN, new DecimalQuantity_DualStorageBCD(Double.NaN).toDouble());
- assertEquals(
- "Inf check failed",
- Double.POSITIVE_INFINITY,
- new DecimalQuantity_DualStorageBCD(Double.POSITIVE_INFINITY).toDouble());
- assertEquals(
- "-Inf check failed",
- Double.NEGATIVE_INFINITY,
- new DecimalQuantity_DualStorageBCD(Double.NEGATIVE_INFINITY).toDouble());
-
- // Generate random doubles
- String alert = "UNEXPECTED FAILURE: PLEASE REPORT THIS MESSAGE TO THE ICU TEAM: ";
- Random rnd = new Random();
- for (int i = 0; i < 10000; i++) {
- double d = Double.longBitsToDouble(rnd.nextLong());
- if (Double.isNaN(d) || Double.isInfinite(d)) continue;
- checkDoubleBehavior(d, false, alert);
+ static void assertDoubleEquals(String message, double d1, double d2) {
+ boolean equal = (Math.abs(d1 - d2) < 1e-6) || (Math.abs((d1 - d2) / d1) < 1e-6);
+ handleAssert(equal, message, d1, d2, null, false);
}
- }
- private static void checkDoubleBehavior(double d, boolean explicitRequired, String alert) {
- DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD(d);
- if (explicitRequired) {
- assertTrue(alert + "Should be using approximate double", !fq.explicitExactDouble);
+ static void assertBigDecimalEquals(String message, String d1, BigDecimal d2) {
+ assertBigDecimalEquals(message, new BigDecimal(d1), d2);
}
- assertEquals(alert + "Initial construction from hard double", d, fq.toDouble());
- fq.roundToInfinity();
- if (explicitRequired) {
- assertTrue(alert + "Should not be using approximate double", fq.explicitExactDouble);
+
+ static void assertBigDecimalEquals(String message, BigDecimal d1, BigDecimal d2) {
+ boolean equal = d1.compareTo(d2) == 0;
+ handleAssert(equal, message, d1, d2, null, false);
}
- assertDoubleEquals(alert + "After conversion to exact BCD (double)", d, fq.toDouble());
- assertBigDecimalEquals(
- alert + "After conversion to exact BCD (BigDecimal)",
- new BigDecimal(Double.toString(d)),
- fq.toBigDecimal());
- }
-
- @Test
- public void testUseApproximateDoubleWhenAble() {
- Object[][] cases = {
- {1.2345678, 1, MATH_CONTEXT_HALF_EVEN, false},
- {1.2345678, 7, MATH_CONTEXT_HALF_EVEN, false},
- {1.2345678, 12, MATH_CONTEXT_HALF_EVEN, false},
- {1.2345678, 13, MATH_CONTEXT_HALF_EVEN, true},
- {1.235, 1, MATH_CONTEXT_HALF_EVEN, false},
- {1.235, 2, MATH_CONTEXT_HALF_EVEN, true},
- {1.235, 3, MATH_CONTEXT_HALF_EVEN, false},
- {1.000000000000001, 0, MATH_CONTEXT_HALF_EVEN, false},
- {1.000000000000001, 0, MATH_CONTEXT_CEILING, true},
- {1.235, 1, MATH_CONTEXT_CEILING, false},
- {1.235, 2, MATH_CONTEXT_CEILING, false},
- {1.235, 3, MATH_CONTEXT_CEILING, true}
- };
-
- for (Object[] cas : cases) {
- double d = (Double) cas[0];
- int maxFrac = (Integer) cas[1];
- MathContext mc = (MathContext) cas[2];
- boolean usesExact = (Boolean) cas[3];
-
- DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD(d);
- assertTrue("Should be using approximate double", !fq.explicitExactDouble);
- fq.roundToMagnitude(-maxFrac, mc);
- assertEquals(
- "Using approximate double after rounding: " + d + " maxFrac=" + maxFrac + " " + mc,
- usesExact,
- fq.explicitExactDouble);
+
+ static void assertToStringAndHealth(DecimalQuantity_DualStorageBCD fq, String expected) {
+ String actual = fq.toString();
+ assertEquals("DecimalQuantity toString", expected, actual);
+ String health = fq.checkHealth();
+ assertNull("DecimalQuantity health", health);
}
- }
-
- @Test
- public void testDecimalQuantityBehaviorStandalone() {
- DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD();
- assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 0E0>");
- fq.setToInt(51423);
- assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 51423E0>");
- fq.adjustMagnitude(-3);
- assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 51423E-3>");
- fq.setToLong(999999999999000L);
- assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 999999999999E3>");
- fq.setIntegerLength(2, 5);
- assertToStringAndHealth(fq, "<DecimalQuantity 5:2:0:-999 long 999999999999E3>");
- fq.setFractionLength(3, 6);
- assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 999999999999E3>");
- fq.setToDouble(987.654321);
- assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
- fq.roundToInfinity();
- assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
- fq.roundToIncrement(new BigDecimal("0.005"), MATH_CONTEXT_HALF_EVEN);
- assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987655E-3>");
- fq.roundToMagnitude(-2, MATH_CONTEXT_HALF_EVEN);
- assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 98766E-2>");
- }
-
- static void assertDoubleEquals(String message, double d1, double d2) {
- boolean equal = (Math.abs(d1 - d2) < 1e-6) || (Math.abs((d1 - d2) / d1) < 1e-6);
- handleAssert(equal, message, d1, d2, null, false);
- }
-
- static void assertBigDecimalEquals(String message, String d1, BigDecimal d2) {
- assertBigDecimalEquals(message, new BigDecimal(d1), d2);
- }
-
- static void assertBigDecimalEquals(String message, BigDecimal d1, BigDecimal d2) {
- boolean equal = d1.compareTo(d2) == 0;
- handleAssert(equal, message, d1, d2, null, false);
- }
-
- static void assertToStringAndHealth(DecimalQuantity_DualStorageBCD fq, String expected) {
- String actual = fq.toString();
- assertEquals("DecimalQuantity toString", expected, actual);
- String health = fq.checkHealth();
- assertNull("DecimalQuantity health", health);
- }
}
@Test
public void testSimpleModifier() {
String[] patterns = { "{0}", "X{0}Y", "XX{0}YYY", "{0}YY", "XX📺XX{0}" };
- Object[][] outputs = { { "", 0, 0 }, { "a📻bcde", 0, 0 }, { "a📻bcde", 4, 4 }, { "a📻bcde", 3, 5 } };
+ Object[][] outputs = {
+ { "", 0, 0 },
+ { "a📻bcde", 0, 0 },
+ { "a📻bcde", 4, 4 },
+ { "a📻bcde", 3, 5 } };
int[] prefixLens = { 0, 1, 2, 0, 6 };
- String[][] expectedCharFields = { { "|", "n" }, { "X|Y", "%n%" }, { "XX|YYY", "%%n%%%" }, { "|YY", "n%%" },
+ String[][] expectedCharFields = {
+ { "|", "n" },
+ { "X|Y", "%n%" },
+ { "XX|YYY", "%%n%%%" },
+ { "|YY", "n%%" },
{ "XX📺XX|", "%%%%%%n" } };
- String[][] expecteds = { { "", "XY", "XXYYY", "YY", "XX📺XX" },
+ String[][] expecteds = {
+ { "", "XY", "XXYYY", "YY", "XX📺XX" },
{ "a📻bcde", "XYa📻bcde", "XXYYYa📻bcde", "YYa📻bcde", "XX📺XXa📻bcde" },
{ "a📻bcde", "a📻bXYcde", "a📻bXXYYYcde", "a📻bYYcde", "a📻bXX📺XXcde" },
{ "a📻bcde", "a📻XbcYde", "a📻XXbcYYYde", "a📻bcYYde", "a📻XX📺XXbcde" } };
String compiledPattern = SimpleFormatterImpl
.compileToStringMinMaxArguments(pattern, new StringBuilder(), 1, 1);
Modifier mod = new SimpleModifier(compiledPattern, NumberFormat.Field.PERCENT, false);
- assertModifierEquals(mod, prefixLens[i], false, expectedCharFields[i][0], expectedCharFields[i][1]);
+ assertModifierEquals(mod,
+ prefixLens[i],
+ false,
+ expectedCharFields[i][0],
+ expectedCharFields[i][1]);
// Test strange insertion positions
for (int j = 0; j < outputs.length; j++) {
// Test custom patterns
// The following line means that the last char of the number should be a | (rather than a digit)
- symbols.setPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_SURROUNDING_MATCH, true, "[|]");
+ symbols.setPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_SURROUNDING_MATCH,
+ true,
+ "[|]");
suffix.append("XYZ", NumberFormat.Field.CURRENCY);
Modifier mod3 = new CurrencySpacingEnabledModifier(prefix, suffix, true, symbols);
assertModifierEquals(mod3, 3, true, "USD|\u00A0XYZ", "$$$nn$$$");
// If this test starts failing, please update the method #getUnicodeSet() in
// BOTH CurrencySpacingEnabledModifier.java AND in C++.
DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(new ULocale("en-US"));
- assertEquals(
- "[:^S:]",
- dfs.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_CURRENCY_MATCH, true));
- assertEquals(
- "[:^S:]",
- dfs.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_CURRENCY_MATCH, false));
- assertEquals(
- "[:digit:]",
- dfs.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_SURROUNDING_MATCH, true));
- assertEquals(
- "[:digit:]",
- dfs.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_SURROUNDING_MATCH, false));
+ assertEquals("[:^S:]",
+ dfs.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_CURRENCY_MATCH,
+ true));
+ assertEquals("[:^S:]",
+ dfs.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_CURRENCY_MATCH,
+ false));
+ assertEquals("[:digit:]",
+ dfs.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_SURROUNDING_MATCH,
+ true));
+ assertEquals("[:digit:]",
+ dfs.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_SURROUNDING_MATCH,
+ false));
}
private void assertModifierEquals(
String expectedFields) {
NumberStringBuilder sb = new NumberStringBuilder();
sb.appendCodePoint('|', null);
- assertModifierEquals(mod, sb, expectedPrefixLength, expectedStrong, expectedChars, expectedFields);
+ assertModifierEquals(mod,
+ sb,
+ expectedPrefixLength,
+ expectedStrong,
+ expectedChars,
+ expectedFields);
}
private void assertModifierEquals(
assertEquals("Strong on " + sb, expectedStrong, mod.isStrong());
if (!(mod instanceof CurrencySpacingEnabledModifier)) {
assertEquals("Code point count equals actual code point count",
- sb.codePointCount() - oldCount, mod.getCodePointCount());
+ sb.codePointCount() - oldCount,
+ mod.getCodePointCount());
}
- assertEquals("<NumberStringBuilder [" + expectedChars + "] [" + expectedFields + "]>", sb.toDebugString());
+ assertEquals("<NumberStringBuilder [" + expectedChars + "] [" + expectedFields + "]>",
+ sb.toDebugString());
}
}
MutablePatternModifier mod = new MutablePatternModifier(false);
mod.setPatternInfo(PatternStringParser.parseToPatternInfo("a0b"));
mod.setPatternAttributes(SignDisplay.AUTO, false);
- mod.setSymbols(
- DecimalFormatSymbols.getInstance(ULocale.ENGLISH),
+ mod.setSymbols(DecimalFormatSymbols.getInstance(ULocale.ENGLISH),
Currency.getInstance("USD"),
UnitWidth.SHORT,
null);
/** @author sffc */
public class NumberStringBuilderTest {
- private static final String[] EXAMPLE_STRINGS = {
- "",
- "xyz",
- "The quick brown fox jumps over the lazy dog",
- "😁",
- "mixed 😇 and ASCII",
- "with combining characters like 🇦🇧🇨🇩",
- "A very very very very very very very very very very long string to force heap"
- };
-
- @Test
- public void testInsertAppendCharSequence() {
-
- StringBuilder sb1 = new StringBuilder();
- NumberStringBuilder sb2 = new NumberStringBuilder();
- for (String str : EXAMPLE_STRINGS) {
- NumberStringBuilder sb3 = new NumberStringBuilder();
- sb1.append(str);
- sb2.append(str, null);
- sb3.append(str, null);
- assertCharSequenceEquals(sb1, sb2);
- assertCharSequenceEquals(sb3, str);
-
- StringBuilder sb4 = new StringBuilder();
- NumberStringBuilder sb5 = new NumberStringBuilder();
- sb4.append("😇");
- sb4.append(str);
- sb4.append("xx");
- sb5.append("😇xx", null);
- sb5.insert(2, str, null);
- assertCharSequenceEquals(sb4, sb5);
-
- int start = Math.min(1, str.length());
- int end = Math.min(10, str.length());
- sb4.insert(3, str, start, end);
- sb5.insert(3, str, start, end, null);
- assertCharSequenceEquals(sb4, sb5);
-
- sb4.append(str.toCharArray());
- sb5.append(str.toCharArray(), null);
- assertCharSequenceEquals(sb4, sb5);
-
- sb4.insert(4, str.toCharArray());
- sb5.insert(4, str.toCharArray(), null);
- assertCharSequenceEquals(sb4, sb5);
-
- sb4.append(sb4.toString());
- sb5.append(new NumberStringBuilder(sb5));
- assertCharSequenceEquals(sb4, sb5);
+ private static final String[] EXAMPLE_STRINGS = {
+ "",
+ "xyz",
+ "The quick brown fox jumps over the lazy dog",
+ "😁",
+ "mixed 😇 and ASCII",
+ "with combining characters like 🇦🇧🇨🇩",
+ "A very very very very very very very very very very long string to force heap" };
+
+ @Test
+ public void testInsertAppendCharSequence() {
+
+ StringBuilder sb1 = new StringBuilder();
+ NumberStringBuilder sb2 = new NumberStringBuilder();
+ for (String str : EXAMPLE_STRINGS) {
+ NumberStringBuilder sb3 = new NumberStringBuilder();
+ sb1.append(str);
+ sb2.append(str, null);
+ sb3.append(str, null);
+ assertCharSequenceEquals(sb1, sb2);
+ assertCharSequenceEquals(sb3, str);
+
+ StringBuilder sb4 = new StringBuilder();
+ NumberStringBuilder sb5 = new NumberStringBuilder();
+ sb4.append("😇");
+ sb4.append(str);
+ sb4.append("xx");
+ sb5.append("😇xx", null);
+ sb5.insert(2, str, null);
+ assertCharSequenceEquals(sb4, sb5);
+
+ int start = Math.min(1, str.length());
+ int end = Math.min(10, str.length());
+ sb4.insert(3, str, start, end);
+ sb5.insert(3, str, start, end, null);
+ assertCharSequenceEquals(sb4, sb5);
+
+ sb4.append(str.toCharArray());
+ sb5.append(str.toCharArray(), null);
+ assertCharSequenceEquals(sb4, sb5);
+
+ sb4.insert(4, str.toCharArray());
+ sb5.insert(4, str.toCharArray(), null);
+ assertCharSequenceEquals(sb4, sb5);
+
+ sb4.append(sb4.toString());
+ sb5.append(new NumberStringBuilder(sb5));
+ assertCharSequenceEquals(sb4, sb5);
+ }
}
- }
-
- @Test
- public void testInsertAppendCodePoint() {
- int[] cases = {0, 1, 60, 127, 128, 0x7fff, 0x8000, 0xffff, 0x10000, 0x1f000, 0x10ffff};
-
- StringBuilder sb1 = new StringBuilder();
- NumberStringBuilder sb2 = new NumberStringBuilder();
- for (int cas : cases) {
- NumberStringBuilder sb3 = new NumberStringBuilder();
- sb1.appendCodePoint(cas);
- sb2.appendCodePoint(cas, null);
- sb3.appendCodePoint(cas, null);
- assertCharSequenceEquals(sb1, sb2);
- assertEquals(Character.codePointAt(sb3, 0), cas);
-
- StringBuilder sb4 = new StringBuilder();
- NumberStringBuilder sb5 = new NumberStringBuilder();
- sb4.append("😇");
- sb4.appendCodePoint(cas); // Java StringBuilder has no insertCodePoint()
- sb4.append("xx");
- sb5.append("😇xx", null);
- sb5.insertCodePoint(2, cas, null);
- assertCharSequenceEquals(sb4, sb5);
+
+ @Test
+ public void testInsertAppendCodePoint() {
+ int[] cases = { 0, 1, 60, 127, 128, 0x7fff, 0x8000, 0xffff, 0x10000, 0x1f000, 0x10ffff };
+
+ StringBuilder sb1 = new StringBuilder();
+ NumberStringBuilder sb2 = new NumberStringBuilder();
+ for (int cas : cases) {
+ NumberStringBuilder sb3 = new NumberStringBuilder();
+ sb1.appendCodePoint(cas);
+ sb2.appendCodePoint(cas, null);
+ sb3.appendCodePoint(cas, null);
+ assertCharSequenceEquals(sb1, sb2);
+ assertEquals(Character.codePointAt(sb3, 0), cas);
+
+ StringBuilder sb4 = new StringBuilder();
+ NumberStringBuilder sb5 = new NumberStringBuilder();
+ sb4.append("😇");
+ sb4.appendCodePoint(cas); // Java StringBuilder has no insertCodePoint()
+ sb4.append("xx");
+ sb5.append("😇xx", null);
+ sb5.insertCodePoint(2, cas, null);
+ assertCharSequenceEquals(sb4, sb5);
+ }
}
- }
-
- @Test
- public void testCopy() {
- for (String str : EXAMPLE_STRINGS) {
- NumberStringBuilder sb1 = new NumberStringBuilder();
- sb1.append(str, null);
- NumberStringBuilder sb2 = new NumberStringBuilder(sb1);
- assertCharSequenceEquals(sb1, sb2);
- assertTrue(sb1.contentEquals(sb2));
-
- sb1.append("12345", null);
- assertNotEquals(sb1.length(), sb2.length());
- assertFalse(sb1.contentEquals(sb2));
+
+ @Test
+ public void testCopy() {
+ for (String str : EXAMPLE_STRINGS) {
+ NumberStringBuilder sb1 = new NumberStringBuilder();
+ sb1.append(str, null);
+ NumberStringBuilder sb2 = new NumberStringBuilder(sb1);
+ assertCharSequenceEquals(sb1, sb2);
+ assertTrue(sb1.contentEquals(sb2));
+
+ sb1.append("12345", null);
+ assertNotEquals(sb1.length(), sb2.length());
+ assertFalse(sb1.contentEquals(sb2));
+ }
}
- }
-
- @Test
- public void testFields() {
- for (String str : EXAMPLE_STRINGS) {
- NumberStringBuilder sb = new NumberStringBuilder();
- sb.append(str, null);
- sb.append(str, NumberFormat.Field.CURRENCY);
- Field[] fields = sb.toFieldArray();
- assertEquals(str.length() * 2, fields.length);
- for (int i = 0; i < str.length(); i++) {
- assertEquals(null, fields[i]);
- assertEquals(null, sb.fieldAt(i));
- assertEquals(NumberFormat.Field.CURRENCY, fields[i + str.length()]);
- assertEquals(NumberFormat.Field.CURRENCY, sb.fieldAt(i + str.length()));
- }
-
- // Very basic FieldPosition test. More robust tests happen in NumberFormatTest.
- // Let NumberFormatTest also take care of AttributedCharacterIterator material.
- FieldPosition fp = new FieldPosition(NumberFormat.Field.CURRENCY);
- sb.populateFieldPosition(fp, 0);
- assertEquals(str.length(), fp.getBeginIndex());
- assertEquals(str.length() * 2, fp.getEndIndex());
-
- if (str.length() > 0) {
- sb.insertCodePoint(2, 100, NumberFormat.Field.INTEGER);
- fields = sb.toFieldArray();
- assertEquals(str.length() * 2 + 1, fields.length);
- assertEquals(fields[2], NumberFormat.Field.INTEGER);
- }
-
- sb.append(new NumberStringBuilder(sb));
- sb.append(sb.toCharArray(), sb.toFieldArray());
- int numNull = 0;
- int numCurr = 0;
- int numInt = 0;
- Field[] oldFields = fields;
- fields = sb.toFieldArray();
- for (int i = 0; i < sb.length(); i++) {
- assertEquals(oldFields[i % oldFields.length], fields[i]);
- if (fields[i] == null) {
- numNull++;
- } else if (fields[i] == NumberFormat.Field.CURRENCY) {
- numCurr++;
- } else if (fields[i] == NumberFormat.Field.INTEGER) {
- numInt++;
- } else {
- throw new AssertionError("Encountered unknown field in " + str);
+
+ @Test
+ public void testFields() {
+ for (String str : EXAMPLE_STRINGS) {
+ NumberStringBuilder sb = new NumberStringBuilder();
+ sb.append(str, null);
+ sb.append(str, NumberFormat.Field.CURRENCY);
+ Field[] fields = sb.toFieldArray();
+ assertEquals(str.length() * 2, fields.length);
+ for (int i = 0; i < str.length(); i++) {
+ assertEquals(null, fields[i]);
+ assertEquals(null, sb.fieldAt(i));
+ assertEquals(NumberFormat.Field.CURRENCY, fields[i + str.length()]);
+ assertEquals(NumberFormat.Field.CURRENCY, sb.fieldAt(i + str.length()));
+ }
+
+ // Very basic FieldPosition test. More robust tests happen in NumberFormatTest.
+ // Let NumberFormatTest also take care of AttributedCharacterIterator material.
+ FieldPosition fp = new FieldPosition(NumberFormat.Field.CURRENCY);
+ sb.populateFieldPosition(fp, 0);
+ assertEquals(str.length(), fp.getBeginIndex());
+ assertEquals(str.length() * 2, fp.getEndIndex());
+
+ if (str.length() > 0) {
+ sb.insertCodePoint(2, 100, NumberFormat.Field.INTEGER);
+ fields = sb.toFieldArray();
+ assertEquals(str.length() * 2 + 1, fields.length);
+ assertEquals(fields[2], NumberFormat.Field.INTEGER);
+ }
+
+ sb.append(new NumberStringBuilder(sb));
+ sb.append(sb.toCharArray(), sb.toFieldArray());
+ int numNull = 0;
+ int numCurr = 0;
+ int numInt = 0;
+ Field[] oldFields = fields;
+ fields = sb.toFieldArray();
+ for (int i = 0; i < sb.length(); i++) {
+ assertEquals(oldFields[i % oldFields.length], fields[i]);
+ if (fields[i] == null) {
+ numNull++;
+ } else if (fields[i] == NumberFormat.Field.CURRENCY) {
+ numCurr++;
+ } else if (fields[i] == NumberFormat.Field.INTEGER) {
+ numInt++;
+ } else {
+ throw new AssertionError("Encountered unknown field in " + str);
+ }
+ }
+ assertEquals(str.length() * 4, numNull);
+ assertEquals(numNull, numCurr);
+ assertEquals(str.length() > 0 ? 4 : 0, numInt);
+
+ NumberStringBuilder sb2 = new NumberStringBuilder();
+ sb2.append(sb);
+ assertTrue(sb.contentEquals(sb2));
+ assertTrue(sb.contentEquals(sb2.toCharArray(), sb2.toFieldArray()));
+
+ sb2.insertCodePoint(0, 50, NumberFormat.Field.FRACTION);
+ assertTrue(!sb.contentEquals(sb2));
+ assertTrue(!sb.contentEquals(sb2.toCharArray(), sb2.toFieldArray()));
}
- }
- assertEquals(str.length() * 4, numNull);
- assertEquals(numNull, numCurr);
- assertEquals(str.length() > 0 ? 4 : 0, numInt);
-
- NumberStringBuilder sb2 = new NumberStringBuilder();
- sb2.append(sb);
- assertTrue(sb.contentEquals(sb2));
- assertTrue(sb.contentEquals(sb2.toCharArray(), sb2.toFieldArray()));
-
- sb2.insertCodePoint(0, 50, NumberFormat.Field.FRACTION);
- assertTrue(!sb.contentEquals(sb2));
- assertTrue(!sb.contentEquals(sb2.toCharArray(), sb2.toFieldArray()));
}
- }
-
- @Test
- public void testUnlimitedCapacity() {
- NumberStringBuilder builder = new NumberStringBuilder();
- // The builder should never fail upon repeated appends.
- for (int i = 0; i < 1000; i++) {
- assertEquals(builder.length(), i);
- builder.appendCodePoint('x', null);
- assertEquals(builder.length(), i + 1);
+
+ @Test
+ public void testUnlimitedCapacity() {
+ NumberStringBuilder builder = new NumberStringBuilder();
+ // The builder should never fail upon repeated appends.
+ for (int i = 0; i < 1000; i++) {
+ assertEquals(builder.length(), i);
+ builder.appendCodePoint('x', null);
+ assertEquals(builder.length(), i + 1);
+ }
}
- }
-
- @Test
- public void testCodePoints() {
- NumberStringBuilder nsb = new NumberStringBuilder();
- assertEquals("First is -1 on empty string", -1, nsb.getFirstCodePoint());
- assertEquals("Last is -1 on empty string", -1, nsb.getLastCodePoint());
- assertEquals("Length is 0 on empty string", 0, nsb.codePointCount());
-
- nsb.append("q", null);
- assertEquals("First is q", 'q', nsb.getFirstCodePoint());
- assertEquals("Last is q", 'q', nsb.getLastCodePoint());
- assertEquals("0th is q", 'q', nsb.codePointAt(0));
- assertEquals("Before 1st is q", 'q', nsb.codePointBefore(1));
- assertEquals("Code point count is 1", 1, nsb.codePointCount());
-
- // 🚀 is two char16s
- nsb.append("🚀", null);
- assertEquals("First is still q", 'q', nsb.getFirstCodePoint());
- assertEquals("Last is space ship", 128640, nsb.getLastCodePoint());
- assertEquals("1st is space ship", 128640, nsb.codePointAt(1));
- assertEquals("Before 1st is q", 'q', nsb.codePointBefore(1));
- assertEquals("Before 3rd is space ship", 128640, nsb.codePointBefore(3));
- assertEquals("Code point count is 2", 2, nsb.codePointCount());
- }
-
- private static void assertCharSequenceEquals(CharSequence a, CharSequence b) {
- assertEquals(a.toString(), b.toString());
-
- assertEquals(a.length(), b.length());
- for (int i = 0; i < a.length(); i++) {
- assertEquals(a.charAt(i), b.charAt(i));
+
+ @Test
+ public void testCodePoints() {
+ NumberStringBuilder nsb = new NumberStringBuilder();
+ assertEquals("First is -1 on empty string", -1, nsb.getFirstCodePoint());
+ assertEquals("Last is -1 on empty string", -1, nsb.getLastCodePoint());
+ assertEquals("Length is 0 on empty string", 0, nsb.codePointCount());
+
+ nsb.append("q", null);
+ assertEquals("First is q", 'q', nsb.getFirstCodePoint());
+ assertEquals("Last is q", 'q', nsb.getLastCodePoint());
+ assertEquals("0th is q", 'q', nsb.codePointAt(0));
+ assertEquals("Before 1st is q", 'q', nsb.codePointBefore(1));
+ assertEquals("Code point count is 1", 1, nsb.codePointCount());
+
+ // 🚀 is two char16s
+ nsb.append("🚀", null);
+ assertEquals("First is still q", 'q', nsb.getFirstCodePoint());
+ assertEquals("Last is space ship", 128640, nsb.getLastCodePoint());
+ assertEquals("1st is space ship", 128640, nsb.codePointAt(1));
+ assertEquals("Before 1st is q", 'q', nsb.codePointBefore(1));
+ assertEquals("Before 3rd is space ship", 128640, nsb.codePointBefore(3));
+ assertEquals("Code point count is 2", 2, nsb.codePointCount());
}
- int start = Math.min(2, a.length());
- int end = Math.min(12, a.length());
- if (start != end) {
- assertCharSequenceEquals(a.subSequence(start, end), b.subSequence(start, end));
+ private static void assertCharSequenceEquals(CharSequence a, CharSequence b) {
+ assertEquals(a.toString(), b.toString());
+
+ assertEquals(a.length(), b.length());
+ for (int i = 0; i < a.length(); i++) {
+ assertEquals(a.charAt(i), b.charAt(i));
+ }
+
+ int start = Math.min(2, a.length());
+ int end = Math.min(12, a.length());
+ if (start != end) {
+ assertCharSequenceEquals(a.subSequence(start, end), b.subSequence(start, end));
+ }
}
- }
}
/** @author sffc */
public class PatternStringTest {
- @Test
- public void testLocalized() {
- DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
- symbols.setDecimalSeparatorString("a");
- symbols.setPercentString("b");
- symbols.setMinusSignString(".");
- symbols.setPlusSignString("'");
-
- String standard = "+-abcb''a''#,##0.0%'a%'";
- String localized = "’.'ab'c'b''a'''#,##0a0b'a%'";
- String toStandard = "+-'ab'c'b''a'''#,##0.0%'a%'";
-
- assertEquals(localized, PatternStringUtils.convertLocalized(standard, symbols, true));
- assertEquals(toStandard, PatternStringUtils.convertLocalized(localized, symbols, false));
- }
-
- @Test
- public void testToPatternSimple() {
- String[][] cases = {
- {"#", "0"},
- {"0", "0"},
- {"#0", "0"},
- {"###", "0"},
- {"0.##", "0.##"},
- {"0.00", "0.00"},
- {"0.00#", "0.00#"},
- {"#E0", "#E0"},
- {"0E0", "0E0"},
- {"#00E00", "#00E00"},
- {"#,##0", "#,##0"},
- {"#;#", "0;0"},
- {"#;-#", "0"}, // ignore a negative prefix pattern of '-' since that is the default
- {"**##0", "**##0"},
- {"*'x'##0", "*x##0"},
- {"a''b0", "a''b0"},
- {"*''##0", "*''##0"},
- {"*📺##0", "*'📺'##0"},
- {"*'நி'##0", "*'நி'##0"},
- };
-
- for (String[] cas : cases) {
- String input = cas[0];
- String output = cas[1];
-
- DecimalFormatProperties properties = PatternStringParser.parseToProperties(input);
- String actual = PatternStringUtils.propertiesToPatternString(properties);
- assertEquals(
- "Failed on input pattern '" + input + "', properties " + properties, output, actual);
+ @Test
+ public void testLocalized() {
+ DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
+ symbols.setDecimalSeparatorString("a");
+ symbols.setPercentString("b");
+ symbols.setMinusSignString(".");
+ symbols.setPlusSignString("'");
+
+ String standard = "+-abcb''a''#,##0.0%'a%'";
+ String localized = "’.'ab'c'b''a'''#,##0a0b'a%'";
+ String toStandard = "+-'ab'c'b''a'''#,##0.0%'a%'";
+
+ assertEquals(localized, PatternStringUtils.convertLocalized(standard, symbols, true));
+ assertEquals(toStandard, PatternStringUtils.convertLocalized(localized, symbols, false));
}
- }
-
- @Test
- public void testToPatternWithProperties() {
- Object[][] cases = {
- {new DecimalFormatProperties().setPositivePrefix("abc"), "abc#"},
- {new DecimalFormatProperties().setPositiveSuffix("abc"), "#abc"},
- {new DecimalFormatProperties().setPositivePrefixPattern("abc"), "abc#"},
- {new DecimalFormatProperties().setPositiveSuffixPattern("abc"), "#abc"},
- {new DecimalFormatProperties().setNegativePrefix("abc"), "#;abc#"},
- {new DecimalFormatProperties().setNegativeSuffix("abc"), "#;#abc"},
- {new DecimalFormatProperties().setNegativePrefixPattern("abc"), "#;abc#"},
- {new DecimalFormatProperties().setNegativeSuffixPattern("abc"), "#;#abc"},
- {new DecimalFormatProperties().setPositivePrefix("+"), "'+'#"},
- {new DecimalFormatProperties().setPositivePrefixPattern("+"), "+#"},
- {new DecimalFormatProperties().setPositivePrefix("+'"), "'+'''#"},
- {new DecimalFormatProperties().setPositivePrefix("'+"), "'''+'#"},
- {new DecimalFormatProperties().setPositivePrefix("'"), "''#"},
- {new DecimalFormatProperties().setPositivePrefixPattern("+''"), "+''#"},
- };
-
- for (Object[] cas : cases) {
- DecimalFormatProperties input = (DecimalFormatProperties) cas[0];
- String output = (String) cas[1];
-
- String actual = PatternStringUtils.propertiesToPatternString(input);
- assertEquals("Failed on input properties " + input, output, actual);
+
+ @Test
+ public void testToPatternSimple() {
+ String[][] cases = {
+ { "#", "0" },
+ { "0", "0" },
+ { "#0", "0" },
+ { "###", "0" },
+ { "0.##", "0.##" },
+ { "0.00", "0.00" },
+ { "0.00#", "0.00#" },
+ { "#E0", "#E0" },
+ { "0E0", "0E0" },
+ { "#00E00", "#00E00" },
+ { "#,##0", "#,##0" },
+ { "#;#", "0;0" },
+ { "#;-#", "0" }, // ignore a negative prefix pattern of '-' since that is the default
+ { "**##0", "**##0" },
+ { "*'x'##0", "*x##0" },
+ { "a''b0", "a''b0" },
+ { "*''##0", "*''##0" },
+ { "*📺##0", "*'📺'##0" },
+ { "*'நி'##0", "*'நி'##0" }, };
+
+ for (String[] cas : cases) {
+ String input = cas[0];
+ String output = cas[1];
+
+ DecimalFormatProperties properties = PatternStringParser.parseToProperties(input);
+ String actual = PatternStringUtils.propertiesToPatternString(properties);
+ assertEquals("Failed on input pattern '" + input + "', properties " + properties,
+ output,
+ actual);
+ }
+ }
+
+ @Test
+ public void testToPatternWithProperties() {
+ Object[][] cases = {
+ { new DecimalFormatProperties().setPositivePrefix("abc"), "abc#" },
+ { new DecimalFormatProperties().setPositiveSuffix("abc"), "#abc" },
+ { new DecimalFormatProperties().setPositivePrefixPattern("abc"), "abc#" },
+ { new DecimalFormatProperties().setPositiveSuffixPattern("abc"), "#abc" },
+ { new DecimalFormatProperties().setNegativePrefix("abc"), "#;abc#" },
+ { new DecimalFormatProperties().setNegativeSuffix("abc"), "#;#abc" },
+ { new DecimalFormatProperties().setNegativePrefixPattern("abc"), "#;abc#" },
+ { new DecimalFormatProperties().setNegativeSuffixPattern("abc"), "#;#abc" },
+ { new DecimalFormatProperties().setPositivePrefix("+"), "'+'#" },
+ { new DecimalFormatProperties().setPositivePrefixPattern("+"), "+#" },
+ { new DecimalFormatProperties().setPositivePrefix("+'"), "'+'''#" },
+ { new DecimalFormatProperties().setPositivePrefix("'+"), "'''+'#" },
+ { new DecimalFormatProperties().setPositivePrefix("'"), "''#" },
+ { new DecimalFormatProperties().setPositivePrefixPattern("+''"), "+''#" }, };
+
+ for (Object[] cas : cases) {
+ DecimalFormatProperties input = (DecimalFormatProperties) cas[0];
+ String output = (String) cas[1];
+
+ String actual = PatternStringUtils.propertiesToPatternString(input);
+ assertEquals("Failed on input properties " + input, output, actual);
+ }
}
- }
-
- @Test
- public void testExceptionOnInvalid() {
- String[] invalidPatterns = {
- "#.#.#", "0#", "0#.", ".#0", "0#.#0", "@0", "0@", "0,", "0,,", "0,,0", "0,,0,", "#,##0E0"
- };
-
- for (String pattern : invalidPatterns) {
- try {
- PatternStringParser.parseToProperties(pattern);
- fail("Didn't throw IllegalArgumentException when parsing pattern: " + pattern);
- } catch (IllegalArgumentException e) {
- }
+
+ @Test
+ public void testExceptionOnInvalid() {
+ String[] invalidPatterns = {
+ "#.#.#",
+ "0#",
+ "0#.",
+ ".#0",
+ "0#.#0",
+ "@0",
+ "0@",
+ "0,",
+ "0,,",
+ "0,,0",
+ "0,,0,",
+ "#,##0E0" };
+
+ for (String pattern : invalidPatterns) {
+ try {
+ PatternStringParser.parseToProperties(pattern);
+ fail("Didn't throw IllegalArgumentException when parsing pattern: " + pattern);
+ } catch (IllegalArgumentException e) {
+ }
+ }
+ }
+
+ @Test
+ public void testBug13117() {
+ DecimalFormatProperties expected = PatternStringParser.parseToProperties("0");
+ DecimalFormatProperties actual = PatternStringParser.parseToProperties("0;");
+ assertEquals("Should not consume negative subpattern", expected, actual);
}
- }
-
- @Test
- public void testBug13117() {
- DecimalFormatProperties expected = PatternStringParser.parseToProperties("0");
- DecimalFormatProperties actual = PatternStringParser.parseToProperties("0;");
- assertEquals("Should not consume negative subpattern", expected, actual);
- }
}
public class PropertiesTest {
- @Test
- public void testBasicEquals() {
- DecimalFormatProperties p1 = new DecimalFormatProperties();
- DecimalFormatProperties p2 = new DecimalFormatProperties();
- assertEquals(p1, p2);
-
- p1.setPositivePrefix("abc");
- assertNotEquals(p1, p2);
- p2.setPositivePrefix("xyz");
- assertNotEquals(p1, p2);
- p1.setPositivePrefix("xyz");
- assertEquals(p1, p2);
- }
-
- @Test
- public void testFieldCoverage() {
- DecimalFormatProperties p0 = new DecimalFormatProperties();
- DecimalFormatProperties p1 = new DecimalFormatProperties();
- DecimalFormatProperties p2 = new DecimalFormatProperties();
- DecimalFormatProperties p3 = new DecimalFormatProperties();
- DecimalFormatProperties p4 = new DecimalFormatProperties();
-
- Set<Integer> hashCodes = new HashSet<Integer>();
- Field[] fields = DecimalFormatProperties.class.getDeclaredFields();
- for (Field field : fields) {
- if (Modifier.isStatic(field.getModifiers())) {
- continue;
- }
-
- // Check for getters and setters
- String fieldNamePascalCase =
- Character.toUpperCase(field.getName().charAt(0)) + field.getName().substring(1);
- String getterName = "get" + fieldNamePascalCase;
- String setterName = "set" + fieldNamePascalCase;
- Method getter, setter;
- try {
- getter = DecimalFormatProperties.class.getMethod(getterName);
- assertEquals(
- "Getter does not return correct type", field.getType(), getter.getReturnType());
- } catch (NoSuchMethodException e) {
- fail("Could not find method " + getterName + " for field " + field);
- continue;
- } catch (SecurityException e) {
- fail("Could not access method " + getterName + " for field " + field);
- continue;
- }
- try {
- setter = DecimalFormatProperties.class.getMethod(setterName, field.getType());
- assertEquals(
- "Method " + setterName + " does not return correct type",
- DecimalFormatProperties.class,
- setter.getReturnType());
- } catch (NoSuchMethodException e) {
- fail("Could not find method " + setterName + " for field " + field);
- continue;
- } catch (SecurityException e) {
- fail("Could not access method " + setterName + " for field " + field);
- continue;
- }
-
- // Check for parameter name equality.
- // The parameter name is not always available, depending on compiler settings.
- // TODO: Enable in Java 8
- /*
- Parameter param = setter.getParameters()[0];
- if (!param.getName().subSequence(0, 3).equals("arg")) {
- assertEquals("Parameter name should equal field name", field.getName(), param.getName());
- }
- */
-
- try {
- // Check for default value (should be null for objects)
- if (field.getType() != Integer.TYPE && field.getType() != Boolean.TYPE) {
- Object default0 = getter.invoke(p0);
- assertEquals("Field " + field + " has non-null default value:", null, default0);
- }
-
- // Check for getter, equals, and hash code behavior
- Object val0 = getSampleValueForType(field.getType(), 0);
- Object val1 = getSampleValueForType(field.getType(), 1);
- Object val2 = getSampleValueForType(field.getType(), 2);
- assertNotEquals(val0, val1);
- setter.invoke(p1, val0);
- setter.invoke(p2, val0);
- assertEquals(p1, p2);
- assertEquals(p1.hashCode(), p2.hashCode());
- assertEquals(getter.invoke(p1), getter.invoke(p2));
- assertEquals(getter.invoke(p1), val0);
- assertNotEquals(getter.invoke(p1), val1);
- hashCodes.add(p1.hashCode());
- setter.invoke(p1, val1);
- assertNotEquals("Field " + field + " is missing from equals()", p1, p2);
- assertNotEquals(getter.invoke(p1), getter.invoke(p2));
- assertNotEquals(getter.invoke(p1), val0);
- assertEquals(getter.invoke(p1), val1);
- setter.invoke(p1, val0);
- assertEquals("Field " + field + " setter might have side effects", p1, p2);
- assertEquals(p1.hashCode(), p2.hashCode());
- assertEquals(getter.invoke(p1), getter.invoke(p2));
- setter.invoke(p1, val1);
- setter.invoke(p2, val1);
+ @Test
+ public void testBasicEquals() {
+ DecimalFormatProperties p1 = new DecimalFormatProperties();
+ DecimalFormatProperties p2 = new DecimalFormatProperties();
assertEquals(p1, p2);
- assertEquals(p1.hashCode(), p2.hashCode());
- assertEquals(getter.invoke(p1), getter.invoke(p2));
- setter.invoke(p1, val2);
- setter.invoke(p1, val1);
- assertEquals("Field " + field + " setter might have side effects", p1, p2);
- assertEquals(p1.hashCode(), p2.hashCode());
- assertEquals(getter.invoke(p1), getter.invoke(p2));
- hashCodes.add(p1.hashCode());
-
- // Check for clone behavior
- DecimalFormatProperties copy = p1.clone();
- assertEquals("Field " + field + " did not get copied in clone", p1, copy);
- assertEquals(p1.hashCode(), copy.hashCode());
- assertEquals(getter.invoke(p1), getter.invoke(copy));
-
- // Check for copyFrom behavior
- setter.invoke(p1, val0);
+
+ p1.setPositivePrefix("abc");
+ assertNotEquals(p1, p2);
+ p2.setPositivePrefix("xyz");
assertNotEquals(p1, p2);
- assertNotEquals(getter.invoke(p1), getter.invoke(p2));
- p2.copyFrom(p1);
- assertEquals("Field " + field + " is missing from copyFrom()", p1, p2);
- assertEquals(p1.hashCode(), p2.hashCode());
- assertEquals(getter.invoke(p1), getter.invoke(p2));
-
- // Load values into p3 and p4 for clear() behavior test
- setter.invoke(p3, getSampleValueForType(field.getType(), 3));
- hashCodes.add(p3.hashCode());
- setter.invoke(p4, getSampleValueForType(field.getType(), 4));
- hashCodes.add(p4.hashCode());
- } catch (IllegalAccessException e) {
- fail("Could not access method for field " + field);
- } catch (IllegalArgumentException e) {
- fail("Could call method for field " + field);
- } catch (InvocationTargetException e) {
- fail("Could invoke method on target for field " + field);
- }
+ p1.setPositivePrefix("xyz");
+ assertEquals(p1, p2);
}
- // Check for clear() behavior
- assertNotEquals(p3, p4);
- p3.clear();
- p4.clear();
- assertEquals("A field is missing from the clear() function", p3, p4);
-
- // A good hashCode() implementation should produce very few collisions. We added at most
- // 4*fields.length codes to the set. We'll say the implementation is good if we had at least
- // fields.length unique values.
- // TODO: Should the requirement be stronger than this?
- assertTrue(
- "Too many hash code collisions: " + hashCodes.size() + " out of " + (fields.length * 4),
- hashCodes.size() >= fields.length);
- }
-
- /**
- * Creates a valid sample instance of the given type. Used to simulate getters and setters.
- *
- * @param type The type to generate.
- * @param seed An integer seed, guaranteed to be positive. The same seed should generate two
- * instances that are equal. A different seed should in general generate two instances that
- * are not equal; this might not always be possible, such as with booleans or enums where
- * there are limited possible values.
- * @return An instance of the specified type.
- */
- Object getSampleValueForType(Class<?> type, int seed) {
- if (type == Integer.TYPE) {
- return seed * 1000001;
-
- } else if (type == Boolean.TYPE) {
- return (seed % 2) == 0;
-
- } else if (type == BigDecimal.class) {
- if (seed == 0) return null;
- return new BigDecimal(seed * 1000002);
-
- } else if (type == String.class) {
- if (seed == 0) return null;
- return BigInteger.valueOf(seed * 1000003).toString(32);
-
- } else if (type == CompactStyle.class) {
- if (seed == 0) return null;
- CompactStyle[] values = CompactStyle.values();
- return values[seed % values.length];
-
- } else if (type == Currency.class) {
- if (seed == 0) return null;
- Object[] currencies = Currency.getAvailableCurrencies().toArray();
- return currencies[seed % currencies.length];
-
- } else if (type == CurrencyPluralInfo.class) {
- if (seed == 0) return null;
- ULocale[] locales = ULocale.getAvailableLocales();
- return CurrencyPluralInfo.getInstance(locales[seed % locales.length]);
-
- } else if (type == CurrencyUsage.class) {
- if (seed == 0) return null;
- CurrencyUsage[] values = CurrencyUsage.values();
- return values[seed % values.length];
-
- } else if (type == GroupingMode.class) {
- if (seed == 0) return null;
- GroupingMode[] values = GroupingMode.values();
- return values[seed % values.length];
-
- } else if (type == FormatWidth.class) {
- if (seed == 0) return null;
- FormatWidth[] values = FormatWidth.values();
- return values[seed % values.length];
-
- } else if (type == Map.class) {
- // Map<String,Map<String,String>> for compactCustomData property
- if (seed == 0) return null;
- Map<String, Map<String, String>> outer = new HashMap<String, Map<String, String>>();
- Map<String, String> inner = new HashMap<String, String>();
- inner.put("one", "0 thousand");
- StringBuilder magnitudeKey = new StringBuilder();
- magnitudeKey.append("1000");
- for (int i = 0; i < seed % 9; i++) {
- magnitudeKey.append("0");
- }
- outer.put(magnitudeKey.toString(), inner);
- return outer;
-
- } else if (type == MathContext.class) {
- if (seed == 0) return null;
- RoundingMode[] modes = RoundingMode.values();
- return new MathContext(seed, modes[seed % modes.length]);
-
- } else if (type == MeasureUnit.class) {
- if (seed == 0) return null;
- Object[] units = MeasureUnit.getAvailable().toArray();
- return units[seed % units.length];
-
- } else if (type == PadPosition.class) {
- if (seed == 0) return null;
- PadPosition[] values = PadPosition.values();
- return values[seed % values.length];
-
- } else if (type == ParseMode.class) {
- if (seed == 0) return null;
- ParseMode[] values = ParseMode.values();
- return values[seed % values.length];
-
- } else if (type == PluralRules.class) {
- if (seed == 0) return null;
- ULocale[] locales = PluralRules.getAvailableULocales();
- return PluralRules.forLocale(locales[seed % locales.length]);
-
- } else if (type == RoundingMode.class) {
- if (seed == 0) return null;
- RoundingMode[] values = RoundingMode.values();
- return values[seed % values.length];
-
- } else {
- fail("Don't know how to handle type " + type + ". Please add it to getSampleValueForType().");
- return null;
+ @Test
+ public void testFieldCoverage() {
+ DecimalFormatProperties p0 = new DecimalFormatProperties();
+ DecimalFormatProperties p1 = new DecimalFormatProperties();
+ DecimalFormatProperties p2 = new DecimalFormatProperties();
+ DecimalFormatProperties p3 = new DecimalFormatProperties();
+ DecimalFormatProperties p4 = new DecimalFormatProperties();
+
+ Set<Integer> hashCodes = new HashSet<Integer>();
+ Field[] fields = DecimalFormatProperties.class.getDeclaredFields();
+ for (Field field : fields) {
+ if (Modifier.isStatic(field.getModifiers())) {
+ continue;
+ }
+
+ // Check for getters and setters
+ String fieldNamePascalCase = Character.toUpperCase(field.getName().charAt(0))
+ + field.getName().substring(1);
+ String getterName = "get" + fieldNamePascalCase;
+ String setterName = "set" + fieldNamePascalCase;
+ Method getter, setter;
+ try {
+ getter = DecimalFormatProperties.class.getMethod(getterName);
+ assertEquals("Getter does not return correct type",
+ field.getType(),
+ getter.getReturnType());
+ } catch (NoSuchMethodException e) {
+ fail("Could not find method " + getterName + " for field " + field);
+ continue;
+ } catch (SecurityException e) {
+ fail("Could not access method " + getterName + " for field " + field);
+ continue;
+ }
+ try {
+ setter = DecimalFormatProperties.class.getMethod(setterName, field.getType());
+ assertEquals("Method " + setterName + " does not return correct type",
+ DecimalFormatProperties.class,
+ setter.getReturnType());
+ } catch (NoSuchMethodException e) {
+ fail("Could not find method " + setterName + " for field " + field);
+ continue;
+ } catch (SecurityException e) {
+ fail("Could not access method " + setterName + " for field " + field);
+ continue;
+ }
+
+ // Check for parameter name equality.
+ // The parameter name is not always available, depending on compiler settings.
+ // TODO: Enable in Java 8
+ /*
+ * Parameter param = setter.getParameters()[0]; if (!param.getName().subSequence(0,
+ * 3).equals("arg")) { assertEquals("Parameter name should equal field name",
+ * field.getName(), param.getName()); }
+ */
+
+ try {
+ // Check for default value (should be null for objects)
+ if (field.getType() != Integer.TYPE && field.getType() != Boolean.TYPE) {
+ Object default0 = getter.invoke(p0);
+ assertEquals("Field " + field + " has non-null default value:", null, default0);
+ }
+
+ // Check for getter, equals, and hash code behavior
+ Object val0 = getSampleValueForType(field.getType(), 0);
+ Object val1 = getSampleValueForType(field.getType(), 1);
+ Object val2 = getSampleValueForType(field.getType(), 2);
+ assertNotEquals(val0, val1);
+ setter.invoke(p1, val0);
+ setter.invoke(p2, val0);
+ assertEquals(p1, p2);
+ assertEquals(p1.hashCode(), p2.hashCode());
+ assertEquals(getter.invoke(p1), getter.invoke(p2));
+ assertEquals(getter.invoke(p1), val0);
+ assertNotEquals(getter.invoke(p1), val1);
+ hashCodes.add(p1.hashCode());
+ setter.invoke(p1, val1);
+ assertNotEquals("Field " + field + " is missing from equals()", p1, p2);
+ assertNotEquals(getter.invoke(p1), getter.invoke(p2));
+ assertNotEquals(getter.invoke(p1), val0);
+ assertEquals(getter.invoke(p1), val1);
+ setter.invoke(p1, val0);
+ assertEquals("Field " + field + " setter might have side effects", p1, p2);
+ assertEquals(p1.hashCode(), p2.hashCode());
+ assertEquals(getter.invoke(p1), getter.invoke(p2));
+ setter.invoke(p1, val1);
+ setter.invoke(p2, val1);
+ assertEquals(p1, p2);
+ assertEquals(p1.hashCode(), p2.hashCode());
+ assertEquals(getter.invoke(p1), getter.invoke(p2));
+ setter.invoke(p1, val2);
+ setter.invoke(p1, val1);
+ assertEquals("Field " + field + " setter might have side effects", p1, p2);
+ assertEquals(p1.hashCode(), p2.hashCode());
+ assertEquals(getter.invoke(p1), getter.invoke(p2));
+ hashCodes.add(p1.hashCode());
+
+ // Check for clone behavior
+ DecimalFormatProperties copy = p1.clone();
+ assertEquals("Field " + field + " did not get copied in clone", p1, copy);
+ assertEquals(p1.hashCode(), copy.hashCode());
+ assertEquals(getter.invoke(p1), getter.invoke(copy));
+
+ // Check for copyFrom behavior
+ setter.invoke(p1, val0);
+ assertNotEquals(p1, p2);
+ assertNotEquals(getter.invoke(p1), getter.invoke(p2));
+ p2.copyFrom(p1);
+ assertEquals("Field " + field + " is missing from copyFrom()", p1, p2);
+ assertEquals(p1.hashCode(), p2.hashCode());
+ assertEquals(getter.invoke(p1), getter.invoke(p2));
+
+ // Load values into p3 and p4 for clear() behavior test
+ setter.invoke(p3, getSampleValueForType(field.getType(), 3));
+ hashCodes.add(p3.hashCode());
+ setter.invoke(p4, getSampleValueForType(field.getType(), 4));
+ hashCodes.add(p4.hashCode());
+ } catch (IllegalAccessException e) {
+ fail("Could not access method for field " + field);
+ } catch (IllegalArgumentException e) {
+ fail("Could call method for field " + field);
+ } catch (InvocationTargetException e) {
+ fail("Could invoke method on target for field " + field);
+ }
+ }
+
+ // Check for clear() behavior
+ assertNotEquals(p3, p4);
+ p3.clear();
+ p4.clear();
+ assertEquals("A field is missing from the clear() function", p3, p4);
+
+ // A good hashCode() implementation should produce very few collisions. We added at most
+ // 4*fields.length codes to the set. We'll say the implementation is good if we had at least
+ // fields.length unique values.
+ // TODO: Should the requirement be stronger than this?
+ assertTrue(
+ "Too many hash code collisions: " + hashCodes.size() + " out of " + (fields.length * 4),
+ hashCodes.size() >= fields.length);
}
- }
-
- @Test
- public void TestBasicSerializationRoundTrip() throws IOException, ClassNotFoundException {
- DecimalFormatProperties props0 = new DecimalFormatProperties();
-
- // Write values to some of the fields
- PatternStringParser.parseToExistingProperties("A-**####,#00.00#b¤", props0);
-
- // Write to byte stream
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- ObjectOutputStream oos = new ObjectOutputStream(baos);
- oos.writeObject(props0);
- oos.flush();
- baos.close();
- byte[] bytes = baos.toByteArray();
-
- // Read from byte stream
- ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
- Object obj = ois.readObject();
- ois.close();
- DecimalFormatProperties props1 = (DecimalFormatProperties) obj;
-
- // Test equality
- assertEquals("Did not round-trip through serialization", props0, props1);
- }
-
- /** Handler for serialization compatibility test suite. */
- public static class PropertiesHandler implements SerializableTestUtility.Handler {
-
- @Override
- public Object[] getTestObjects() {
- return new Object[] {
- new DecimalFormatProperties(),
- PatternStringParser.parseToProperties("x#,##0.00%"),
- new DecimalFormatProperties().setCompactStyle(CompactStyle.LONG).setMinimumExponentDigits(2)
- };
+
+ /**
+ * Creates a valid sample instance of the given type. Used to simulate getters and setters.
+ *
+ * @param type
+ * The type to generate.
+ * @param seed
+ * An integer seed, guaranteed to be positive. The same seed should generate two instances
+ * that are equal. A different seed should in general generate two instances that are not
+ * equal; this might not always be possible, such as with booleans or enums where there
+ * are limited possible values.
+ * @return An instance of the specified type.
+ */
+ Object getSampleValueForType(Class<?> type, int seed) {
+ if (type == Integer.TYPE) {
+ return seed * 1000001;
+
+ } else if (type == Boolean.TYPE) {
+ return (seed % 2) == 0;
+
+ } else if (type == BigDecimal.class) {
+ if (seed == 0)
+ return null;
+ return new BigDecimal(seed * 1000002);
+
+ } else if (type == String.class) {
+ if (seed == 0)
+ return null;
+ return BigInteger.valueOf(seed * 1000003).toString(32);
+
+ } else if (type == CompactStyle.class) {
+ if (seed == 0)
+ return null;
+ CompactStyle[] values = CompactStyle.values();
+ return values[seed % values.length];
+
+ } else if (type == Currency.class) {
+ if (seed == 0)
+ return null;
+ Object[] currencies = Currency.getAvailableCurrencies().toArray();
+ return currencies[seed % currencies.length];
+
+ } else if (type == CurrencyPluralInfo.class) {
+ if (seed == 0)
+ return null;
+ ULocale[] locales = ULocale.getAvailableLocales();
+ return CurrencyPluralInfo.getInstance(locales[seed % locales.length]);
+
+ } else if (type == CurrencyUsage.class) {
+ if (seed == 0)
+ return null;
+ CurrencyUsage[] values = CurrencyUsage.values();
+ return values[seed % values.length];
+
+ } else if (type == GroupingMode.class) {
+ if (seed == 0)
+ return null;
+ GroupingMode[] values = GroupingMode.values();
+ return values[seed % values.length];
+
+ } else if (type == FormatWidth.class) {
+ if (seed == 0)
+ return null;
+ FormatWidth[] values = FormatWidth.values();
+ return values[seed % values.length];
+
+ } else if (type == Map.class) {
+ // Map<String,Map<String,String>> for compactCustomData property
+ if (seed == 0)
+ return null;
+ Map<String, Map<String, String>> outer = new HashMap<String, Map<String, String>>();
+ Map<String, String> inner = new HashMap<String, String>();
+ inner.put("one", "0 thousand");
+ StringBuilder magnitudeKey = new StringBuilder();
+ magnitudeKey.append("1000");
+ for (int i = 0; i < seed % 9; i++) {
+ magnitudeKey.append("0");
+ }
+ outer.put(magnitudeKey.toString(), inner);
+ return outer;
+
+ } else if (type == MathContext.class) {
+ if (seed == 0)
+ return null;
+ RoundingMode[] modes = RoundingMode.values();
+ return new MathContext(seed, modes[seed % modes.length]);
+
+ } else if (type == MeasureUnit.class) {
+ if (seed == 0)
+ return null;
+ Object[] units = MeasureUnit.getAvailable().toArray();
+ return units[seed % units.length];
+
+ } else if (type == PadPosition.class) {
+ if (seed == 0)
+ return null;
+ PadPosition[] values = PadPosition.values();
+ return values[seed % values.length];
+
+ } else if (type == ParseMode.class) {
+ if (seed == 0)
+ return null;
+ ParseMode[] values = ParseMode.values();
+ return values[seed % values.length];
+
+ } else if (type == PluralRules.class) {
+ if (seed == 0)
+ return null;
+ ULocale[] locales = PluralRules.getAvailableULocales();
+ return PluralRules.forLocale(locales[seed % locales.length]);
+
+ } else if (type == RoundingMode.class) {
+ if (seed == 0)
+ return null;
+ RoundingMode[] values = RoundingMode.values();
+ return values[seed % values.length];
+
+ } else {
+ fail("Don't know how to handle type "
+ + type
+ + ". Please add it to getSampleValueForType().");
+ return null;
+ }
}
- @Override
- public boolean hasSameBehavior(Object a, Object b) {
- return a.equals(b);
+ @Test
+ public void TestBasicSerializationRoundTrip() throws IOException, ClassNotFoundException {
+ DecimalFormatProperties props0 = new DecimalFormatProperties();
+
+ // Write values to some of the fields
+ PatternStringParser.parseToExistingProperties("A-**####,#00.00#b¤", props0);
+
+ // Write to byte stream
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(baos);
+ oos.writeObject(props0);
+ oos.flush();
+ baos.close();
+ byte[] bytes = baos.toByteArray();
+
+ // Read from byte stream
+ ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
+ Object obj = ois.readObject();
+ ois.close();
+ DecimalFormatProperties props1 = (DecimalFormatProperties) obj;
+
+ // Test equality
+ assertEquals("Did not round-trip through serialization", props0, props1);
}
- }
- /** Handler for the ICU 59 class named "Properties" before it was renamed to "DecimalFormatProperties". */
- public static class ICU59PropertiesHandler implements SerializableTestUtility.Handler {
+ /** Handler for serialization compatibility test suite. */
+ public static class PropertiesHandler implements SerializableTestUtility.Handler {
+
+ @Override
+ public Object[] getTestObjects() {
+ return new Object[] {
+ new DecimalFormatProperties(),
+ PatternStringParser.parseToProperties("x#,##0.00%"),
+ new DecimalFormatProperties().setCompactStyle(CompactStyle.LONG)
+ .setMinimumExponentDigits(2) };
+ }
- @Override
- public Object[] getTestObjects() {
- return new Object[] {
- new com.ibm.icu.impl.number.Properties()
- };
+ @Override
+ public boolean hasSameBehavior(Object a, Object b) {
+ return a.equals(b);
+ }
}
- @Override
- public boolean hasSameBehavior(Object a, Object b) {
- return true;
+ /**
+ * Handler for the ICU 59 class named "Properties" before it was renamed to
+ * "DecimalFormatProperties".
+ */
+ public static class ICU59PropertiesHandler implements SerializableTestUtility.Handler {
+
+ @Override
+ public Object[] getTestObjects() {
+ return new Object[] { new com.ibm.icu.impl.number.Properties() };
+ }
+
+ @Override
+ public boolean hasSameBehavior(Object a, Object b) {
+ return true;
+ }
}
- }
}