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);
}
*/
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.
+ *
+ * <p>
+ * Example input: "-$x"; example output: "'-'$x"
+ *
+ * @param input
+ * The string to be escaped.
+ * @param output
+ * The string builder to which to append the escaped string.
+ * @return The number of chars (UTF-16 code units) appended to the output.
+ */
+ public static int escape(CharSequence input, StringBuilder output) {
+ if (input == null)
+ return 0;
+ int state = STATE_BASE;
+ int offset = 0;
+ int startLength = output.length();
+ for (; offset < input.length();) {
+ int cp = Character.codePointAt(input, offset);
+
+ switch (cp) {
+ case '\'':
+ output.append("''");
+ break;
- return length;
- }
+ 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.
- *
- * <p>Example input: "-$x"; example output: "'-'$x"
- *
- * @param input The string to be escaped.
- * @param output The string builder to which to append the escaped string.
- * @return The number of chars (UTF-16 code units) appended to the output.
- */
- public static int escape(CharSequence input, StringBuilder output) {
- if (input == null) return 0;
- int state = STATE_BASE;
- int offset = 0;
- int startLength = output.length();
- for (; offset < input.length(); ) {
- int cp = Character.codePointAt(input, offset);
-
- switch (cp) {
- case '\'':
- output.append("''");
- break;
-
- case '-':
- case '+':
- case '%':
- case '‰':
- case '¤':
- if (state == STATE_BASE) {
- output.append('\'');
- output.appendCodePoint(cp);
- state = STATE_INSIDE_QUOTE;
- } else {
- output.appendCodePoint(cp);
- }
- break;
+ 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.
- *
- * <p>Example input: "'-'¤x"; example output: "-$x"
- *
- * @param affixPattern The original string to be unescaped.
- * @param output The NumberStringBuilder to mutate with the result.
- * @param position The index into the NumberStringBuilder to insert the the string.
- * @param provider An object to generate locale symbols.
- * @return The length of the string added to affixPattern.
- */
- public static int unescape(
- CharSequence affixPattern,
- NumberStringBuilder output,
- int position,
- SymbolProvider provider) {
- assert affixPattern != null;
- int length = 0;
- long tag = 0L;
- while (hasNext(tag, affixPattern)) {
- tag = nextToken(tag, affixPattern);
- int typeOrCp = getTypeOrCp(tag);
- if (typeOrCp == TYPE_CURRENCY_OVERFLOW) {
- // Don't go to the provider for this special case
- length += output.insertCodePoint(position + length, 0xFFFD, NumberFormat.Field.CURRENCY);
- } else if (typeOrCp < 0) {
- length += output.insert(position + length, provider.getSymbol(typeOrCp), getFieldForType(typeOrCp));
- } else {
- length += output.insertCodePoint(position + length, typeOrCp, null);
- }
+ /**
+ * Executes the unescape state machine. Replaces the unquoted characters "-", "+", "%", "‰", and "¤"
+ * with the corresponding symbols provided by the {@link SymbolProvider}, and inserts the result into
+ * the NumberStringBuilder at the requested location.
+ *
+ * <p>
+ * Example input: "'-'¤x"; example output: "-$x"
+ *
+ * @param affixPattern
+ * The original string to be unescaped.
+ * @param output
+ * The NumberStringBuilder to mutate with the result.
+ * @param position
+ * The index into the NumberStringBuilder to insert the the string.
+ * @param provider
+ * An object to generate locale symbols.
+ * @return The length of the string added to affixPattern.
+ */
+ public static int unescape(
+ CharSequence affixPattern,
+ NumberStringBuilder output,
+ int position,
+ SymbolProvider provider) {
+ assert affixPattern != null;
+ int length = 0;
+ long tag = 0L;
+ while (hasNext(tag, affixPattern)) {
+ tag = nextToken(tag, affixPattern);
+ int typeOrCp = getTypeOrCp(tag);
+ if (typeOrCp == TYPE_CURRENCY_OVERFLOW) {
+ // Don't go to the provider for this special case
+ length += output.insertCodePoint(position + length, 0xFFFD, NumberFormat.Field.CURRENCY);
+ } else if (typeOrCp < 0) {
+ length += output.insert(position + length,
+ provider.getSymbol(typeOrCp),
+ getFieldForType(typeOrCp));
+ } else {
+ length += output.insertCodePoint(position + length, typeOrCp, null);
+ }
+ }
+ return length;
}
- return length;
- }
- /**
- * Sames as {@link #unescape}, but only calculates the code point count. More efficient than {@link #unescape}
- * if you only need the length but not the string itself.
- *
- * @param affixPattern The original string to be unescaped.
- * @param provider An object to generate locale symbols.
- * @return The number of code points in the unescaped string.
- */
- public static int unescapedCodePointCount(CharSequence affixPattern, SymbolProvider provider) {
- int length = 0;
- long tag = 0L;
- while (hasNext(tag, affixPattern)) {
- tag = nextToken(tag, affixPattern);
- int typeOrCp = getTypeOrCp(tag);
- if (typeOrCp == TYPE_CURRENCY_OVERFLOW) {
- length += 1;
- } else if (typeOrCp < 0) {
- CharSequence symbol = provider.getSymbol(typeOrCp);
- length += Character.codePointCount(symbol, 0, symbol.length());
- } else {
- length += 1;
- }
+ /**
+ * Sames as {@link #unescape}, but only calculates the code point count. More efficient than
+ * {@link #unescape} if you only need the length but not the string itself.
+ *
+ * @param affixPattern
+ * The original string to be unescaped.
+ * @param provider
+ * An object to generate locale symbols.
+ * @return The number of code points in the unescaped string.
+ */
+ public static int unescapedCodePointCount(CharSequence affixPattern, SymbolProvider provider) {
+ int length = 0;
+ long tag = 0L;
+ while (hasNext(tag, affixPattern)) {
+ tag = nextToken(tag, affixPattern);
+ int typeOrCp = getTypeOrCp(tag);
+ if (typeOrCp == TYPE_CURRENCY_OVERFLOW) {
+ length += 1;
+ } else if (typeOrCp < 0) {
+ CharSequence symbol = provider.getSymbol(typeOrCp);
+ length += Character.codePointCount(symbol, 0, symbol.length());
+ } else {
+ length += 1;
+ }
+ }
+ return length;
}
- return length;
- }
- /**
- * Checks whether the given affix pattern contains at least one token of the given type, which is
- * one of the constants "TYPE_" in {@link AffixUtils}.
- *
- * @param affixPattern The affix pattern to check.
- * @param type The token type.
- * @return true if the affix pattern contains the given token type; false otherwise.
- */
- public static boolean containsType(CharSequence affixPattern, int type) {
- if (affixPattern == null || affixPattern.length() == 0) {
+ /**
+ * Checks whether the given affix pattern contains at least one token of the given type, which is one
+ * of the constants "TYPE_" in {@link AffixUtils}.
+ *
+ * @param affixPattern
+ * The affix pattern to check.
+ * @param type
+ * The token type.
+ * @return true if the affix pattern contains the given token type; false otherwise.
+ */
+ public static boolean containsType(CharSequence affixPattern, int type) {
+ if (affixPattern == null || affixPattern.length() == 0) {
+ return false;
+ }
+ long tag = 0L;
+ while (hasNext(tag, affixPattern)) {
+ tag = nextToken(tag, affixPattern);
+ if (getTypeOrCp(tag) == type) {
+ return true;
+ }
+ }
return false;
}
- long tag = 0L;
- while (hasNext(tag, affixPattern)) {
- tag = nextToken(tag, affixPattern);
- if (getTypeOrCp(tag) == type) {
- return true;
- }
- }
- 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);
- }
- /**
- * 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;
+ /**
+ * 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.
+ *
+ * <p>
+ * 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;
+ 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 <code>TYPE_</code>
- * constants, such as {@link #TYPE_MINUS_SIGN}. If greater than or equal to zero, a literal
- * code point.
- */
- public static int getTypeOrCp(long tag) {
- assert tag >= 0;
- int type = getType(tag);
- return (type == TYPE_CODEPOINT) ? getCodePoint(tag) : -type;
- }
+ /**
+ * This function helps determine the identity of the token consumed by {@link #nextToken}. Converts
+ * from a bitmask tag, based on a call to {@link #nextToken}, to its corresponding symbol type or
+ * code point.
+ *
+ * @param tag
+ * The bitmask tag of the current token, as returned by {@link #nextToken}.
+ * @return If less than zero, a symbol type corresponding to one of the <code>TYPE_</code> constants,
+ * such as {@link #TYPE_MINUS_SIGN}. If greater than or equal to zero, a literal code point.
+ */
+ public static int getTypeOrCp(long tag) {
+ assert tag >= 0;
+ int type = getType(tag);
+ return (type == TYPE_CODEPOINT) ? getCodePoint(tag) : -type;
+ }
- /**
- * Encodes the given values into a 64-bit tag.
- *
- * <ul>
- * <li>Bits 0-31 => offset (int32)
- * <li>Bits 32-35 => type (uint4)
- * <li>Bits 36-39 => state (uint4)
- * <li>Bits 40-60 => code point (uint21)
- * <li>Bits 61-63 => unused
- * </ul>
- */
- private static long makeTag(int offset, int type, int state, int cp) {
- long tag = 0L;
- tag |= offset;
- tag |= (-(long) type) << 32;
- tag |= ((long) state) << 36;
- tag |= ((long) cp) << 40;
- assert tag >= 0;
- return tag;
- }
+ /**
+ * Encodes the given values into a 64-bit tag.
+ *
+ * <ul>
+ * <li>Bits 0-31 => offset (int32)
+ * <li>Bits 32-35 => type (uint4)
+ * <li>Bits 36-39 => state (uint4)
+ * <li>Bits 40-60 => code point (uint21)
+ * <li>Bits 61-63 => unused
+ * </ul>
+ */
+ private static long makeTag(int offset, int type, int state, int cp) {
+ long tag = 0L;
+ tag |= offset;
+ tag |= (-(long) type) << 32;
+ tag |= ((long) state) << 36;
+ tag |= ((long) cp) << 40;
+ assert tag >= 0;
+ return tag;
+ }
- static int getOffset(long tag) {
- return (int) (tag & 0xffffffff);
- }
+ static int 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);
+ }
}
* An interface representing a number to be processed by the decimal formatting pipeline. Includes
* methods for rounding, plural rules, and decimal digit extraction.
*
- * <p>By design, this is NOT IMMUTABLE and NOT THREAD SAFE. It is intended to be an intermediate
- * object holding state during a pass through the decimal formatting pipeline.
+ * <p>
+ * By design, this is NOT IMMUTABLE and NOT THREAD SAFE. It is intended to be an intermediate object
+ * holding state during a pass through the decimal formatting pipeline.
*
- * <p>Implementations of this interface are free to use any internal storage mechanism.
+ * <p>
+ * Implementations of this interface are free to use any internal storage mechanism.
*
- * <p>TODO: Should I change this to an abstract class so that logic for min/max digits doesn't need
- * to be copied to every implementation?
+ * <p>
+ * TODO: Should I change this to an abstract class so that logic for min/max digits doesn't need to be
+ * copied to every implementation?
*/
public interface DecimalQuantity extends PluralRules.IFixedDecimal {
- /**
- * Sets the minimum and maximum integer digits that this {@link DecimalQuantity} should generate.
- * This method does not perform rounding.
- *
- * @param minInt The minimum number of integer digits.
- * @param maxInt The maximum number of integer digits.
- */
- public void setIntegerLength(int minInt, int maxInt);
-
- /**
- * Sets the minimum and maximum fraction digits that this {@link DecimalQuantity} should generate.
- * This method does not perform rounding.
- *
- * @param minFrac The minimum number of fraction digits.
- * @param maxFrac The maximum number of fraction digits.
- */
- public void setFractionLength(int minFrac, int maxFrac);
-
- /**
- * Rounds the number to a specified interval, such as 0.05.
- *
- * <p>If rounding to a power of ten, use the more efficient {@link #roundToMagnitude} instead.
- *
- * @param roundingInterval The increment to which to round.
- * @param mathContext The {@link MathContext} to use if rounding is necessary. Undefined behavior
- * if null.
- */
- public void roundToIncrement(BigDecimal roundingInterval, MathContext mathContext);
-
- /**
- * Rounds the number to a specified magnitude (power of ten).
- *
- * @param roundingMagnitude The power of ten to which to round. For example, a value of -2 will
- * round to 2 decimal places.
- * @param mathContext The {@link MathContext} to use if rounding is necessary. Undefined behavior
- * if null.
- */
- public void roundToMagnitude(int roundingMagnitude, MathContext mathContext);
-
- /**
- * Rounds the number to an infinite number of decimal points. This has no effect except for
- * forcing the double in {@link DecimalQuantity_AbstractBCD} to adopt its exact representation.
- */
- public void roundToInfinity();
-
- /**
- * 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.
+ *
+ * <p>
+ * If rounding to a power of ten, use the more efficient {@link #roundToMagnitude} instead.
+ *
+ * @param roundingInterval
+ * The increment to which to round.
+ * @param mathContext
+ * The {@link MathContext} to use if rounding is necessary. Undefined behavior if null.
+ */
+ public void roundToIncrement(BigDecimal roundingInterval, MathContext mathContext);
+
+ /**
+ * Rounds the number to a specified magnitude (power of ten).
+ *
+ * @param roundingMagnitude
+ * The power of ten to which to round. For example, a value of -2 will round to 2 decimal
+ * places.
+ * @param mathContext
+ * The {@link MathContext} to use if rounding is necessary. Undefined behavior if null.
+ */
+ public void roundToMagnitude(int roundingMagnitude, MathContext mathContext);
+
+ /**
+ * Rounds the number to an infinite number of decimal points. This has no effect except for forcing
+ * the double in {@link DecimalQuantity_AbstractBCD} to adopt its exact representation.
+ */
+ public void roundToInfinity();
+
++ /**
++ * 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);
}
*/
public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
- /**
- * The power of ten corresponding to the least significant digit in the BCD. For example, if this
- * object represents the number "3.14", the BCD will be "0x314" and the scale will be -2.
- *
- * <p>Note that in {@link java.math.BigDecimal}, the scale is defined differently: the number of
- * digits after the decimal place, which is the negative of our definition of scale.
- */
- protected int scale;
-
- /**
- * The number of digits in the BCD. For example, "1007" has BCD "0x1007" and precision 4. A long
- * cannot represent precisions greater than 16.
- *
- * <p>This value must be re-calculated whenever the value in bcd changes by using {@link
- * #computePrecisionAndCompact()}.
- */
- protected int precision;
-
- /**
- * A bitmask of properties relating to the number represented by this object.
- *
- * @see #NEGATIVE_FLAG
- * @see #INFINITY_FLAG
- * @see #NAN_FLAG
- */
- protected byte flags;
-
- protected static final int NEGATIVE_FLAG = 1;
- protected static final int INFINITY_FLAG = 2;
- protected static final int NAN_FLAG = 4;
-
- // The following three fields relate to the double-to-ascii fast path algorithm.
- // When a double is given to DecimalQuantityBCD, it is converted to using a fast algorithm. The
- // fast algorithm guarantees correctness to only the first ~12 digits of the double. The process
- // of rounding the number ensures that the converted digits are correct, falling back to a slow-
- // path algorithm if required. Therefore, if a DecimalQuantity is constructed from a double, it
- // is *required* that roundToMagnitude(), roundToIncrement(), or roundToInfinity() is called. If
- // you don't round, assertions will fail in certain other methods if you try calling them.
-
- /**
- * The original number provided by the user and which is represented in BCD. Used when we need to
- * re-compute the BCD for an exact double representation.
- */
- protected double origDouble;
+ /**
+ * The power of ten corresponding to the least significant digit in the BCD. For example, if this
+ * object represents the number "3.14", the BCD will be "0x314" and the scale will be -2.
+ *
+ * <p>
+ * Note that in {@link java.math.BigDecimal}, the scale is defined differently: the number of digits
+ * after the decimal place, which is the negative of our definition of scale.
+ */
+ protected int scale;
+
+ /**
- * The number of digits in the BCD. For example, "1007" has BCD "0x1007" and precision 4. The maximum
- * precision is 16 since a long can hold only 16 digits.
++ * The number of digits in the BCD. For example, "1007" has BCD "0x1007" and precision 4. A long
++ * cannot represent precisions greater than 16.
+ *
+ * <p>
+ * This value must be re-calculated whenever the value in bcd changes by using
+ * {@link #computePrecisionAndCompact()}.
+ */
+ protected int precision;
+
+ /**
+ * A bitmask of properties relating to the number represented by this object.
+ *
+ * @see #NEGATIVE_FLAG
+ * @see #INFINITY_FLAG
+ * @see #NAN_FLAG
+ */
+ protected byte flags;
+
+ protected static final int NEGATIVE_FLAG = 1;
+ protected static final int INFINITY_FLAG = 2;
+ protected static final int NAN_FLAG = 4;
+
+ // The following three fields relate to the double-to-ascii fast path algorithm.
+ // When a double is given to DecimalQuantityBCD, it is converted to using a fast algorithm. The
+ // fast algorithm guarantees correctness to only the first ~12 digits of the double. The process
+ // of rounding the number ensures that the converted digits are correct, falling back to a slow-
+ // path algorithm if required. Therefore, if a DecimalQuantity is constructed from a double, it
+ // is *required* that roundToMagnitude(), roundToIncrement(), or roundToInfinity() is called. If
+ // you don't round, assertions will fail in certain other methods if you try calling them.
+
+ /**
+ * The original number provided by the user and which is represented in BCD. Used when we need to
+ * re-compute the BCD for an exact double representation.
+ */
+ protected double origDouble;
+
+ /**
+ * The change in magnitude relative to the original double. Used when we need to re-compute the BCD
+ * for an exact double representation.
+ */
+ protected int origDelta;
+
+ /**
+ * Whether the value in the BCD comes from the double fast path without having been rounded to ensure
+ * correctness
+ */
+ protected boolean isApproximate;
+
+ // Four positions: left optional '(', left required '[', right required ']', right optional ')'.
+ // These four positions determine which digits are displayed in the output string. They do NOT
+ // affect rounding. These positions are internal-only and can be specified only by the public
+ // endpoints like setFractionLength, setIntegerLength, and setSignificantDigits, among others.
+ //
+ // * Digits between lReqPos and rReqPos are in the "required zone" and are always displayed.
+ // * Digits between lOptPos and rOptPos but outside the required zone are in the "optional zone"
+ // and are displayed unless they are trailing off the left or right edge of the number and
+ // have a numerical value of zero. In order to be "trailing", the digits need to be beyond
+ // the decimal point in their respective directions.
+ // * Digits outside of the "optional zone" are never displayed.
+ //
+ // See the table below for illustrative examples.
+ //
+ // +---------+---------+---------+---------+------------+------------------------+--------------+
+ // | lOptPos | lReqPos | rReqPos | rOptPos | number | positions | en-US string |
+ // +---------+---------+---------+---------+------------+------------------------+--------------+
+ // | 5 | 2 | -1 | -5 | 1234.567 | ( 12[34.5]67 ) | 1,234.567 |
+ // | 3 | 2 | -1 | -5 | 1234.567 | 1(2[34.5]67 ) | 234.567 |
+ // | 3 | 2 | -1 | -2 | 1234.567 | 1(2[34.5]6)7 | 234.56 |
+ // | 6 | 4 | 2 | -5 | 123456789. | 123(45[67]89. ) | 456,789. |
+ // | 6 | 4 | 2 | 1 | 123456789. | 123(45[67]8)9. | 456,780. |
+ // | -1 | -1 | -3 | -4 | 0.123456 | 0.1([23]4)56 | .0234 |
+ // | 6 | 4 | -2 | -2 | 12.3 | ( [ 12.3 ]) | 0012.30 |
+ // +---------+---------+---------+---------+------------+------------------------+--------------+
+ //
+ protected int lOptPos = Integer.MAX_VALUE;
+ protected int lReqPos = 0;
+ protected int rReqPos = 0;
+ protected int rOptPos = Integer.MIN_VALUE;
+
+ @Override
+ public void copyFrom(DecimalQuantity _other) {
+ copyBcdFrom(_other);
+ DecimalQuantity_AbstractBCD other = (DecimalQuantity_AbstractBCD) _other;
+ lOptPos = other.lOptPos;
+ lReqPos = other.lReqPos;
+ rReqPos = other.rReqPos;
+ rOptPos = other.rOptPos;
+ scale = other.scale;
+ precision = other.precision;
+ flags = other.flags;
+ origDouble = other.origDouble;
+ origDelta = other.origDelta;
+ isApproximate = other.isApproximate;
+ }
- /**
- * 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<precision; p++) {
+ byte digit = getDigitPos(18-p);
+ if (digit < INT64_BCD[p]) {
+ return true;
+ } else if (digit > 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.
+ *
+ * <p>The primary use of this method is to construct numbers during a parsing loop. It allows
+ * parsing to take advantage of the digit list infrastructure primarily designed for formatting.
+ *
+ * @param value The digit to append.
+ * @param leadingZeros The number of zeros to append before the digit. For example, if the value
+ * in this instance starts as 12.3, and you append a 4 with 1 leading zero, the value becomes
+ * 12.304.
+ * @param appendAsInteger If true, increase the magnitude of existing digits to make room for the
+ * new digit. If false, append to the end like a fraction digit. If true, there must not be
+ * any fraction digits already in the number.
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ public void appendDigit(byte value, int leadingZeros, boolean appendAsInteger) {
+ assert leadingZeros >= 0;
++=======
+ /**
+ * Appends a digit, optionally with one or more leading zeros, to the end of the value represented by
+ * this DecimalQuantity.
+ *
+ * <p>
+ * The primary use of this method is to construct numbers during a parsing loop. It allows parsing to
+ * take advantage of the digit list infrastructure primarily designed for formatting.
+ *
+ * @param value
+ * The digit to append.
+ * @param leadingZeros
+ * The number of zeros to append before the digit. For example, if the value in this
+ * instance starts as 12.3, and you append a 4 with 1 leading zero, the value becomes
+ * 12.304.
+ * @param appendAsInteger
+ * If true, increase the magnitude of existing digits to make room for the new digit. If
+ * false, append to the end like a fraction digit. If true, there must not be any fraction
+ * digits already in the number.
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ public void appendDigit(byte value, int leadingZeros, boolean appendAsInteger) {
+ assert leadingZeros >= 0;
++>>>>>>> .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();
+ }
-
- /**
- * Sets the internal representation to zero. Clears any values stored in scale, precision,
- * hasDouble, origDouble, origDelta, and BCD data.
- */
- protected abstract void setBcdToZero();
-
- /**
- * Sets the internal BCD state to represent the value in the given int. The int is guaranteed to
- * be either positive. The internal state is guaranteed to be empty when this method is called.
- *
- * @param n The value to consume.
- */
- protected abstract void readIntToBcd(int input);
-
- /**
- * Sets the internal BCD state to represent the value in the given long. The long is guaranteed to
- * be either positive. The internal state is guaranteed to be empty when this method is called.
- *
- * @param n The value to consume.
- */
- protected abstract void readLongToBcd(long input);
-
- /**
- * Sets the internal BCD state to represent the value in the given BigInteger. The BigInteger is
- * guaranteed to be positive, and it is guaranteed to be larger than Long.MAX_VALUE. The internal
- * state is guaranteed to be empty when this method is called.
- *
- * @param n The value to consume.
- */
- protected abstract void readBigIntegerToBcd(BigInteger input);
-
- /**
- * Returns a BigDecimal encoding the internal BCD value.
- *
- * @return A BigDecimal representation of the internal BCD.
- */
- protected abstract BigDecimal bcdToBigDecimal();
-
- protected abstract void copyBcdFrom(DecimalQuantity _other);
-
- /**
- * Removes trailing zeros from the BCD (adjusting the scale as required) and then computes the
- * precision. The precision is the number of digits in the number up through the greatest nonzero
- * digit.
- *
- * <p>This method must always be called when bcd changes in order for assumptions to be correct in
- * methods like {@link #fractionCount()}.
- */
- protected abstract void compact();
+ /**
+ * 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);
++=======
+ 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.
+ *
+ * <p>
+ * This method must always be called when bcd changes in order for assumptions to be correct in
+ * methods like {@link #fractionCount()}.
+ */
+ protected abstract void compact();
}
* @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;
* @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();
// 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();
+ }
}
@RunWith(JUnit4.class)
public class DecimalQuantityTest extends TestFmwk {
- @Ignore
- @Test
- public void testBehavior() throws ParseException {
-
- // Make a list of several formatters to test the behavior of DecimalQuantity.
- List<LocalizedNumberFormatter> formats = new ArrayList<LocalizedNumberFormatter>();
-
- DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
-
- DecimalFormatProperties properties = new DecimalFormatProperties();
- formats.add(NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
-
- properties =
- new DecimalFormatProperties()
- .setMinimumSignificantDigits(3)
- .setMaximumSignificantDigits(3)
- .setCompactStyle(CompactStyle.LONG);
- formats.add(NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
-
- properties =
- new DecimalFormatProperties()
- .setMinimumExponentDigits(1)
- .setMaximumIntegerDigits(3)
- .setMaximumFractionDigits(1);
- formats.add(NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
-
- properties = new DecimalFormatProperties().setRoundingIncrement(new BigDecimal("0.5"));
- formats.add(NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
-
- String[] cases = {
- "1.0",
- "2.01",
- "1234.56",
- "3000.0",
- "0.00026418",
- "0.01789261",
- "468160.0",
- "999000.0",
- "999900.0",
- "999990.0",
- "0.0",
- "12345678901.0",
- "-5193.48",
- };
-
- String[] hardCases = {
- "9999999999999900.0",
- "789000000000000000000000.0",
- "789123123567853156372158.0",
- "987654321987654321987654321987654321987654311987654321.0",
- };
-
- String[] doubleCases = {
- "512.0000000000017",
- "4095.9999999999977",
- "4095.999999999998",
- "4095.9999999999986",
- "4095.999999999999",
- "4095.9999999999995",
- "4096.000000000001",
- "4096.000000000002",
- "4096.000000000003",
- "4096.000000000004",
- "4096.000000000005",
- "4096.0000000000055",
- "4096.000000000006",
- "4096.000000000007",
- };
-
- int i = 0;
- for (String str : cases) {
- testDecimalQuantity(i++, str, formats, 0);
+ @Ignore
+ @Test
+ public void testBehavior() throws ParseException {
+
+ // Make a list of several formatters to test the behavior of DecimalQuantity.
+ List<LocalizedNumberFormatter> formats = new ArrayList<LocalizedNumberFormatter>();
+
+ DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
+
+ DecimalFormatProperties properties = new DecimalFormatProperties();
+ formats.add(
+ NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
+
+ properties = new DecimalFormatProperties().setMinimumSignificantDigits(3)
+ .setMaximumSignificantDigits(3).setCompactStyle(CompactStyle.LONG);
+ formats.add(
+ NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
+
+ properties = new DecimalFormatProperties().setMinimumExponentDigits(1).setMaximumIntegerDigits(3)
+ .setMaximumFractionDigits(1);
+ formats.add(
+ NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
+
+ properties = new DecimalFormatProperties().setRoundingIncrement(new BigDecimal("0.5"));
+ formats.add(
+ NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
+
+ String[] cases = {
+ "1.0",
+ "2.01",
+ "1234.56",
+ "3000.0",
+ "0.00026418",
+ "0.01789261",
+ "468160.0",
+ "999000.0",
+ "999900.0",
+ "999990.0",
+ "0.0",
+ "12345678901.0",
+ "-5193.48", };
+
+ String[] hardCases = {
+ "9999999999999900.0",
+ "789000000000000000000000.0",
+ "789123123567853156372158.0",
+ "987654321987654321987654321987654321987654311987654321.0", };
+
+ String[] doubleCases = {
+ "512.0000000000017",
+ "4095.9999999999977",
+ "4095.999999999998",
+ "4095.9999999999986",
+ "4095.999999999999",
+ "4095.9999999999995",
+ "4096.000000000001",
+ "4096.000000000002",
+ "4096.000000000003",
+ "4096.000000000004",
+ "4096.000000000005",
+ "4096.0000000000055",
+ "4096.000000000006",
+ "4096.000000000007", };
+
+ int i = 0;
+ for (String str : cases) {
+ testDecimalQuantity(i++, str, formats, 0);
+ }
+
+ i = 0;
+ for (String str : hardCases) {
+ testDecimalQuantity(i++, str, formats, 1);
+ }
+
+ i = 0;
+ for (String str : doubleCases) {
+ testDecimalQuantity(i++, str, formats, 2);
+ }
}
- i = 0;
- for (String str : hardCases) {
- testDecimalQuantity(i++, str, formats, 1);
+ static void testDecimalQuantity(
+ int t,
+ String str,
+ List<LocalizedNumberFormatter> formats,
+ int mode) {
+ if (mode == 2) {
+ assertEquals("Double is not valid", Double.toString(Double.parseDouble(str)), str);
+ }
+
+ List<DecimalQuantity> qs = new ArrayList<DecimalQuantity>();
+ BigDecimal d = new BigDecimal(str);
+ qs.add(new DecimalQuantity_SimpleStorage(d));
+ if (mode == 0)
+ qs.add(new DecimalQuantity_64BitBCD(d));
+ qs.add(new DecimalQuantity_ByteArrayBCD(d));
+ qs.add(new DecimalQuantity_DualStorageBCD(d));
+
+ if (new BigDecimal(Double.toString(d.doubleValue())).compareTo(d) == 0) {
+ double dv = d.doubleValue();
+ qs.add(new DecimalQuantity_SimpleStorage(dv));
+ if (mode == 0)
+ qs.add(new DecimalQuantity_64BitBCD(dv));
+ qs.add(new DecimalQuantity_ByteArrayBCD(dv));
+ qs.add(new DecimalQuantity_DualStorageBCD(dv));
+ }
+
+ if (new BigDecimal(Long.toString(d.longValue())).compareTo(d) == 0) {
+ double lv = d.longValue();
+ qs.add(new DecimalQuantity_SimpleStorage(lv));
+ if (mode == 0)
+ qs.add(new DecimalQuantity_64BitBCD(lv));
+ qs.add(new DecimalQuantity_ByteArrayBCD(lv));
+ qs.add(new DecimalQuantity_DualStorageBCD(lv));
+ }
+
+ testDecimalQuantityExpectedOutput(qs.get(0), str);
+
+ if (qs.size() == 1) {
+ return;
+ }
+
+ for (int i = 1; i < qs.size(); i++) {
+ DecimalQuantity q0 = qs.get(0);
+ DecimalQuantity q1 = qs.get(i);
+ testDecimalQuantityExpectedOutput(q1, str);
+ testDecimalQuantityRounding(q0, q1);
+ testDecimalQuantityRoundingInterval(q0, q1);
+ testDecimalQuantityMath(q0, q1);
+ testDecimalQuantityWithFormats(q0, q1, formats);
+ }
}
- i = 0;
- for (String str : doubleCases) {
- testDecimalQuantity(i++, str, formats, 2);
+ private static void testDecimalQuantityExpectedOutput(DecimalQuantity rq, String expected) {
+ DecimalQuantity q0 = rq.createCopy();
+ // Force an accurate double
+ q0.roundToInfinity();
+ q0.setIntegerLength(1, Integer.MAX_VALUE);
+ q0.setFractionLength(1, Integer.MAX_VALUE);
+ String actual = q0.toPlainString();
+ assertEquals("Unexpected output from simple string conversion (" + q0 + ")", expected, actual);
}
- }
- static void testDecimalQuantity(int t, String str, List<LocalizedNumberFormatter> formats, int mode) {
- if (mode == 2) {
- assertEquals("Double is not valid", Double.toString(Double.parseDouble(str)), str);
+ private static final MathContext MATH_CONTEXT_HALF_EVEN = new MathContext(0, RoundingMode.HALF_EVEN);
+ private static final MathContext MATH_CONTEXT_CEILING = new MathContext(0, RoundingMode.CEILING);
++ private static final MathContext MATH_CONTEXT_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<DecimalQuantity> qs = new ArrayList<DecimalQuantity>();
- BigDecimal d = new BigDecimal(str);
- qs.add(new DecimalQuantity_SimpleStorage(d));
- if (mode == 0) qs.add(new DecimalQuantity_64BitBCD(d));
- qs.add(new DecimalQuantity_ByteArrayBCD(d));
- qs.add(new DecimalQuantity_DualStorageBCD(d));
-
- if (new BigDecimal(Double.toString(d.doubleValue())).compareTo(d) == 0) {
- double dv = d.doubleValue();
- qs.add(new DecimalQuantity_SimpleStorage(dv));
- if (mode == 0) qs.add(new DecimalQuantity_64BitBCD(dv));
- qs.add(new DecimalQuantity_ByteArrayBCD(dv));
- qs.add(new DecimalQuantity_DualStorageBCD(dv));
+ private static void testDecimalQuantityRoundingInterval(DecimalQuantity rq0, DecimalQuantity rq1) {
+ DecimalQuantity q0 = rq0.createCopy();
+ DecimalQuantity q1 = rq1.createCopy();
+ q0.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_HALF_EVEN);
+ q1.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_HALF_EVEN);
+ testDecimalQuantityBehavior(q0, q1);
+
+ q0 = rq0.createCopy();
+ q1 = rq1.createCopy();
+ q0.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_CEILING);
+ q1.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_CEILING);
+ testDecimalQuantityBehavior(q0, q1);
}
- if (new BigDecimal(Long.toString(d.longValue())).compareTo(d) == 0) {
- double lv = d.longValue();
- qs.add(new DecimalQuantity_SimpleStorage(lv));
- if (mode == 0) qs.add(new DecimalQuantity_64BitBCD(lv));
- qs.add(new DecimalQuantity_ByteArrayBCD(lv));
- qs.add(new DecimalQuantity_DualStorageBCD(lv));
+ private static void testDecimalQuantityMath(DecimalQuantity rq0, DecimalQuantity rq1) {
+ DecimalQuantity q0 = rq0.createCopy();
+ DecimalQuantity q1 = rq1.createCopy();
+ q0.adjustMagnitude(-3);
+ q1.adjustMagnitude(-3);
+ testDecimalQuantityBehavior(q0, q1);
+
+ q0 = rq0.createCopy();
+ q1 = rq1.createCopy();
+ q0.multiplyBy(new BigDecimal("3.14159"));
+ q1.multiplyBy(new BigDecimal("3.14159"));
+ testDecimalQuantityBehavior(q0, q1);
}
- testDecimalQuantityExpectedOutput(qs.get(0), str);
-
- if (qs.size() == 1) {
- return;
+ private static void testDecimalQuantityWithFormats(
+ DecimalQuantity rq0,
+ DecimalQuantity rq1,
+ List<LocalizedNumberFormatter> formats) {
+ for (LocalizedNumberFormatter format : formats) {
+ DecimalQuantity q0 = rq0.createCopy();
+ DecimalQuantity q1 = rq1.createCopy();
+ String s1 = format.format(q0).toString();
+ String s2 = format.format(q1).toString();
+ assertEquals("Different output from formatter (" + q0 + ", " + q1 + ")", s1, s2);
+ }
}
- for (int i = 1; i < qs.size(); i++) {
- DecimalQuantity q0 = qs.get(0);
- DecimalQuantity q1 = qs.get(i);
- testDecimalQuantityExpectedOutput(q1, str);
- testDecimalQuantityRounding(q0, q1);
- testDecimalQuantityRoundingInterval(q0, q1);
- testDecimalQuantityMath(q0, q1);
- testDecimalQuantityWithFormats(q0, q1, formats);
+ private static void 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<LocalizedNumberFormatter> formats) {
- for (LocalizedNumberFormatter format : formats) {
- DecimalQuantity q0 = rq0.createCopy();
- DecimalQuantity q1 = rq1.createCopy();
- String s1 = format.format(q0).toString();
- String s2 = format.format(q1).toString();
- assertEquals("Different output from formatter (" + q0 + ", " + q1 + ")", s1, s2);
+ @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, "<DecimalQuantity 999:0:0:-999 long 0E0>");
+ fq.setToInt(51423);
+ assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 51423E0>");
+ fq.adjustMagnitude(-3);
+ assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 51423E-3>");
+ fq.setToLong(999999999999000L);
+ assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 999999999999E3>");
+ fq.setIntegerLength(2, 5);
+ assertToStringAndHealth(fq, "<DecimalQuantity 5:2:0:-999 long 999999999999E3>");
+ fq.setFractionLength(3, 6);
+ assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 999999999999E3>");
+ fq.setToDouble(987.654321);
+ assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
+ fq.roundToInfinity();
+ assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
+ fq.roundToIncrement(new BigDecimal("0.005"), MATH_CONTEXT_HALF_EVEN);
+ assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987655E-3>");
+ fq.roundToMagnitude(-2, MATH_CONTEXT_HALF_EVEN);
+ assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 98766E-2>");
}
- assertEquals("NaN check failed", Double.NaN, new DecimalQuantity_DualStorageBCD(Double.NaN).toDouble());
- assertEquals(
- "Inf check failed",
- Double.POSITIVE_INFINITY,
- new DecimalQuantity_DualStorageBCD(Double.POSITIVE_INFINITY).toDouble());
- assertEquals(
- "-Inf check failed",
- Double.NEGATIVE_INFINITY,
- new DecimalQuantity_DualStorageBCD(Double.NEGATIVE_INFINITY).toDouble());
-
- // Generate random doubles
- String alert = "UNEXPECTED FAILURE: PLEASE REPORT THIS MESSAGE TO THE ICU TEAM: ";
- Random rnd = new Random();
- for (int i = 0; i < 10000; i++) {
- double d = Double.longBitsToDouble(rnd.nextLong());
- if (Double.isNaN(d) || Double.isInfinite(d)) continue;
- checkDoubleBehavior(d, false, alert);
- }
- }
-
- 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, "<DecimalQuantity 999:0:0:-999 long 0E0>");
- fq.setToInt(51423);
- assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 51423E0>");
- fq.adjustMagnitude(-3);
- assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 51423E-3>");
- fq.setToLong(999999999999000L);
- assertToStringAndHealth(fq, "<DecimalQuantity 999:0:0:-999 long 999999999999E3>");
- fq.setIntegerLength(2, 5);
- assertToStringAndHealth(fq, "<DecimalQuantity 5:2:0:-999 long 999999999999E3>");
- fq.setFractionLength(3, 6);
- assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 999999999999E3>");
- fq.setToDouble(987.654321);
- assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
- fq.roundToInfinity();
- assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987654321E-6>");
- fq.roundToIncrement(new BigDecimal("0.005"), MATH_CONTEXT_HALF_EVEN);
- assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 987655E-3>");
- fq.roundToMagnitude(-2, MATH_CONTEXT_HALF_EVEN);
- assertToStringAndHealth(fq, "<DecimalQuantity 5:2:-3:-6 long 98766E-2>");
- }
-
+ @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);
+ }
}