From: Shane Carr Date: Sat, 23 Dec 2017 01:07:53 +0000 (+0000) Subject: ICU-13524 Reformatting ICU4J number files with 4 spaces. X-Git-Tag: release-61-rc~153 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=56574d10420a295006c2f2b0e5880f1395af5b83;p=icu ICU-13524 Reformatting ICU4J number files with 4 spaces. X-SVN-Rev: 40749 --- diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/AffixPatternProvider.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/AffixPatternProvider.java index 0cf547e341e..02cda7dd551 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/AffixPatternProvider.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/AffixPatternProvider.java @@ -3,24 +3,24 @@ 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); } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/AffixUtils.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/AffixUtils.java index bf324514688..2cf6d06b74e 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/AffixUtils.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/AffixUtils.java @@ -9,608 +9,651 @@ import com.ibm.icu.text.NumberFormat; * format pattern. For example: * * - * - * - * - * - * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * *
Affix PatternExample Unescaped (Formatted) String
abcabc
ab-ab−
ab'-'ab-
ab''ab'
Affix PatternExample Unescaped (Formatted) String
abcabc
ab-ab−
ab'-'ab-
ab''ab'
* - * 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. * *
  * 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;
+ *     }
  * }
  * 
*/ 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. + * + *

+ * 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. - * - *

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. - * - *

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. + * + *

+ * 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 TYPE_ 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. + * + *

+ */ + 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 TYPE_ - * 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. - * - * - */ - 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); - } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CompactData.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CompactData.java index ed524b3ab4e..8b66e18fd6f 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CompactData.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CompactData.java @@ -39,10 +39,15 @@ public class CompactData implements MultiplierProducer { 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; @@ -71,7 +76,11 @@ public class CompactData implements MultiplierProducer { } /** 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); @@ -82,7 +91,8 @@ public class CompactData implements MultiplierProducer { /** Java-only method used by CLDR tooling. */ public void populate(Map> powersToPluralsToPatterns) { assert isEmpty; - for (Map.Entry> magnitudeEntry : powersToPluralsToPatterns.entrySet()) { + for (Map.Entry> magnitudeEntry : powersToPluralsToPatterns + .entrySet()) { byte magnitude = (byte) (magnitudeEntry.getKey().length() - 1); for (Map.Entry pluralEntry : magnitudeEntry.getValue().entrySet()) { StandardPlural plural = StandardPlural.fromString(pluralEntry.getKey().toString()); @@ -155,7 +165,7 @@ public class CompactData implements MultiplierProducer { 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; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantAffixModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantAffixModifier.java index 53f79096fe5..4ebb3d0cc63 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantAffixModifier.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantAffixModifier.java @@ -21,7 +21,8 @@ public class ConstantAffixModifier implements Modifier { * Constructs an instance with the given strings. * *

- * 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. diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantMultiFieldModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantMultiFieldModifier.java index c6c38e07b9e..557d0b80f83 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantMultiFieldModifier.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ConstantMultiFieldModifier.java @@ -5,8 +5,9 @@ package com.ibm.icu.impl.number; 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 { @@ -18,7 +19,10 @@ 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(); @@ -55,7 +59,8 @@ public class ConstantMultiFieldModifier implements Modifier { NumberStringBuilder temp = new NumberStringBuilder(); apply(temp, 0, 0); int prefixLength = getPrefixLength(); - return String.format("", temp.subSequence(0, prefixLength), + return String.format("", + temp.subSequence(0, prefixLength), temp.subSequence(prefixLength, temp.length())); } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CurrencySpacingEnabledModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CurrencySpacingEnabledModifier.java index 5aff0f36304..9fb35f7546a 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CurrencySpacingEnabledModifier.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CurrencySpacingEnabledModifier.java @@ -27,7 +27,10 @@ public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier { 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); @@ -70,12 +73,14 @@ public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier { 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); @@ -87,8 +92,13 @@ public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier { } /** 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); @@ -103,12 +113,16 @@ public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier { } /** 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; } @@ -135,8 +149,10 @@ public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier { 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:]")) { @@ -147,6 +163,7 @@ public class CurrencySpacingEnabledModifier extends ConstantMultiFieldModifier { } 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); } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CustomSymbolCurrency.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CustomSymbolCurrency.java index e8549096eea..b794aebe534 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CustomSymbolCurrency.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/CustomSymbolCurrency.java @@ -7,70 +7,69 @@ import com.ibm.icu.util.Currency; 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); + } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalFormatProperties.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalFormatProperties.java index 26859abe166..73d41e5b2fb 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalFormatProperties.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalFormatProperties.java @@ -105,8 +105,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable { * Sets all properties to their defaults (unset). * *

- * 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). * *

* All booleans default to false. @@ -550,7 +550,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable { 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 @@ -596,8 +597,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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: * *

      * {
@@ -619,15 +620,17 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
      *            A map with the above structure.
      * @return The property bag, for chaining.
      */
-    public DecimalFormatProperties setCompactCustomData(Map> compactCustomData) {
+    public DecimalFormatProperties setCompactCustomData(
+            Map> 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 en-US, 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 en-US, whereas CompactStyle.LONG produces output
+     * like "10 thousand" in that locale.
      *
      * @param compactStyle
      *            The style of prefixes/suffixes to append.
@@ -668,11 +671,12 @@ public class DecimalFormatProperties implements Cloneable, Serializable {
     }
 
     /**
-     * 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.
      *
      * 

- * 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. @@ -684,9 +688,10 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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 - * parse(), 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 parse(), and an error index will be set in the + * {@link ParsePosition}. * * @param decimalPatternMatchRequired * true to set an error if decimal is not present @@ -698,8 +703,9 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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 en-US. + * 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 + * en-US. * * @param alwaysShowDecimal * Whether to show the decimal point when it is optional. @@ -711,8 +717,9 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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 en-US. + * 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 en-US. * * @param exponentSignAlwaysShown * Whether to show the plus sign in positive exponents. @@ -724,13 +731,13 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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. * *

- * 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. * *

* Width is counted in UTF-16 code units. @@ -747,9 +754,9 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * Sets the number of digits between grouping separators. For example, the en-US 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 en-US 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. @@ -761,8 +768,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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. @@ -775,8 +782,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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. @@ -790,11 +797,12 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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 en-US 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 + * en-US 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. @@ -806,10 +814,11 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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 en-US. 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 en-US. 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. @@ -821,20 +830,21 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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 en-US 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 en-US + * with the default rounding mode. * *

- * 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. * *

- * 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. @@ -846,8 +856,9 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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 en-US. + * 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 + * en-US. * * @param minimumExponentDigits * The minimum number of digits to display in the exponent field. @@ -859,9 +870,10 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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 en-US. + * 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 en-US. * * @param minimumFractionDigits * The minimum number of fraction digits to output. @@ -873,10 +885,10 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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 en-US. 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 en-US. 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. @@ -888,9 +900,10 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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 en-US. + * 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 en-US. * * @param minimumIntegerDigits * The minimum number of integer digits to output. @@ -902,20 +915,22 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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 en-US. 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 + * en-US. Note that minimum significant digits is relevant only when numbers have digits + * after the decimal point. * *

- * 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". * *

- * 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. @@ -940,9 +955,10 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * Sets the prefix to prepend to negative numbers. The prefix will be interpreted literally. For example, if you set - * a negative prefix of n, then the number -123 will be formatted as "n123" in the locale - * en-US. 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 n, then the number -123 will be formatted as + * "n123" in the locale en-US. Note that if the negative prefix is left unset, the locale's + * minus sign is used. * *

* For more information on prefixes and suffixes, see {@link MutablePatternModifier}. @@ -958,14 +974,15 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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). * *

* 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 */ @@ -975,10 +992,11 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * Sets the suffix to append to negative numbers. The suffix will be interpreted literally. For example, if you set - * a suffix prefix of n, then the number -123 will be formatted as "-123n" in the locale - * en-US. 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 n, then the number -123 will be formatted as + * "-123n" in the locale en-US. 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. * *

* For more information on prefixes and suffixes, see {@link MutablePatternModifier}. @@ -994,14 +1012,15 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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). * *

* 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 */ @@ -1011,8 +1030,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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. * *

* Must be used in conjunction with {@link #setFormatWidth}. @@ -1028,7 +1047,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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. * *

* Must be used in conjunction with {@link #setFormatWidth}. @@ -1044,12 +1064,14 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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. * *

- * 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. @@ -1061,23 +1083,23 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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. * *

- * The comma, period, space, and apostrophe have different meanings in different locales. For example, in - * en-US and most American locales, the period is used as a decimal separator, but in es-PY 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 en-US and most American locales, the period is used as a decimal separator, + * but in es-PY and most European locales, it is used as a grouping separator. * *

- * Suppose you are in fr-FR the parser encounters the string "1.234". In fr-FR, the grouping is a - * space and the decimal is a comma. The grouping mode 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 fr-FR the parser encounters the string "1.234". In fr-FR, the + * grouping is a space and the decimal is a comma. The grouping mode 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). * *

- * When resolving grouping separators, it is the equivalence class 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 equivalence class 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. @@ -1089,7 +1111,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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 @@ -1101,8 +1124,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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}. @@ -1114,7 +1137,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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. @@ -1126,11 +1150,12 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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) { @@ -1151,9 +1176,9 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * Sets the prefix to prepend to positive numbers. The prefix will be interpreted literally. For example, if you set - * a positive prefix of p, then the number 123 will be formatted as "p123" in the locale - * en-US. + * Sets the prefix to prepend to positive numbers. The prefix will be interpreted literally. For + * example, if you set a positive prefix of p, then the number 123 will be formatted as + * "p123" in the locale en-US. * *

* For more information on prefixes and suffixes, see {@link MutablePatternModifier}. @@ -1169,14 +1194,15 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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). * *

* 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 */ @@ -1186,9 +1212,9 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * Sets the suffix to append to positive numbers. The suffix will be interpreted literally. For example, if you set - * a positive suffix of p, then the number 123 will be formatted as "123p" in the locale - * en-US. + * Sets the suffix to append to positive numbers. The suffix will be interpreted literally. For + * example, if you set a positive suffix of p, then the number 123 will be formatted as + * "123p" in the locale en-US. * *

* For more information on prefixes and suffixes, see {@link MutablePatternModifier}. @@ -1204,14 +1230,15 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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). * *

* 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 */ @@ -1221,15 +1248,16 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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 en-US 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 en-US with the default rounding + * mode. * *

* You can use either a rounding increment or significant digits, but not both at the same time. * *

- * 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. @@ -1241,9 +1269,9 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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}. * *

* This setting is ignored if {@link #setMathContext} is used. @@ -1260,13 +1288,13 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * Sets the number of digits between grouping separators higher than the least-significant grouping separator. For - * example, the locale hi 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 hi 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". * *

- * The two levels of grouping separators can be specified in the pattern string. For example, the hi - * locale's default decimal format pattern is "#,##,##0.###". + * The two levels of grouping separators can be specified in the pattern string. For example, the + * hi locale's default decimal format pattern is "#,##,##0.###". * * @param secondaryGroupingSize * The secondary grouping size. @@ -1281,14 +1309,16 @@ public class DecimalFormatProperties implements Cloneable, Serializable { * Sets whether to always display of a plus sign on positive numbers. * *

- * 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 #;#- is used, then formatting 123 would result in "123+" in the locale en-US. + * 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 #;#- is used, + * then formatting 123 would result in "123+" in the locale en-US. * *

- * This method should be used instead of 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 instead of 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. @@ -1309,8 +1339,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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(); @@ -1337,8 +1367,8 @@ public class DecimalFormatProperties implements Cloneable, Serializable { } /** - * 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); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java index 368dcfbec17..4d34b38133f 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java @@ -14,167 +14,183 @@ import com.ibm.icu.text.UFieldPosition; * An interface representing a number to be processed by the decimal formatting pipeline. Includes * methods for rounding, plural rules, and decimal digit extraction. * - *

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. + *

+ * 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. * - *

Implementations of this interface are free to use any internal storage mechanism. + *

+ * Implementations of this interface are free to use any internal storage mechanism. * - *

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? + *

+ * 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. - * - *

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. + * + *

+ * 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); } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java index b442cf1f3ee..a242d7eac00 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java @@ -19,911 +19,957 @@ import com.ibm.icu.text.UFieldPosition; */ 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. - * - *

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. - * - *

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. + * + *

+ * 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. + * + *

+ * 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. - * - *

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. - * - *

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. + * + *

+ * 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. + * + *

+ * 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(); } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java index 2ea374b6b50..92e660df28a 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_DualStorageBCD.java @@ -6,412 +6,433 @@ import java.math.BigDecimal; 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". - * - *

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". + * + *

+ * 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( - "", - (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("", + (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(); + } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java index 43bde7b4de1..e1f1cf7794a 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MacroProps.java @@ -15,96 +15,114 @@ import com.ibm.icu.util.MeasureUnit; 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); + } } - } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroProps.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroProps.java index 6d5a33aec8a..019a10168ab 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroProps.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroProps.java @@ -31,8 +31,8 @@ public class MicroProps implements Cloneable, MicroPropsGenerator { /** * @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; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroPropsGenerator.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroPropsGenerator.java index c633ce7ec70..12ce18db490 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroPropsGenerator.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MicroPropsGenerator.java @@ -3,19 +3,21 @@ 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. * *

- * In other words, this interface is used for the parts of number processing that are quantity-dependent. + * In other words, this interface is used for the parts of number processing that are + * quantity-dependent. * *

- * 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. * *

* A class implementing MicroPropsGenerator looks something like this: @@ -42,7 +44,8 @@ package com.ibm.icu.impl.number; */ 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. diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Modifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Modifier.java index b37e4d740fa..a7ab9b98a89 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Modifier.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Modifier.java @@ -3,12 +3,12 @@ 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 { @@ -18,17 +18,18 @@ 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. */ @@ -40,9 +41,9 @@ public interface Modifier { 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. */ diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierImpl.java index 0533ebf4e98..dd495d1a795 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierImpl.java @@ -5,39 +5,39 @@ package com.ibm.icu.impl.number; 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; - } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierProducer.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierProducer.java index e39150ae832..708481d6c98 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierProducer.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MultiplierProducer.java @@ -7,8 +7,9 @@ package com.ibm.icu.impl.number; */ 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. diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java index df1fa909930..9f8ea1fcd27 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java @@ -11,25 +11,27 @@ import com.ibm.icu.text.PluralRules; 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}. * *

- * 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. * *

- * 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. * *

- * 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; @@ -62,8 +64,8 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq /** * @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; @@ -71,8 +73,8 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq /** * 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; @@ -101,10 +103,14 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq * @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; @@ -118,8 +124,8 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq * @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(); @@ -128,17 +134,18 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq } /** - * 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. * *

* The resulting modifier cannot be used in a QuantityChain. @@ -150,9 +157,9 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq } /** - * 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. @@ -184,17 +191,19 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq } /** - * 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()) { @@ -209,7 +218,10 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq 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; @@ -260,8 +272,12 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq 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; } @@ -269,7 +285,7 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq 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; } @@ -278,10 +294,10 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq 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; } @@ -337,7 +353,8 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq // 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: @@ -357,7 +374,8 @@ public class MutablePatternModifier implements Modifier, SymbolProvider, CharSeq && (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)); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Padder.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Padder.java index c1be7034d4c..efb4cec0951 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Padder.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Padder.java @@ -6,40 +6,37 @@ public class Padder { 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); @@ -73,10 +70,16 @@ public class Padder { 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) { @@ -102,7 +105,10 @@ public class Padder { 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. diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ParameterizedModifier.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ParameterizedModifier.java index 5a7191af67c..3ccf5080490 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ParameterizedModifier.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/ParameterizedModifier.java @@ -5,8 +5,8 @@ package com.ibm.icu.impl.number; 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; @@ -28,8 +28,8 @@ public class ParameterizedModifier { } /** - * 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. * *

* If this constructor is used, a plural form MUST be passed to {@link #getModifier}. diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringParser.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringParser.java index 50336bc09b7..80c2c23e104 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringParser.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringParser.java @@ -12,8 +12,8 @@ public class PatternStringParser { 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. * *

* To obtain a more useful form of the data, consider using {@link #parseToProperties} instead. @@ -35,9 +35,10 @@ public class PatternStringParser { * @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. @@ -55,9 +56,10 @@ public class PatternStringParser { } /** - * 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" @@ -68,7 +70,9 @@ public class PatternStringParser { * @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); } @@ -262,7 +266,10 @@ public class PatternStringParser { 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; } @@ -504,7 +511,10 @@ public class PatternStringParser { /// 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? @@ -518,7 +528,9 @@ public class PatternStringParser { } /** 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. @@ -572,12 +584,14 @@ public class PatternStringParser { 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); @@ -633,7 +647,8 @@ public class PatternStringParser { // 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); @@ -662,9 +677,10 @@ public class PatternStringParser { 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); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringUtils.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringUtils.java index 4bed35f1f56..7a16b5e5230 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringUtils.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/PatternStringUtils.java @@ -16,8 +16,9 @@ public class PatternStringUtils { * Creates a pattern string from a 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. + * 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. @@ -61,7 +62,8 @@ public class PatternStringUtils { // 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; @@ -184,7 +186,9 @@ public class PatternStringUtils { // 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) @@ -232,14 +236,15 @@ public class PatternStringUtils { } /** - * 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 fr-FR, 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 fr-FR, 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. * *

- * 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. * *

* Locale symbols are not allowed to contain the ASCII quote character. @@ -252,11 +257,14 @@ public class PatternStringUtils { * @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; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Properties.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Properties.java index bb635cdc70a..59197b6a3bc 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Properties.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/Properties.java @@ -8,8 +8,8 @@ import java.io.ObjectOutputStream; 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 { diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java index 0c97552c487..9098d8c6aa2 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/RoundingUtils.java @@ -9,177 +9,193 @@ import java.math.RoundingMode; /** @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. - * - *

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. + * + *

+ * 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()]; - } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java b/icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java index f9a383525ee..c176ee25018 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/CompactNotation.java @@ -21,13 +21,13 @@ import com.ibm.icu.text.CompactDecimalFormat.CompactStyle; 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. * *

- * 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. @@ -48,8 +48,13 @@ public class CompactNotation extends Notation { 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); } @@ -66,8 +71,14 @@ public class CompactNotation extends Notation { final Map 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(); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/CurrencyRounder.java b/icu4j/main/classes/core/src/com/ibm/icu/number/CurrencyRounder.java index 97e36314a0c..dd0d0f02ab3 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/CurrencyRounder.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/CurrencyRounder.java @@ -5,8 +5,8 @@ package com.ibm.icu.number; 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. * *

* To create a CurrencyRounder, use one of the factory methods on Rounder. @@ -24,13 +24,13 @@ public abstract class CurrencyRounder extends Rounder { * Associates a currency with this rounding strategy. * *

- * Calling this method is not required, 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. + * Calling this method is not required, 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. * *

- * 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. diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/FractionRounder.java b/icu4j/main/classes/core/src/com/ibm/icu/number/FractionRounder.java index 2ced2a44bad..61ed736cb7e 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/FractionRounder.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/FractionRounder.java @@ -5,8 +5,8 @@ package com.ibm.icu.number; 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. * *

* To create a FractionRounder, use one of the factory methods on Rounder. @@ -21,15 +21,16 @@ public abstract class FractionRounder extends 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. * *

- * 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. * *

- * 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. @@ -42,22 +43,23 @@ public abstract class FractionRounder extends Rounder { 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. * *

- * 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. * *

- * 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. @@ -70,8 +72,9 @@ public abstract class FractionRounder extends Rounder { 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 diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/Grouper.java b/icu4j/main/classes/core/src/com/ibm/icu/number/Grouper.java index 6d1c1935c4c..b3e4318c871 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/Grouper.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/Grouper.java @@ -108,7 +108,8 @@ public class Grouper { 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 diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/IntegerWidth.java b/icu4j/main/classes/core/src/com/ibm/icu/number/IntegerWidth.java index 4d75894fad3..db59f234c98 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/IntegerWidth.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/IntegerWidth.java @@ -27,7 +27,8 @@ public class IntegerWidth { } /** - * 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. * *

* For example, with minInt=3, the number 55 will get printed as "055". @@ -45,8 +46,9 @@ public class IntegerWidth { } 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)"); } } @@ -56,7 +58,8 @@ public class IntegerWidth { * 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. @@ -70,8 +73,9 @@ public class IntegerWidth { } 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 diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java index 3be9101fb25..f614d1d70d5 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/LocalizedNumberFormatter.java @@ -38,8 +38,8 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings - * 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. @@ -115,8 +115,9 @@ public class LocalizedNumberFormatter extends NumberFormatterSettings * This function is very hot, being called in every call to the number formatting pipeline. diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/Notation.java b/icu4j/main/classes/core/src/com/ibm/icu/number/Notation.java index c1ec6c98859..409a4e9e0ce 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/Notation.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/Notation.java @@ -15,8 +15,14 @@ import com.ibm.icu.text.CompactDecimalFormat.CompactStyle; 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(); @@ -25,10 +31,11 @@ public class Notation { } /** - * 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". * *

* Example outputs in en-US when printing 8.765E4 through 8.765E-3: @@ -55,8 +62,8 @@ public class Notation { } /** - * 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. * *

* Example outputs in en-US when printing 8.765E4 through 8.765E-3: @@ -86,18 +93,18 @@ public class Notation { * Print the number using short-form compact notation. * *

- * Compact notation, 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. + * Compact notation, 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. * *

- * 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. * *

- * In short form, the powers of ten are abbreviated. In en-US, the abbreviations are "K" for thousands, "M" - * for millions, "B" for billions, and "T" for trillions. Example outputs in en-US when printing 8.765E7 - * through 8.765E0: + * In short form, the powers of ten are abbreviated. In en-US, the abbreviations are "K" for + * thousands, "M" for millions, "B" for billions, and "T" for trillions. Example outputs in + * en-US when printing 8.765E7 through 8.765E0: * *

      * 88M
@@ -111,10 +118,10 @@ public class Notation {
      * 
* *

- * 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: * *

      * Rounder.integer().withMinDigits(2)
@@ -134,8 +141,8 @@ public class Notation {
      * {@link #compactShort}.
      *
      * 

- * In long form, the powers of ten are spelled out fully. Example outputs in en-US when printing 8.765E7 - * through 8.765E0: + * In long form, the powers of ten are spelled out fully. Example outputs in en-US when + * printing 8.765E7 through 8.765E0: * *

      * 88 million
@@ -158,11 +165,12 @@ public class Notation {
     }
 
     /**
-     * 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.
      *
      * 

- * 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. * *

* Example outputs in en-US when printing 8.765E7 through 8.765E0: diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java index 2b788a4165b..5aece9b81b7 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatter.java @@ -10,7 +10,8 @@ import com.ibm.icu.text.DecimalFormatSymbols; 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: * *

  * // Most basic usage:
@@ -39,21 +40,25 @@ import com.ibm.icu.util.ULocale;
  * 
* *

- * 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. * *

- * 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. * *

- * 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"
  * 
* *

- * This API is based on the fluent design pattern popularized by libraries such as Google's Guava. For - * extensive details on the design of this API, read the design doc. + * This API is based on the fluent design pattern popularized by libraries such as Google's + * Guava. For extensive details on the design of this API, read the + * design doc. * * @author Shane Carr * @draft ICU 60 @@ -64,8 +69,8 @@ public final class NumberFormatter { 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 en-CA: + * An enum declaring how to render units, including currencies. Example outputs when formatting 123 + * USD and 123 meters in en-CA: * *

    *
  • NARROW: "$123.00" and "123 m" @@ -84,13 +89,14 @@ public final class NumberFormatter { */ 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. * *

    - * 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. @@ -99,16 +105,16 @@ public final class NumberFormatter { 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. * *

    - * For example, in es-US, 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 es-US, 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. * *

    - * 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. @@ -120,8 +126,8 @@ public final class NumberFormatter { * Print the full name of the unit, without any abbreviations. * *

    - * 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. @@ -130,8 +136,8 @@ public final class NumberFormatter { 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. * *

    * In CLDR, this option corresponds to the "¤¤" placeholder for currencies. @@ -143,9 +149,9 @@ public final class NumberFormatter { 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. @@ -155,8 +161,8 @@ public final class NumberFormatter { } /** - * An enum declaring how to denote positive and negative numbers. Example outputs when formatting 123 and -123 in - * en-US: + * An enum declaring how to denote positive and negative numbers. Example outputs when formatting 123 + * and -123 in en-US: * *

      *
    • AUTO: "123" and "-123" @@ -175,8 +181,8 @@ public final class NumberFormatter { */ 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. @@ -203,16 +209,17 @@ public final class NumberFormatter { 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. * *

      - * 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. * *

      - * 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. @@ -221,8 +228,9 @@ public final class NumberFormatter { 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. @@ -232,8 +240,8 @@ public final class NumberFormatter { } /** - * An enum declaring how to render the decimal separator. Example outputs when formatting 1 and 1.1 in - * en-US: + * An enum declaring how to render the decimal separator. Example outputs when formatting 1 and 1.1 + * in en-US: * *

        *
      • AUTO: "1" and "1.1" @@ -246,8 +254,8 @@ public final class NumberFormatter { */ 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. @@ -266,8 +274,9 @@ public final class NumberFormatter { } /** - * 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; @@ -278,8 +287,8 @@ public final class NumberFormatter { } /** - * 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 @@ -290,8 +299,8 @@ public final class NumberFormatter { } /** - * 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. @@ -304,8 +313,8 @@ public final class NumberFormatter { } /** - * 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. @@ -322,8 +331,10 @@ public final class NumberFormatter { * @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); } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java index bb07017ecd1..0702398bfb2 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterImpl.java @@ -25,12 +25,12 @@ import com.ibm.icu.util.Currency; 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. * *

        - * 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 { @@ -40,8 +40,13 @@ 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); @@ -84,17 +89,17 @@ class NumberFormatterImpl { } /** - * 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 - * not 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 not 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); @@ -109,7 +114,8 @@ class NumberFormatterImpl { 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) { @@ -134,13 +140,15 @@ class NumberFormatterImpl { } 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); ///////////////////////////////////////////////////////////////////////////////////// @@ -247,7 +255,8 @@ class NumberFormatterImpl { // 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 @@ -267,11 +276,15 @@ class NumberFormatterImpl { // 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; @@ -289,7 +302,10 @@ class NumberFormatterImpl { * @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); @@ -309,7 +325,10 @@ class NumberFormatterImpl { } } - 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); @@ -322,9 +341,12 @@ class NumberFormatterImpl { 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 @@ -334,30 +356,40 @@ class NumberFormatterImpl { 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++) { @@ -367,7 +399,8 @@ class NumberFormatterImpl { 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; diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java index 05adfa7ce35..df5d94bca9f 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberFormatterSettings.java @@ -16,9 +16,9 @@ import com.ibm.icu.util.NoUnit; 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. @@ -63,8 +63,8 @@ public abstract class NumberFormatterSettings * *

        - * 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. * *

        * Pass this method the return value of a {@link Notation} factory method. For example: @@ -97,13 +97,13 @@ public abstract class NumberFormatterSettings * Note: 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. * *

        - * 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. * *

        * Pass this method any instance of {@link MeasureUnit}. For units of measure: @@ -145,8 +145,8 @@ public abstract class NumberFormatterSettings * Pass this method any instance of {@link MeasureUnit}. For example: @@ -191,10 +191,10 @@ public abstract class NumberFormatterSettings * In most cases, the default rounding strategy is to round to 6 fraction places; i.e., - * Rounder.maxFraction(6). 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). + * Rounder.maxFraction(6). 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. @@ -271,8 +271,8 @@ public abstract class NumberFormatterSettings *

      • en_US symbols: "12,345.67" @@ -289,17 +289,17 @@ public abstract class NumberFormatterSettings * *

        - * Note: 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. + * Note: 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. * *

        - * Note: The instance of DecimalFormatSymbols will be copied: changes made to the symbols object - * after passing it into the fluent chain will not be seen. + * Note: The instance of DecimalFormatSymbols will be copied: changes made to the + * symbols object after passing it into the fluent chain will not be seen. * *

        - * Note: Calling this method will override the NumberingSystem previously specified in - * {@link #symbols(NumberingSystem)}. + * Note: Calling this method will override the NumberingSystem previously specified + * in {@link #symbols(NumberingSystem)}. * *

        * The default is to choose the symbols based on the locale specified in the fluent chain. @@ -326,16 +326,16 @@ public abstract class NumberFormatterSettings * *

        - * 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): * *

              * NumberFormatter.with().symbols(NumberingSystem.LATIN)
              * 
        * *

        - * Note: Calling this method will override the DecimalFormatSymbols previously specified in - * {@link #symbols(DecimalFormatSymbols)}. + * Note: Calling this method will override the DecimalFormatSymbols previously + * specified in {@link #symbols(DecimalFormatSymbols)}. * *

        * The default is to choose the best numbering system for the locale. @@ -412,8 +412,8 @@ public abstract class NumberFormatterSettings *

      • Auto: "1" @@ -464,8 +464,8 @@ public abstract class NumberFormatterSettings - * 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); @@ -51,9 +53,9 @@ final class NumberPropertyMapper { } /** - * 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. @@ -63,7 +65,9 @@ final class NumberPropertyMapper { * 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(); @@ -96,8 +100,10 @@ final class NumberPropertyMapper { // 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; @@ -122,7 +128,8 @@ final class NumberPropertyMapper { 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) { @@ -151,7 +158,8 @@ final class NumberPropertyMapper { 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) { @@ -159,10 +167,12 @@ final class NumberPropertyMapper { } 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); @@ -198,7 +208,8 @@ final class NumberPropertyMapper { ///////////// if (properties.getFormatWidth() != -1) { - macros.padder = new Padder(properties.getPadString(), properties.getFormatWidth(), + macros.padder = new Padder(properties.getPadString(), + properties.getFormatWidth(), properties.getPadPosition()); } @@ -246,7 +257,8 @@ final class NumberPropertyMapper { // 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(); @@ -342,15 +354,20 @@ final class NumberPropertyMapper { 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. @@ -449,14 +466,18 @@ final class NumberPropertyMapper { @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); } } diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java b/icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java index 38db8c861fb..783adf35c52 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/Rounder.java @@ -34,11 +34,12 @@ public abstract class Rounder implements Cloneable { * Show all available digits to full precision. * *

        - * NOTE: When formatting a double, this method, along with {@link #minFraction} and - * {@link #minDigits}, will trigger complex algorithm similar to Dragon4 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. + * NOTE: When formatting a double, this method, along with + * {@link #minFraction} and {@link #minDigits}, will trigger complex algorithm similar to + * Dragon4 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. * *

        * http://www.serpentine.com/blog/2011/06/29/here-be-dragons-advances-in-problems-you-didnt-even-know-you-had/ @@ -65,8 +66,9 @@ public abstract class Rounder implements Cloneable { } /** - * 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. * *

        * Example output with minMaxFractionPlaces = 3: @@ -86,8 +88,8 @@ public abstract class Rounder implements Cloneable { * 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. @@ -97,21 +99,23 @@ public abstract class Rounder implements Cloneable { 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). * *

        - * NOTE: If you are formatting doubles, see the performance note in {@link #unlimited}. + * NOTE: If you are formatting doubles, 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. @@ -121,18 +125,20 @@ public abstract class Rounder implements Cloneable { 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 not 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 not + * 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. @@ -142,46 +148,51 @@ public abstract class Rounder implements Cloneable { 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. * *

        * 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. @@ -191,17 +202,19 @@ public abstract class Rounder implements Cloneable { 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). * *

        - * NOTE: If you are formatting doubles, see the performance note in {@link #unlimited}. + * NOTE: If you are formatting doubles, see the performance note in + * {@link #unlimited}. * * @param minSignificantDigits * The minimum number of significant digits to display (padding with zeros if too short). @@ -214,8 +227,9 @@ public abstract class Rounder implements Cloneable { 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)"); } } @@ -233,14 +247,15 @@ public abstract class Rounder implements Cloneable { 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). @@ -252,23 +267,26 @@ public abstract class Rounder implements Cloneable { * @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. * *

        - * 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: * *

              * Rounder.increment(new BigDecimal("0.50"))
        @@ -293,18 +311,20 @@ public abstract class Rounder implements Cloneable {
             }
         
             /**
        -     * Show numbers rounded and padded according to the rules for the currency unit. The most common rounding settings
        -     * for currencies include Rounder.fixedFraction(2), Rounder.integer(), and
        -     * Rounder.increment(0.05) 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 Rounder.fixedFraction(2),
        +     * Rounder.integer(), and Rounder.increment(0.05) for cash transactions
        +     * ("nickel rounding").
              *
              * 

        * 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. @@ -319,8 +339,8 @@ public abstract class Rounder implements Cloneable { } /** - * 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. @@ -470,8 +490,8 @@ public abstract class Rounder implements Cloneable { } /** - * 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. @@ -486,17 +506,21 @@ public abstract class Rounder implements Cloneable { } /** - * 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. * *

        - * 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) { @@ -563,7 +587,8 @@ public abstract class Rounder implements Cloneable { @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); } } @@ -579,10 +604,14 @@ public abstract class Rounder implements Cloneable { @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); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java b/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java index c0f7a60293a..e121fb7db81 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/ScientificNotation.java @@ -15,7 +15,8 @@ import com.ibm.icu.text.DecimalFormatSymbols; 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. * *

        * To create a ScientificNotation, use one of the factory methods in {@link Notation}. @@ -31,7 +32,10 @@ public class ScientificNotation extends Notation implements Cloneable { 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; @@ -40,12 +44,12 @@ public class ScientificNotation extends Notation implements Cloneable { } /** - * 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. * *

        - * For example, with minExponentDigits=2, the number 123 will be printed as "1.23E02" in en-US instead of - * the default "1.23E2". + * For example, with minExponentDigits=2, the number 123 will be printed as "1.23E02" in + * en-US instead of the default "1.23E2". * * @param minExponentDigits * The minimum number of digits to show in the exponent. @@ -60,18 +64,19 @@ public class ScientificNotation extends Notation implements Cloneable { 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. * *

        - * For example, with exponentSignDisplay=ALWAYS, the number 123 will be printed as "1.23E+2" in en-US - * instead of the default "1.23E2". + * For example, with exponentSignDisplay=ALWAYS, the number 123 will be printed as "1.23E+2" in + * en-US instead of the default "1.23E2". * * @param exponentSignDisplay * The strategy for displaying the sign in the exponent. @@ -101,21 +106,27 @@ public class ScientificNotation extends Notation implements Cloneable { } } - /* 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 { @@ -125,7 +136,10 @@ public class ScientificNotation extends Notation implements Cloneable { 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; @@ -152,7 +166,8 @@ public class ScientificNotation extends Notation implements Cloneable { 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); diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/SimpleNotation.java b/icu4j/main/classes/core/src/com/ibm/icu/number/SimpleNotation.java index 7007a3af909..bd9ca6e5b61 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/SimpleNotation.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/SimpleNotation.java @@ -6,8 +6,8 @@ package com.ibm.icu.number; * A class that defines the simple notation style to be used when formatting numbers in NumberFormatter. * *

        - * 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. diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/UnlocalizedNumberFormatter.java b/icu4j/main/classes/core/src/com/ibm/icu/number/UnlocalizedNumberFormatter.java index bc82c90b062..decce7ffe2e 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/UnlocalizedNumberFormatter.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/UnlocalizedNumberFormatter.java @@ -7,7 +7,8 @@ import java.util.Locale; 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 @@ -25,8 +26,8 @@ public class UnlocalizedNumberFormatter extends NumberFormatterSettings * To use the Java default locale, call Locale.getDefault(): diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/AffixUtilsTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/AffixUtilsTest.java index fe77028a44f..b29d3482f3d 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/AffixUtilsTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/AffixUtilsTest.java @@ -13,214 +13,205 @@ import com.ibm.icu.impl.number.NumberStringBuilder; 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(); + } } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java index 786ea1a4ecf..d8ce44129bb 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java @@ -31,490 +31,487 @@ import com.ibm.icu.util.ULocale; @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 formats = new ArrayList(); - - 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 formats = new ArrayList(); + + 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 formats, + int mode) { + if (mode == 2) { + assertEquals("Double is not valid", Double.toString(Double.parseDouble(str)), str); + } + + List qs = new ArrayList(); + 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 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 qs = new ArrayList(); - 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 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 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, ""); + fq.setToInt(51423); + assertToStringAndHealth(fq, ""); + fq.adjustMagnitude(-3); + assertToStringAndHealth(fq, ""); + fq.setToLong(999999999999000L); + assertToStringAndHealth(fq, ""); + fq.setIntegerLength(2, 5); + assertToStringAndHealth(fq, ""); + fq.setFractionLength(3, 6); + assertToStringAndHealth(fq, ""); + fq.setToDouble(987.654321); + assertToStringAndHealth(fq, ""); + fq.roundToInfinity(); + assertToStringAndHealth(fq, ""); + fq.roundToIncrement(new BigDecimal("0.005"), MATH_CONTEXT_HALF_EVEN); + assertToStringAndHealth(fq, ""); + fq.roundToMagnitude(-2, MATH_CONTEXT_HALF_EVEN); + assertToStringAndHealth(fq, ""); } - 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, ""); - fq.setToInt(51423); - assertToStringAndHealth(fq, ""); - fq.adjustMagnitude(-3); - assertToStringAndHealth(fq, ""); - fq.setToLong(999999999999000L); - assertToStringAndHealth(fq, ""); - fq.setIntegerLength(2, 5); - assertToStringAndHealth(fq, ""); - fq.setFractionLength(3, 6); - assertToStringAndHealth(fq, ""); - fq.setToDouble(987.654321); - assertToStringAndHealth(fq, ""); - fq.roundToInfinity(); - assertToStringAndHealth(fq, ""); - fq.roundToIncrement(new BigDecimal("0.005"), MATH_CONTEXT_HALF_EVEN); - assertToStringAndHealth(fq, ""); - fq.roundToMagnitude(-2, MATH_CONTEXT_HALF_EVEN); - assertToStringAndHealth(fq, ""); - } - - 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); - } } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ModifierTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ModifierTest.java index 6af4ea1c4a0..10d768080a4 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ModifierTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/ModifierTest.java @@ -46,11 +46,20 @@ public class ModifierTest { @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" } }; @@ -59,7 +68,11 @@ public class ModifierTest { 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++) { @@ -99,7 +112,9 @@ public class ModifierTest { // 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$$$"); @@ -112,18 +127,18 @@ public class ModifierTest { // 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( @@ -134,7 +149,12 @@ public class ModifierTest { 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( @@ -150,8 +170,10 @@ public class ModifierTest { 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("", sb.toDebugString()); + assertEquals("", + sb.toDebugString()); } } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MutablePatternModifierTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MutablePatternModifierTest.java index 80f44ada5d4..9622e8dd1aa 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MutablePatternModifierTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/MutablePatternModifierTest.java @@ -27,8 +27,7 @@ public class MutablePatternModifierTest { 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); diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberStringBuilderTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberStringBuilderTest.java index 8d9814cd697..3baa78f3213 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberStringBuilderTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberStringBuilderTest.java @@ -17,208 +17,207 @@ import com.ibm.icu.text.NumberFormat; /** @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)); + } } - } } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PatternStringTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PatternStringTest.java index b0979839862..93afd753763 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PatternStringTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PatternStringTest.java @@ -16,104 +16,113 @@ import com.ibm.icu.util.ULocale; /** @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); - } } diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PropertiesTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PropertiesTest.java index a93b7673dda..dc4611ae3d3 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PropertiesTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/PropertiesTest.java @@ -45,322 +45,341 @@ import com.ibm.icu.util.ULocale; 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 hashCodes = new HashSet(); - 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> for compactCustomData property - if (seed == 0) return null; - Map> outer = new HashMap>(); - Map inner = new HashMap(); - 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 hashCodes = new HashSet(); + 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> for compactCustomData property + if (seed == 0) + return null; + Map> outer = new HashMap>(); + Map inner = new HashMap(); + 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; + } } - } }