From: Shane Carr Date: Sat, 23 Dec 2017 03:13:03 +0000 (+0000) Subject: ICU-13513 Merging trunk to branch (includes the big reformatting commit). X-Git-Tag: release-61-rc~138^2~17 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=bdb19075f81c25adeed5a03a2ae36aee733078eb;p=icu ICU-13513 Merging trunk to branch (includes the big reformatting commit). X-SVN-Rev: 40751 --- bdb19075f81c25adeed5a03a2ae36aee733078eb diff --cc icu4j/main/classes/core/src/com/ibm/icu/impl/number/AffixPatternProvider.java index daf22c29051,02cda7dd551..01d137fceef --- 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,32 -3,24 +3,32 @@@ 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; + } - // Convenience compound flags - public static final int FLAG_POS_PREFIX = Flags.PREFIX; - public static final int FLAG_POS_SUFFIX = 0; - public static final int FLAG_NEG_PREFIX = Flags.PREFIX | Flags.NEGATIVE_SUBPATTERN; - public static final int FLAG_NEG_SUFFIX = Flags.NEGATIVE_SUBPATTERN; ++ // Convenience compound flags ++ public static final int FLAG_POS_PREFIX = Flags.PREFIX; ++ public static final int FLAG_POS_SUFFIX = 0; ++ public static final int FLAG_NEG_PREFIX = Flags.PREFIX | Flags.NEGATIVE_SUBPATTERN; ++ public static final int FLAG_NEG_SUFFIX = Flags.NEGATIVE_SUBPATTERN; + - 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 String getString(int flags); ++ public String getString(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 --cc icu4j/main/classes/core/src/com/ibm/icu/impl/number/AffixUtils.java index d5706e3d017,2cf6d06b74e..73d717d4f5d --- 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 @@@ -46,609 -60,600 +61,637 @@@ import com.ibm.icu.text.UnicodeSet */ 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; + 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 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 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 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 percent sign symbol '%'. */ + public static final int TYPE_PERCENT = -3; - /** Represents a permille sign symbol '‰'. */ - public static final int TYPE_PERMILLE = -4; + /** 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 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 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 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 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 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; + /** 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); - } + 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; + /** + * 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; - } + case '-': + case '+': + case '%': + case '‰': + case '¤': + if (state == STATE_BASE) { + output.append('\''); + output.appendCodePoint(cp); + state = STATE_INSIDE_QUOTE; + } else { + output.appendCodePoint(cp); + } + 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; - - 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) { + output.append('\''); + output.appendCodePoint(cp); + state = STATE_BASE; + } else { + output.appendCodePoint(cp); + } + break; + } + offset += Character.charCount(cp); + } - default: - if (state == STATE_INSIDE_QUOTE) { + 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('\''); + return output.length() - startLength; } - 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(); - } + /** 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; - } - } - 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; - } + /** + * 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; - } - /** - * 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; - } + /** + * 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 new String(chars); - } + /** + * Appends a new affix pattern with all symbols and code points in the given "ignorables" UnicodeSet trimmed from the + * beginning and end. Similar to calling unescape with a symbol provider that always returns the empty string. + * + *

+ * Accepts and returns a StringBuilder, allocating it only if necessary. + */ + public static StringBuilder trimSymbolsAndIgnorables( + CharSequence affixPattern, + UnicodeSet ignorables, + StringBuilder sb) { + assert affixPattern != null; + long tag = 0L; + int trailingIgnorables = 0; + while (hasNext(tag, affixPattern)) { + tag = nextToken(tag, affixPattern); + int typeOrCp = getTypeOrCp(tag); + if (typeOrCp >= 0) { + if (!ignorables.contains(typeOrCp)) { + if (sb == null) { + // Lazy-initialize the StringBuilder + sb = new StringBuilder(); + } + sb.appendCodePoint(typeOrCp); + trailingIgnorables = 0; + } else if (sb != null && sb.length() > 0) { + sb.appendCodePoint(typeOrCp); + trailingIgnorables += Character.charCount(typeOrCp); + } + } + } + if (trailingIgnorables > 0) { + sb.setLength(sb.length() - trailingIgnorables); + } + return sb; + } + - /** - * 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; + /** + * 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; + 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(); - } - } - // 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(); + 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(); + /** + * 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; - } + /** + * 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; - } + /** + * 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 getOffset(long tag) { + return (int) (tag & 0xffffffff); + } - static int getType(long tag) { - return (int) ((tag >>> 32) & 0xf); - } + static int getType(long tag) { + return (int) ((tag >>> 32) & 0xf); + } - static int getState(long tag) { - return (int) ((tag >>> 36) & 0xf); - } + static int getState(long tag) { + return (int) ((tag >>> 36) & 0xf); + } - static int getCodePoint(long tag) { - return (int) (tag >>> 40); - } + static int getCodePoint(long tag) { + return (int) (tag >>> 40); + } } diff --cc icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity.java index a5decc3469f,4d34b38133f..2f2fad57b72 --- 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,172 -14,183 +14,188 @@@ 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(); - - /** - * Truncates the decimals from this DecimalQuantity. Equivalent to calling roundToMagnitude(0, FLOOR) - */ - void truncate(); - - /** - * 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(); + ++ /** ++ * Truncates the decimals from this DecimalQuantity. Equivalent to calling roundToMagnitude(0, FLOOR) ++ */ ++ void truncate(); ++ + /** + * 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 --cc icu4j/main/classes/core/src/com/ibm/icu/impl/number/DecimalQuantity_AbstractBCD.java index 147ca5fb628,a242d7eac00..f499e6bb13e --- 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,961 -19,957 +19,1092 @@@ 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. A long - * cannot represent precisions greater than 16. - * - *

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 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. ++ * The number of digits in the BCD. For example, "1007" has BCD "0x1007" and precision 4. A long ++ * cannot represent precisions greater than 16. + * + *

+ * 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; + } - /** - * 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; + 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; + } - /** - * 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 setIntegerLength(int minInt, int maxInt) { + // Validation should happen outside of DecimalQuantity, e.g., in the Rounder class. + assert minInt >= 0; + assert maxInt >= minInt; - @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; - } + // Save values into internal state + // Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE + lOptPos = maxInt; + lReqPos = minInt; + } - 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 setFractionLength(int minFrac, int maxFrac) { + // Validation should happen outside of DecimalQuantity, e.g., in the Rounder class. + assert minFrac >= 0; + assert maxFrac >= minFrac; - @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; - } + // 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 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 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; + @Override + public void multiplyBy(BigDecimal multiplicand) { + if (isInfinite() || isZero() || isNaN()) { + return; + } + BigDecimal temp = toBigDecimal(); + temp = temp.multiply(multiplicand); + setToBigDecimal(temp); } - 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 int getMagnitude() throws ArithmeticException { + if (precision == 0) { + throw new ArithmeticException("Magnitude is not well-defined for zero"); + } else { + return scale + precision - 1; + } } - } ++<<<<<<< .working + @Override + public void adjustMagnitude(int delta) { + if (precision != 0) { + // TODO: Math.addExact is not in 1.6 or 1.7 + scale = Math.addExact(scale, delta); + origDelta = Math.addExact(origDelta, delta); ++======= + @Override + public void adjustMagnitude(int delta) { + if (precision != 0) { + scale += delta; + origDelta += delta; + } ++>>>>>>> .merge-right.r40750 } - } - @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 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 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 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 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; - @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; + int magnitude = scale + precision; + int result = (lReqPos > magnitude) ? lReqPos : (lOptPos < magnitude) ? lOptPos : magnitude; + return result - 1; + } - return getDigitPos(magnitude - scale); - } + @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; - private int fractionCount() { - return -getLowerDisplayMagnitude(); - } + int magnitude = scale; + int result = (rReqPos < magnitude) ? rReqPos : (rOptPos > magnitude) ? rOptPos : magnitude; + return result; + } - private int fractionCountWithoutTrailingZeros() { - return Math.max(-scale, 0); - } + @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; - @Override - public boolean isNegative() { - return (flags & NEGATIVE_FLAG) != 0; - } + return getDigitPos(magnitude - scale); + } - @Override - public boolean isInfinite() { - return (flags & INFINITY_FLAG) != 0; - } + private int fractionCount() { + return -getLowerDisplayMagnitude(); + } - @Override - public boolean isNaN() { - return (flags & NAN_FLAG) != 0; - } + private int fractionCountWithoutTrailingZeros() { + return Math.max(-scale, 0); + } - @Override - public boolean isZero() { - return precision == 0; - } + @Override + public boolean isNegative() { + return (flags & NEGATIVE_FLAG) != 0; + } - public void setToInt(int n) { - setBcdToZero(); - flags = 0; - if (n < 0) { - flags |= NEGATIVE_FLAG; - n = -n; + @Override + public boolean isInfinite() { + return (flags & INFINITY_FLAG) != 0; } - if (n != 0) { - _setToInt(n); - compact(); + + @Override + public boolean isNaN() { + return (flags & NAN_FLAG) != 0; } - } - private void _setToInt(int n) { - if (n == Integer.MIN_VALUE) { - readLongToBcd(-(long) n); - } else { - readIntToBcd(n); + @Override + public boolean isZero() { + return precision == 0; } - } - public void setToLong(long n) { - setBcdToZero(); - flags = 0; - if (n < 0) { - flags |= NEGATIVE_FLAG; - n = -n; + public void setToInt(int n) { + setBcdToZero(); + flags = 0; + if (n < 0) { + flags |= NEGATIVE_FLAG; + n = -n; + } + if (n != 0) { + _setToInt(n); + compact(); + } } - if (n != 0) { - _setToLong(n); - compact(); + + private void _setToInt(int n) { + if (n == Integer.MIN_VALUE) { + readLongToBcd(-(long) n); + } else { + readIntToBcd(n); + } } - } - 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 setToLong(long n) { + setBcdToZero(); + flags = 0; + if (n < 0) { + flags |= NEGATIVE_FLAG; + n = -n; + } + if (n != 0) { + _setToLong(n); + compact(); + } } - } - public void setToBigInteger(BigInteger n) { - setBcdToZero(); - flags = 0; - if (n.signum() == -1) { - flags |= NEGATIVE_FLAG; - n = n.negate(); + 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); + } } - if (n.signum() != 0) { - _setToBigInteger(n); - compact(); + + 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); + 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(); + /** + * 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 - }; + 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; - /** - * 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; + // 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; - } + /** + * 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(); - /** - * 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; + // 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; + } - /** - * 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(); + scale += delta; + compact(); + explicitExactDouble = true; } - if (n.signum() != 0) { - _setToBigDecimal(n); - compact(); + + /** + * 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(); + } } - } ++<<<<<<< .working + 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. + */ + public long toLong() { + long result = 0L; + for (int magnitude = scale + precision - 1; magnitude >= 0; magnitude--) { + result = result * 10 + getDigitPos(magnitude - scale); ++======= + private void _setToBigDecimal(BigDecimal n) { + int fracLength = n.scale(); + n = n.scaleByPowerOfTen(fracLength); + BigInteger bi = n.toBigInteger(); + _setToBigInteger(bi); + scale -= fracLength; ++>>>>>>> .merge-right.r40750 } - return result; - } ++<<<<<<< .working + /** + * 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. + */ + public 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); ++======= + /** + * 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; ++>>>>>>> .merge-right.r40750 } - return result; - } ++<<<<<<< .working + static final byte[] INT64_BCD = {9,2,2,3,3,7,2,0,3,6,8,5,4,7,7,5,8,0,7}; + + /** + * Returns whether or not a Long can fully represent the value stored in this DecimalQuantity. + * Assumes that the DecimalQuantity is positive. + */ + public boolean fitsInLong() { + if (isZero()) { + return true; + } + if (scale < 0) { + return false; + } + int magnitude = getMagnitude(); + if (magnitude < 18) { + return true; + } + if (magnitude > 18) { + return false; + } + // Hard case: the magnitude is 10^18. + // The largest int64 is: 9,223,372,036,854,775,807 + for (int p=0; p INT64_BCD[p]) { + return false; + } + } + // Exactly equal to max long. + return true; + } + + /** + * 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(); ++======= + /** + * 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; ++>>>>>>> .merge-right.r40750 } - 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; - } + /** + * 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(); + } - private static final int SECTION_LOWER_EDGE = -1; - private static final int SECTION_UPPER_EDGE = -2; + if (isNaN()) { + return Double.NaN; + } else if (isInfinite()) { + return isNegative() ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY; + } - @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; - } - } + long tempLong = 0L; + int lostDigits = precision - Math.min(precision, 17); + for (int shift = precision - 1; shift >= lostDigits; shift--) { + tempLong = tempLong * 10 + getDigitPos(shift); } - } 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; + 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 { - section = RoundingUtils.SECTION_UPPER; + // 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; + } - 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; + @Override + public BigDecimal toBigDecimal() { + if (isApproximate) { + // Converting to a BigDecimal requires Double.toString(). + convertToAccurateDouble(); } + return bcdToBigDecimal(); + } - // 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; + 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; + } - // Good to continue rounding. - if (section == SECTION_LOWER_EDGE) section = RoundingUtils.SECTION_LOWER; - if (section == SECTION_UPPER_EDGE) section = RoundingUtils.SECTION_UPPER; - } + 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; + } - boolean roundDown = - RoundingUtils.getRoundingDirection( - (trailingDigit % 2) == 0, - isNegative(), - section, - mathContext.getRoundingMode().ordinal(), - this); + 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; + } - // Perform truncation - if (position >= precision) { - setBcdToZero(); - scale = magnitude; - } else { - shiftRight(position); - } + 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; + } - // 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 - } + 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(); + compact(); + } } - } - @Override - public void roundToInfinity() { - if (isApproximate) { - convertToAccurateDouble(); + @Override + public void roundToInfinity() { + if (isApproximate) { + convertToAccurateDouble(); + } } - } ++<<<<<<< .working + @Override + public void truncate() { + if (scale < 0) { + shiftRight(-scale); + scale = 0; + compact(); + } + } + + /** + * 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; ++======= + /** + * 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; ++>>>>>>> .merge-right.r40750 + + // 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; + } - // 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; - } - } + // Deal with trailing zeros + if (scale > 0) { + leadingZeros += scale; + if (appendAsInteger) { + scale = 0; + } + } - // Append digit - shiftLeft(leadingZeros + 1); - setDigitPos(0, value); + // Append digit + shiftLeft(leadingZeros + 1); + setDigitPos(0, value); - // Fix scale if in integer mode - if (appendAsInteger) { - scale += leadingZeros + 1; + // 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); + @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); + ++<<<<<<< .working + /** + * Removes digits from the end of the BCD list. This may result in an invalid BCD representation; it is + * the caller's responsibility to follow-up with a call to {@link #compact}. + * + * @param numDigits The number of zeros to add. + */ + 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(); ++======= + protected abstract void shiftRight(int numDigits); ++>>>>>>> .merge-right.r40750 + + /** + * 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 --cc icu4j/main/classes/core/src/com/ibm/icu/impl/number/MutablePatternModifier.java index 929c50b0601,9f8ea1fcd27..3eb1a9b3b6e --- 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 @@@ -101,11 -103,15 +103,15 @@@ public class MutablePatternModifie * @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(); + //assert (rules != null) == needsPlurals(); this.symbols = symbols; this.currency = currency; this.unitWidth = unitWidth; diff --cc icu4j/main/classes/core/src/com/ibm/icu/number/NumberPropertyMapper.java index c07d88b7705,49f1f1e0aaf..4c082fc3deb --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberPropertyMapper.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberPropertyMapper.java @@@ -58,10 -62,12 +60,12 @@@ final class NumberPropertyMapper * @param symbols * The symbols associated with the property bag. * @param exportedProperties - * A property bag in which to store validated properties. + * A property bag in which to store validated properties. Used by some DecimalFormat getters. * @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(); diff --cc icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/AffixUtilsTest.java index f10fad7d5df,b29d3482f3d..b7f3e994867 --- 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 @@@ -21,229 -19,199 +20,221 @@@ public class AffixUtilsTest // 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'" }; - @Test - public void testUnescapeWithSymbolProvider() { - String[][] cases = { - {"", ""}, - {"-", "1"}, - {"'-'", "-"}, - {"- + % ‰ ¤ ¤¤ ¤¤¤ ¤¤¤¤ ¤¤¤¤¤", "1 2 3 4 5 6 7 8 9"}, - {"'¤¤¤¤¤¤'", "¤¤¤¤¤¤"}, - {"¤¤¤¤¤¤", "\uFFFD"} - }; + 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 + } + } + } - 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()); - } - + @Test + public void testWithoutSymbolsOrIgnorables() { + String[][] cases = { + {"", ""}, + {"-", ""}, + {" ", ""}, + {"'-'", "-"}, + {" a + b ", "a b"}, + {"-a+b%c‰d¤e¤¤f¤¤¤g¤¤¤¤h¤¤¤¤¤i", "abcdefghi"}, + }; + + UnicodeSet ignorables = new UnicodeSet("[:whitespace:]"); + StringBuilder sb = new StringBuilder(); + for (String[] cas : cases) { + String input = cas[0]; + String expected = cas[1]; + sb.setLength(0); + AffixUtils.trimSymbolsAndIgnorables(input, ignorables, sb); + assertEquals("Removing symbols from: " + input, expected, 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 --cc icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/DecimalQuantityTest.java index dca7d30074d,d8ce44129bb..28c7f61a007 --- 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 @@@ -32,534 -31,487 +32,519 @@@ 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_FLOOR = new MathContext(0, RoundingMode.FLOOR); + 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 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 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_FLOOR = new MathContext(0, RoundingMode.FLOOR); - 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); - - q0 = rq0.createCopy(); - q1 = rq1.createCopy(); - q0.roundToMagnitude(0, MATH_CONTEXT_FLOOR); - q1.truncate(); - testDecimalQuantityBehavior(q0, q1); - - q0 = rq0.createCopy(); - q1 = rq1.createCopy(); - q0.truncate(); - q1.roundToMagnitude(0, MATH_CONTEXT_FLOOR); - 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); + @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()); } - } - 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 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 (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); + @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); - } - } - - 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()); - } - - @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); - } - } - - @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, ""); - } - + @Test + public void testFitsInLong() { + DecimalQuantity_DualStorageBCD quantity = new DecimalQuantity_DualStorageBCD(); + quantity.setToInt(0); + assertTrue("Zero should fit", quantity.fitsInLong()); + quantity.setToInt(42); + assertTrue("Small int should fit", quantity.fitsInLong()); + quantity.setToDouble(0.1); + assertFalse("Fraction should not fit", quantity.fitsInLong()); + quantity.setToDouble(42.1); + assertFalse("Fraction should not fit", quantity.fitsInLong()); + quantity.setToLong(1000000); + assertTrue("Large low-precision int should fit", quantity.fitsInLong()); + quantity.setToLong(1000000000000000000L); + assertTrue("10^19 should fit", quantity.fitsInLong()); + quantity.setToLong(1234567890123456789L); + assertTrue("A number between 10^19 and max long should fit", quantity.fitsInLong()); + quantity.setToLong(9223372026854775808L); + assertTrue("A number less than max long but with similar digits should fit", quantity.fitsInLong()); + quantity.setToLong(9223372036854775806L); + assertTrue("One less than max long should fit", quantity.fitsInLong()); + quantity.setToLong(9223372036854775807L); + assertTrue("Max long should fit", quantity.fitsInLong()); + quantity.setToBigInteger(new BigInteger("9223372036854775808")); + assertFalse("One greater than max long long should not fit", quantity.fitsInLong()); + quantity.setToBigInteger(new BigInteger("9223372046854775806")); + assertFalse("A number between max long and 10^20 should not fit", quantity.fitsInLong()); + quantity.setToBigInteger(new BigInteger("10000000000000000000")); + assertFalse("10^20 should not fit", quantity.fitsInLong()); + } + - 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 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, 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 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); - } + 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); + } }