com.ibm.icu.text.DigitList Maven / Gradle / Ivy
Show all versions of icu4j Show documentation
/* ******************************************************************************* * Copyright (C) 1996-2011, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* */ package com.ibm.icu.text; import java.math.BigInteger; /** *
representing the value stored in this *DigitList
handles the transcoding between numeric values and * strings of characters. It only represents non-negative numbers. The * division of labor betweenDigitList
and *DecimalFormat
is thatDigitList
handles the radix * 10 representation issues and numeric conversion, including rounding; *DecimalFormat
handles the locale-specific issues such as * positive and negative representation, digit grouping, decimal point, * currency, and so on. * *A
DigitList
is a representation of a finite numeric value. *DigitList
objects do not representNaN
or infinite * values. ADigitList
value can be converted to a *BigDecimal
without loss of precision. Conversion to other * numeric formats may involve loss of precision, depending on the specific * value. * *The
DigitList
representation consists of a string of * characters, which are the digits radix 10, from '0' to '9'. It also has a * base 10 exponent associated with it. The value represented by a *DigitList
object can be computed by mulitplying the fraction * f, where 0 <= f < 1, derived by placing all the digits of * the list to the right of the decimal point, by 10^exponent. * * @see java.util.Locale * @see java.text.Format * @see NumberFormat * @see DecimalFormat * @see java.text.ChoiceFormat * @see java.text.MessageFormat * @version 1.18 08/12/98 * @author Mark Davis, Alan Liu * */ final class DigitList { /** * The maximum number of significant digits in an IEEE 754 double, that * is, in a Java double. This must not be increased, or garbage digits * will be generated, and should not be decreased, or accuracy will be lost. */ public static final int MAX_LONG_DIGITS = 19; // == Long.toString(Long.MAX_VALUE).length() public static final int DBL_DIG = 17; /** * These data members are intentionally public and can be set directly. * * The value represented is given by placing the decimal point before * digits[decimalAt]. If decimalAt is < 0, then leading zeros between * the decimal point and the first nonzero digit are implied. If decimalAt * is > count, then trailing zeros between the digits[count-1] and the * decimal point are implied. * * Equivalently, the represented value is given by f * 10^decimalAt. Here * f is a value 0.1 <= f < 1 arrived at by placing the digits in Digits to * the right of the decimal. * * DigitList is normalized, so if it is non-zero, figits[0] is non-zero. We * don't allow denormalized numbers because our exponent is effectively of * unlimited magnitude. The count value contains the number of significant * digits present in digits[]. * * Zero is represented by any DigitList with count == 0 or with each digits[i] * for all i <= count == '0'. */ public int decimalAt = 0; public int count = 0; public byte[] digits = new byte[MAX_LONG_DIGITS]; private final void ensureCapacity(int digitCapacity, int digitsToCopy) { if (digitCapacity > digits.length) { byte[] newDigits = new byte[digitCapacity * 2]; System.arraycopy(digits, 0, newDigits, 0, digitsToCopy); digits = newDigits; } } /** * Return true if the represented number is zero. */ boolean isZero() { for (int i=0; iBigInteger DigitList
. This method assumes that this object contains * an integral value; if not, it will return an incorrect value. * [bnf] * @param isPositive determines the sign of the returned result * @return the value of this object as aBigInteger
*/ public BigInteger getBigInteger(boolean isPositive) { if (isZero()) return BigInteger.valueOf(0); //Eclipse stated the following is "dead code" /*if (false) { StringBuilder stringRep = new StringBuilder(count); if (!isPositive) { stringRep.append('-'); } for (int i=0; icount) { stringRep.append('0'); } return new BigInteger(stringRep.toString()); } else*/ { int len = decimalAt > count ? decimalAt : count; if (!isPositive) { len += 1; } char[] text = new char[len]; int n = 0; if (!isPositive) { text[0] = '-'; for (int i = 0; i < count; ++i) { text[i+1] = (char)digits[i]; } n = count+1; } else { for (int i = 0; i < count; ++i) { text[i] = (char)digits[i]; } n = count; } for (int i = n; i < text.length; ++i) { text[i] = '0'; } return new BigInteger(new String(text)); } } private String getStringRep(boolean isPositive) { if (isZero()) return "0"; StringBuilder stringRep = new StringBuilder(count+1); if (!isPositive) { stringRep.append('-'); } int d = decimalAt; if (d < 0) { stringRep.append('.'); while (d < 0) { stringRep.append('0'); ++d; } d = -1; } for (int i=0; i count) { stringRep.append('0'); } return stringRep.toString(); } /** * Return a BigDecimal
representing the value stored in this *DigitList
. * [bnf] * @param isPositive determines the sign of the returned result * @return the value of this object as aBigDecimal
*/ ///CLOVER:OFF // The method is in a protected class and is not called by anything public java.math.BigDecimal getBigDecimal(boolean isPositive) { if (isZero()) { return java.math.BigDecimal.valueOf(0); } // if exponential notion is negative, // we prefer to use BigDecimal constructor with scale, // because it works better when extremely small value // is used. See #5698. long scale = (long)count - (long)decimalAt; if (scale > 0) { int numDigits = count; if (scale > (long)Integer.MAX_VALUE) { // try to reduce the scale long numShift = scale - (long)Integer.MAX_VALUE; if (numShift < count) { numDigits -= numShift; } else { // fallback to 0 return new java.math.BigDecimal(0); } } StringBuilder significantDigits = new StringBuilder(numDigits + 1); if (!isPositive) { significantDigits.append('-'); } for (int i = 0; i < numDigits; i++) { significantDigits.append((char)digits[i]); } BigInteger unscaledVal = new BigInteger(significantDigits.toString()); return new java.math.BigDecimal(unscaledVal, (int)scale); } else { // We should be able to use a negative scale value for a positive exponential // value on JDK1.5. But it is not supported by older JDK. So, for now, // we always use BigDecimal constructor which takes String. return new java.math.BigDecimal(getStringRep(isPositive)); } } ///CLOVER:ON /** * Return anICU BigDecimal
representing the value stored in this *DigitList
. * [bnf] * @param isPositive determines the sign of the returned result * @return the value of this object as aBigDecimal
*/ public com.ibm.icu.math.BigDecimal getBigDecimalICU(boolean isPositive) { if (isZero()) { return com.ibm.icu.math.BigDecimal.valueOf(0); } // if exponential notion is negative, // we prefer to use BigDecimal constructor with scale, // because it works better when extremely small value // is used. See #5698. long scale = (long)count - (long)decimalAt; if (scale > 0) { int numDigits = count; if (scale > (long)Integer.MAX_VALUE) { // try to reduce the scale long numShift = scale - (long)Integer.MAX_VALUE; if (numShift < count) { numDigits -= numShift; } else { // fallback to 0 return new com.ibm.icu.math.BigDecimal(0); } } StringBuilder significantDigits = new StringBuilder(numDigits + 1); if (!isPositive) { significantDigits.append('-'); } for (int i = 0; i < numDigits; i++) { significantDigits.append((char)digits[i]); } BigInteger unscaledVal = new BigInteger(significantDigits.toString()); return new com.ibm.icu.math.BigDecimal(unscaledVal, (int)scale); } else { return new com.ibm.icu.math.BigDecimal(getStringRep(isPositive)); } } /** * Return whether or not this objects represented value is an integer. * [bnf] * @return true if the represented value of this object is an integer */ boolean isIntegral() { // Trim trailing zeros. This does not change the represented value. while (count > 0 && digits[count - 1] == (byte)'0') --count; return count == 0 || decimalAt >= count; } // Unused as of ICU 2.6 - alan // /** // * Return true if the number represented by this object can fit into // * a long. // */ // boolean fitsIntoLong(boolean isPositive) // { // // Figure out if the result will fit in a long. We have to // // first look for nonzero digits after the decimal point; // // then check the size. If the digit count is 18 or less, then // // the value can definitely be represented as a long. If it is 19 // // then it may be too large. // // // Trim trailing zeros. This does not change the represented value. // while (count > 0 && digits[count - 1] == (byte)'0') --count; // // if (count == 0) { // // Positive zero fits into a long, but negative zero can only // // be represented as a double. - bug 4162852 // return isPositive; // } // // if (decimalAt < count || decimalAt > MAX_LONG_DIGITS) return false; // // if (decimalAt < MAX_LONG_DIGITS) return true; // // // At this point we have decimalAt == count, and count == MAX_LONG_DIGITS. // // The number will overflow if it is larger than 9223372036854775807 // // or smaller than -9223372036854775808. // for (int i=0; imax) return false; // if (dig < max) return true; // } // // // At this point the first count digits match. If decimalAt is less // // than count, then the remaining digits are zero, and we return true. // if (count < decimalAt) return true; // // // Now we have a representation of Long.MIN_VALUE, without the leading // // negative sign. If this represents a positive value, then it does // // not fit; otherwise it fits. // return !isPositive; // } // Unused as of ICU 2.6 - alan // /** // * Set the digit list to a representation of the given double value. // * This method supports fixed-point notation. // * @param source Value to be converted; must not be Inf, -Inf, Nan, // * or a value <= 0. // * @param maximumFractionDigits The most fractional digits which should // * be converted. // */ // public final void set(double source, int maximumFractionDigits) // { // set(source, maximumFractionDigits, true); // } /** * Set the digit list to a representation of the given double value. * This method supports both fixed-point and exponential notation. * @param source Value to be converted; must not be Inf, -Inf, Nan, * or a value <= 0. * @param maximumDigits The most fractional or total digits which should * be converted. * @param fixedPoint If true, then maximumDigits is the maximum * fractional digits to be converted. If false, total digits. */ final void set(double source, int maximumDigits, boolean fixedPoint) { if (source == 0) source = 0; // Generate a representation of the form DDDDD, DDDDD.DDDDD, or // DDDDDE+/-DDDDD. String rep = Double.toString(source); set(rep, MAX_LONG_DIGITS); if (fixedPoint) { // The negative of the exponent represents the number of leading // zeros between the decimal and the first non-zero digit, for // a value < 0.1 (e.g., for 0.00123, -decimalAt == 2). If this // is more than the maximum fraction digits, then we have an underflow // for the printed representation. if (-decimalAt > maximumDigits) { count = 0; return; } else if (-decimalAt == maximumDigits) { if (shouldRoundUp(0)) { count = 1; ++decimalAt; digits[0] = (byte)'1'; } else { count = 0; } return; } // else fall through } // Eliminate trailing zeros. while (count > 1 && digits[count - 1] == '0') --count; // Eliminate digits beyond maximum digits to be displayed. // Round up if appropriate. round(fixedPoint ? (maximumDigits + decimalAt) : maximumDigits == 0 ? -1 : maximumDigits); } /** * Given a string representation of the form DDDDD, DDDDD.DDDDD, * or DDDDDE+/-DDDDD, set this object's value to it. Ignore * any leading '-'. */ private void set(String rep, int maxCount) { decimalAt = -1; count = 0; int exponent = 0; // Number of zeros between decimal point and first non-zero digit after // decimal point, for numbers < 1. int leadingZerosAfterDecimal = 0; boolean nonZeroDigitSeen = false; // Skip over leading '-' int i=0; if (rep.charAt(i) == '-') { ++i; } for (; i < rep.length(); ++i) { char c = rep.charAt(i); if (c == '.') { decimalAt = count; } else if (c == 'e' || c == 'E') { ++i; // Integer.parseInt doesn't handle leading '+' signs if (rep.charAt(i) == '+') { ++i; } exponent = Integer.valueOf(rep.substring(i)).intValue(); break; } else if (count < maxCount) { if (!nonZeroDigitSeen) { nonZeroDigitSeen = (c != '0'); if (!nonZeroDigitSeen && decimalAt != -1) { ++leadingZerosAfterDecimal; } } if (nonZeroDigitSeen) { ensureCapacity(count+1, count); digits[count++] = (byte)c; } } } if (decimalAt == -1) { decimalAt = count; } decimalAt += exponent - leadingZerosAfterDecimal; } /** * Return true if truncating the representation to the given number * of digits will result in an increment to the last digit. This * method implements half-even rounding, the default rounding mode. * [bnf] * @param maximumDigits the number of digits to keep, from 0 to * count-1
. If 0, then all digits are rounded away, and * this method returns true if a one should be generated (e.g., formatting * 0.09 with "#.#"). * @return true if digitmaximumDigits-1
should be * incremented */ private boolean shouldRoundUp(int maximumDigits) { // variable not used boolean increment = false; // Implement IEEE half-even rounding /*Bug 4243108 format(0.0) gives "0.1" if preceded by parse("99.99") [Richard/GCL] */ if (maximumDigits < count) { if (digits[maximumDigits] > '5') { return true; } else if (digits[maximumDigits] == '5' ) { for (int i=maximumDigits+1; i0 && (digits[maximumDigits-1] % 2 != 0); } } return false; } /** * Round the representation to the given number of digits. * @param maximumDigits The maximum number of digits to be shown. * Upon return, count will be less than or equal to maximumDigits. * This now performs rounding when maximumDigits is 0, formerly it did not. */ public final void round(int maximumDigits) { // Eliminate digits beyond maximum digits to be displayed. // Round up if appropriate. // [bnf] rewritten to fix 4179818 if (maximumDigits >= 0 && maximumDigits < count) { if (shouldRoundUp(maximumDigits)) { // Rounding up involves incrementing digits from LSD to MSD. // In most cases this is simple, but in a worst case situation // (9999..99) we have to adjust the decimalAt value. for (;;) { --maximumDigits; if (maximumDigits < 0) { // We have all 9's, so we increment to a single digit // of one and adjust the exponent. digits[0] = (byte) '1'; ++decimalAt; maximumDigits = 0; // Adjust the count break; } ++digits[maximumDigits]; if (digits[maximumDigits] <= '9') break; // digits[maximumDigits] = '0'; // Unnecessary since we'll truncate this } ++maximumDigits; // Increment for use as count } count = maximumDigits; } // Bug 4217661 DecimalFormat formats 1.001 to "1.00" instead of "1" // Eliminate trailing zeros. [Richard/GCL] // [dlf] moved outside if block, see ticket #6408 while (count > 1 && digits[count-1] == '0') { --count; } } /** * Utility routine to set the value of the digit list from a long */ public final void set(long source) { set(source, 0); } /** * Set the digit list to a representation of the given long value. * @param source Value to be converted; must be >= 0 or == * Long.MIN_VALUE. * @param maximumDigits The most digits which should be converted. * If maximumDigits is lower than the number of significant digits * in source, the representation will be rounded. Ignored if <= 0. */ public final void set(long source, int maximumDigits) { // This method does not expect a negative number. However, // "source" can be a Long.MIN_VALUE (-9223372036854775808), // if the number being formatted is a Long.MIN_VALUE. In that // case, it will be formatted as -Long.MIN_VALUE, a number // which is outside the legal range of a long, but which can // be represented by DigitList. // [NEW] Faster implementation if (source <= 0) { if (source == Long.MIN_VALUE) { decimalAt = count = MAX_LONG_DIGITS; System.arraycopy(LONG_MIN_REP, 0, digits, 0, count); } else { count = 0; decimalAt = 0; } } else { int left = MAX_LONG_DIGITS; int right; while (source > 0) { digits[--left] = (byte) (((long) '0') + (source % 10)); source /= 10; } decimalAt = MAX_LONG_DIGITS-left; // Don't copy trailing zeros // we are guaranteed that there is at least one non-zero digit, // so we don't have to check lower bounds for (right = MAX_LONG_DIGITS - 1; digits[right] == (byte) '0'; --right) {} count = right - left + 1; System.arraycopy(digits, left, digits, 0, count); } if (maximumDigits > 0) round(maximumDigits); } /** * Set the digit list to a representation of the given BigInteger value. * [bnf] * @param source Value to be converted * @param maximumDigits The most digits which should be converted. * If maximumDigits is lower than the number of significant digits * in source, the representation will be rounded. Ignored if <= 0. */ public final void set(BigInteger source, int maximumDigits) { String stringDigits = source.toString(); count = decimalAt = stringDigits.length(); // Don't copy trailing zeros while (count > 1 && stringDigits.charAt(count - 1) == '0') --count; int offset = 0; if (stringDigits.charAt(0) == '-') { ++offset; --count; --decimalAt; } ensureCapacity(count, 0); for (int i = 0; i < count; ++i) { digits[i] = (byte) stringDigits.charAt(i + offset); } if (maximumDigits > 0) round(maximumDigits); } /** * Internal method that sets this digit list to represent the * given value. The value is given as a String of the format * returned by BigDecimal. * @param stringDigits value to be represented with the following * syntax, expressed as a regular expression: -?\d*.?\d* * Must not be an empty string. * @param maximumDigits The most digits which should be converted. * If maximumDigits is lower than the number of significant digits * in source, the representation will be rounded. Ignored if <= 0. * @param fixedPoint If true, then maximumDigits is the maximum * fractional digits to be converted. If false, total digits. */ private void setBigDecimalDigits(String stringDigits, int maximumDigits, boolean fixedPoint) { //| // Find the first non-zero digit, the decimal, and the last non-zero digit. //| int first=-1, last=stringDigits.length()-1, decimal=-1; //| for (int i=0; (first<0 || decimal<0) && i<=last; ++i) { //| char c = stringDigits.charAt(i); //| if (c == '.') { //| decimal = i; //| } else if (first < 0 && (c >= '1' && c <= '9')) { //| first = i; //| } //| } //| //| if (first < 0) { //| clear(); //| return; //| } //| //| // At this point we know there is at least one non-zero digit, so the //| // following loop is safe. //| for (;;) { //| char c = stringDigits.charAt(last); //| if (c != '0' && c != '.') { //| break; //| } //| --last; //| } //| //| if (decimal < 0) { //| decimal = stringDigits.length(); //| } //| //| count = last - first; //| if (decimal < first || decimal > last) { //| ++count; //| } //| decimalAt = decimal - first; //| if (decimalAt < 0) { //| ++decimalAt; //| } //| //| ensureCapacity(count, 0); //| for (int i = 0; i < count; ++i) { //| digits[i] = (byte) stringDigits.charAt(first++); //| if (first == decimal) { //| ++first; //| } //| } // The maxDigits here could also be Integer.MAX_VALUE set(stringDigits, stringDigits.length()); // Eliminate digits beyond maximum digits to be displayed. // Round up if appropriate. // {dlf} Some callers depend on passing '0' to round to mean 'don't round', but // rather than pass that information explicitly, we rely on some magic with maximumDigits // and decimalAt. Unfortunately, this is no good, because there are cases where maximumDigits // is zero and we do want to round, e.g. BigDecimal values -1 < x < 1. So since round // changed to perform rounding when the argument is 0, we now force the argument // to -1 in the situations where it matters. round(fixedPoint ? (maximumDigits + decimalAt) : maximumDigits == 0 ? -1 : maximumDigits); } /** * Set the digit list to a representation of the given BigDecimal value. * [bnf] * @param source Value to be converted * @param maximumDigits The most digits which should be converted. * If maximumDigits is lower than the number of significant digits * in source, the representation will be rounded. Ignored if <= 0. * @param fixedPoint If true, then maximumDigits is the maximum * fractional digits to be converted. If false, total digits. */ public final void set(java.math.BigDecimal source, int maximumDigits, boolean fixedPoint) { setBigDecimalDigits(source.toString(), maximumDigits, fixedPoint); } /* * Set the digit list to a representation of the given BigDecimal value. * [bnf] * @param source Value to be converted * @param maximumDigits The most digits which should be converted. * If maximumDigits is lower than the number of significant digits * in source, the representation will be rounded. Ignored if <= 0. * @param fixedPoint If true, then maximumDigits is the maximum * fractional digits to be converted. If false, total digits. */ public final void set(com.ibm.icu.math.BigDecimal source, int maximumDigits, boolean fixedPoint) { setBigDecimalDigits(source.toString(), maximumDigits, fixedPoint); } /** * Returns true if this DigitList represents Long.MIN_VALUE; * false, otherwise. This is required so that getLong() works. */ private boolean isLongMIN_VALUE() { if (decimalAt != count || count != MAX_LONG_DIGITS) return false; for (int i = 0; i < count; ++i) { if (digits[i] != LONG_MIN_REP[i]) return false; } return true; } private static byte[] LONG_MIN_REP; static { // Store the representation of LONG_MIN without the leading '-' String s = Long.toString(Long.MIN_VALUE); LONG_MIN_REP = new byte[MAX_LONG_DIGITS]; for (int i=0; i < MAX_LONG_DIGITS; ++i) { LONG_MIN_REP[i] = (byte)s.charAt(i + 1); } } // Unused -- Alan 2003-05 // /** // * Return the floor of the log base 10 of a given double. // * This method compensates for inaccuracies which arise naturally when // * computing logs, and always give the correct value. The parameter // * must be positive and finite. // */ // private static final int log10(double d) // { // // The reason this routine is needed is that simply taking the // // log and dividing by log10 yields a result which may be off // // by 1 due to rounding errors. For example, the naive log10 // // of 1.0e300 taken this way is 299, rather than 300. // double log10 = Math.log(d) / LOG10; // int ilog10 = (int)Math.floor(log10); // // Positive logs could be too small, e.g. 0.99 instead of 1.0 // if (log10 > 0 && d >= Math.pow(10, ilog10 + 1)) // { // ++ilog10; // } // // Negative logs could be too big, e.g. -0.99 instead of -1.0 // else if (log10 < 0 && d < Math.pow(10, ilog10)) // { // --ilog10; // } // return ilog10; // } // // private static final double LOG10 = Math.log(10.0); // (The following boilerplate methods are currently not called, // and cannot be called by tests since this class is // package-private. The methods may be useful in the future, so // we do not delete them. 2003-06-11 ICU 2.6 Alan) ///CLOVER:OFF /** * equality test between two digit lists. */ public boolean equals(Object obj) { if (this == obj) // quick check return true; if (!(obj instanceof DigitList)) // (1) same object? return false; DigitList other = (DigitList) obj; if (count != other.count || decimalAt != other.decimalAt) return false; for (int i = 0; i < count; i++) if (digits[i] != other.digits[i]) return false; return true; } /** * Generates the hash code for the digit list. */ public int hashCode() { int hashcode = decimalAt; for (int i = 0; i < count; i++) hashcode = hashcode * 37 + digits[i]; return hashcode; } public String toString() { if (isZero()) return "0"; StringBuilder buf = new StringBuilder("0."); for (int i=0; i