001: /*
002: *
003: * @(#)DigitList.java 1.33 06/10/10
004: *
005: * Portions Copyright 2000-2006 Sun Microsystems, Inc. All Rights
006: * Reserved. Use is subject to license terms.
007: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
008: *
009: * This program is free software; you can redistribute it and/or
010: * modify it under the terms of the GNU General Public License version
011: * 2 only, as published by the Free Software Foundation.
012: *
013: * This program is distributed in the hope that it will be useful, but
014: * WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016: * General Public License version 2 for more details (a copy is
017: * included at /legal/license.txt).
018: *
019: * You should have received a copy of the GNU General Public License
020: * version 2 along with this work; if not, write to the Free Software
021: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022: * 02110-1301 USA
023: *
024: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
025: * Clara, CA 95054 or visit www.sun.com if you need additional
026: * information or have any questions.
027: */
028:
029: /*
030: * (C) Copyright Taligent, Inc. 1996, 1997 - All Rights Reserved
031: * (C) Copyright IBM Corp. 1996 - 1998 - All Rights Reserved
032: *
033: * The original version of this source code and documentation is copyrighted
034: * and owned by Taligent, Inc., a wholly-owned subsidiary of IBM. These
035: * materials are provided under terms of a License Agreement between Taligent
036: * and Sun. This technology is protected by multiple US and International
037: * patents. This notice and attribution to Taligent may not be removed.
038: * Taligent is a registered trademark of Taligent, Inc.
039: *
040: */
041:
042: package java.text;
043:
044: /**
045: * Digit List. Private to DecimalFormat.
046: * Handles the transcoding
047: * between numeric values and strings of characters. Only handles
048: * non-negative numbers. The division of labor between DigitList and
049: * DecimalFormat is that DigitList handles the radix 10 representation
050: * issues; DecimalFormat handles the locale-specific issues such as
051: * positive/negative, grouping, decimal point, currency, and so on.
052: *
053: * A DigitList is really a representation of a floating point value.
054: * It may be an integer value; we assume that a double has sufficient
055: * precision to represent all digits of a long.
056: *
057: * The DigitList representation consists of a string of characters,
058: * which are the digits radix 10, from '0' to '9'. It also has a radix
059: * 10 exponent associated with it. The value represented by a DigitList
060: * object can be computed by mulitplying the fraction f, where 0 <= f < 1,
061: * derived by placing all the digits of the list to the right of the
062: * decimal point, by 10^exponent.
063: *
064: * @see Locale
065: * @see Format
066: * @see NumberFormat
067: * @see DecimalFormat
068: * @see ChoiceFormat
069: * @see MessageFormat
070: * @version 1.28, 11/17/03
071: * @author Mark Davis, Alan Liu
072: */
073: final class DigitList implements Cloneable {
074: /**
075: * The maximum number of significant digits in an IEEE 754 double, that
076: * is, in a Java double. This must not be increased, or garbage digits
077: * will be generated, and should not be decreased, or accuracy will be lost.
078: */
079: public static final int MAX_COUNT = 19; // == Long.toString(Long.MAX_VALUE).length()
080: public static final int DBL_DIG = 17;
081:
082: /**
083: * These data members are intentionally public and can be set directly.
084: *
085: * The value represented is given by placing the decimal point before
086: * digits[decimalAt]. If decimalAt is < 0, then leading zeros between
087: * the decimal point and the first nonzero digit are implied. If decimalAt
088: * is > count, then trailing zeros between the digits[count-1] and the
089: * decimal point are implied.
090: *
091: * Equivalently, the represented value is given by f * 10^decimalAt. Here
092: * f is a value 0.1 <= f < 1 arrived at by placing the digits in Digits to
093: * the right of the decimal.
094: *
095: * DigitList is normalized, so if it is non-zero, figits[0] is non-zero. We
096: * don't allow denormalized numbers because our exponent is effectively of
097: * unlimited magnitude. The count value contains the number of significant
098: * digits present in digits[].
099: *
100: * Zero is represented by any DigitList with count == 0 or with each digits[i]
101: * for all i <= count == '0'.
102: */
103: public int decimalAt = 0;
104: public int count = 0;
105: public char[] digits = new char[MAX_COUNT];
106:
107: /**
108: * Return true if the represented number is zero.
109: */
110: boolean isZero() {
111: for (int i = 0; i < count; ++i)
112: if (digits[i] != '0')
113: return false;
114: return true;
115: }
116:
117: /**
118: * Clears out the digits.
119: * Use before appending them.
120: * Typically, you set a series of digits with append, then at the point
121: * you hit the decimal point, you set myDigitList.decimalAt = myDigitList.count;
122: * then go on appending digits.
123: */
124: public void clear() {
125: decimalAt = 0;
126: count = 0;
127: }
128:
129: /**
130: * Appends a digit to the list. Ignores all digits over MAX_COUNT,
131: * since they are not significant for either longs or doubles.
132: */
133: public void append(char digit) {
134: if (count < MAX_COUNT)
135: digits[count++] = digit;
136: }
137:
138: /**
139: * Utility routine to get the value of the digit list
140: * If (count == 0) this throws a NumberFormatException, which
141: * mimics Long.parseLong().
142: */
143: public final double getDouble() {
144: if (count == 0)
145: return 0.0;
146: StringBuffer temp = getStringBuffer();
147: temp.append('.').append(digits, 0, count);
148: temp.append('E');
149: temp.append(decimalAt);
150: return Double.parseDouble(temp.toString());
151: }
152:
153: /**
154: * Utility routine to get the value of the digit list.
155: * If (count == 0) this returns 0, unlike Long.parseLong().
156: */
157: public final long getLong() {
158: // for now, simple implementation; later, do proper IEEE native stuff
159:
160: if (count == 0)
161: return 0;
162:
163: // We have to check for this, because this is the one NEGATIVE value
164: // we represent. If we tried to just pass the digits off to parseLong,
165: // we'd get a parse failure.
166: if (isLongMIN_VALUE())
167: return Long.MIN_VALUE;
168:
169: StringBuffer temp = getStringBuffer();
170: temp.append(digits, 0, count);
171: for (int i = count; i < decimalAt; ++i) {
172: temp.append('0');
173: }
174: return Long.parseLong(temp.toString());
175: }
176:
177: /**
178: * Return true if the number represented by this object can fit into
179: * a long.
180: * @param isPositive true if this number should be regarded as positive
181: * @param ignoreNegativeZero true if -0 should be regarded as identical to
182: * +0; otherwise they are considered distinct
183: * @return true if this number fits into a Java long
184: */
185: boolean fitsIntoLong(boolean isPositive, boolean ignoreNegativeZero) {
186: // Figure out if the result will fit in a long. We have to
187: // first look for nonzero digits after the decimal point;
188: // then check the size. If the digit count is 18 or less, then
189: // the value can definitely be represented as a long. If it is 19
190: // then it may be too large.
191:
192: // Trim trailing zeros. This does not change the represented value.
193: while (count > 0 && digits[count - 1] == '0')
194: --count;
195:
196: if (count == 0) {
197: // Positive zero fits into a long, but negative zero can only
198: // be represented as a double. - bug 4162852
199: return isPositive || ignoreNegativeZero;
200: }
201:
202: if (decimalAt < count || decimalAt > MAX_COUNT)
203: return false;
204:
205: if (decimalAt < MAX_COUNT)
206: return true;
207:
208: // At this point we have decimalAt == count, and count == MAX_COUNT.
209: // The number will overflow if it is larger than 9223372036854775807
210: // or smaller than -9223372036854775808.
211: for (int i = 0; i < count; ++i) {
212: char dig = digits[i], max = LONG_MIN_REP[i];
213: if (dig > max)
214: return false;
215: if (dig < max)
216: return true;
217: }
218:
219: // At this point the first count digits match. If decimalAt is less
220: // than count, then the remaining digits are zero, and we return true.
221: if (count < decimalAt)
222: return true;
223:
224: // Now we have a representation of Long.MIN_VALUE, without the leading
225: // negative sign. If this represents a positive value, then it does
226: // not fit; otherwise it fits.
227: return !isPositive;
228: }
229:
230: /**
231: * Set the digit list to a representation of the given double value.
232: * This method supports fixed-point notation.
233: * @param source Value to be converted; must not be Inf, -Inf, Nan,
234: * or a value <= 0.
235: * @param maximumFractionDigits The most fractional digits which should
236: * be converted.
237: */
238: public final void set(double source, int maximumFractionDigits) {
239: set(source, maximumFractionDigits, true);
240: }
241:
242: /**
243: * Set the digit list to a representation of the given double value.
244: * This method supports both fixed-point and exponential notation.
245: * @param source Value to be converted; must not be Inf, -Inf, Nan,
246: * or a value <= 0.
247: * @param maximumDigits The most fractional or total digits which should
248: * be converted.
249: * @param fixedPoint If true, then maximumDigits is the maximum
250: * fractional digits to be converted. If false, total digits.
251: */
252: final void set(double source, int maximumDigits, boolean fixedPoint) {
253: if (source == 0)
254: source = 0;
255: // Generate a representation of the form DDDDD, DDDDD.DDDDD, or
256: // DDDDDE+/-DDDDD.
257: char[] rep = Double.toString(source).toCharArray();
258:
259: decimalAt = -1;
260: count = 0;
261: int exponent = 0;
262: // Number of zeros between decimal point and first non-zero digit after
263: // decimal point, for numbers < 1.
264: int leadingZerosAfterDecimal = 0;
265: boolean nonZeroDigitSeen = false;
266:
267: for (int i = 0; i < rep.length;) {
268: char c = rep[i++];
269: if (c == '.') {
270: decimalAt = count;
271: } else if (c == 'e' || c == 'E') {
272: exponent = parseInt(rep, i);
273: break;
274: } else if (count < MAX_COUNT) {
275: if (!nonZeroDigitSeen) {
276: nonZeroDigitSeen = (c != '0');
277: if (!nonZeroDigitSeen && decimalAt != -1)
278: ++leadingZerosAfterDecimal;
279: }
280: if (nonZeroDigitSeen)
281: digits[count++] = c;
282: }
283: }
284: if (decimalAt == -1)
285: decimalAt = count;
286: if (nonZeroDigitSeen) {
287: decimalAt += exponent - leadingZerosAfterDecimal;
288: }
289:
290: if (fixedPoint) {
291: // The negative of the exponent represents the number of leading
292: // zeros between the decimal and the first non-zero digit, for
293: // a value < 0.1 (e.g., for 0.00123, -decimalAt == 2). If this
294: // is more than the maximum fraction digits, then we have an underflow
295: // for the printed representation.
296: if (-decimalAt > maximumDigits) {
297: // Handle an underflow to zero when we round something like
298: // 0.0009 to 2 fractional digits.
299: count = 0;
300: return;
301: } else if (-decimalAt == maximumDigits) {
302: // If we round 0.0009 to 3 fractional digits, then we have to
303: // create a new one digit in the least significant location.
304: if (shouldRoundUp(0)) {
305: count = 1;
306: ++decimalAt;
307: digits[0] = '1';
308: } else {
309: count = 0;
310: }
311: return;
312: }
313: // else fall through
314: }
315:
316: // Eliminate trailing zeros.
317: while (count > 1 && digits[count - 1] == '0')
318: --count;
319:
320: // Eliminate digits beyond maximum digits to be displayed.
321: // Round up if appropriate.
322: round(fixedPoint ? (maximumDigits + decimalAt) : maximumDigits);
323: }
324:
325: /**
326: * Round the representation to the given number of digits.
327: * @param maximumDigits The maximum number of digits to be shown.
328: * Upon return, count will be less than or equal to maximumDigits.
329: */
330: private final void round(int maximumDigits) {
331: // Eliminate digits beyond maximum digits to be displayed.
332: // Round up if appropriate.
333: if (maximumDigits >= 0 && maximumDigits < count) {
334: if (shouldRoundUp(maximumDigits)) {
335: // Rounding up involved incrementing digits from LSD to MSD.
336: // In most cases this is simple, but in a worst case situation
337: // (9999..99) we have to adjust the decimalAt value.
338: for (;;) {
339: --maximumDigits;
340: if (maximumDigits < 0) {
341: // We have all 9's, so we increment to a single digit
342: // of one and adjust the exponent.
343: digits[0] = '1';
344: ++decimalAt;
345: maximumDigits = 0; // Adjust the count
346: break;
347: }
348:
349: ++digits[maximumDigits];
350: if (digits[maximumDigits] <= '9')
351: break;
352: // digits[maximumDigits] = '0'; // Unnecessary since we'll truncate this
353: }
354: ++maximumDigits; // Increment for use as count
355: }
356: count = maximumDigits;
357:
358: // Eliminate trailing zeros.
359: while (count > 1 && digits[count - 1] == '0') {
360: --count;
361: }
362: }
363: }
364:
365: /**
366: * Return true if truncating the representation to the given number
367: * of digits will result in an increment to the last digit. This
368: * method implements half-even rounding, the default rounding mode.
369: * [bnf]
370: * @param maximumDigits the number of digits to keep, from 0 to
371: * <code>count-1</code>. If 0, then all digits are rounded away, and
372: * this method returns true if a one should be generated (e.g., formatting
373: * 0.09 with "#.#").
374: * @return true if digit <code>maximumDigits-1</code> should be
375: * incremented
376: */
377: private boolean shouldRoundUp(int maximumDigits) {
378: boolean increment = false;
379: // Implement IEEE half-even rounding
380: if (maximumDigits < count) {
381: if (digits[maximumDigits] > '5') {
382: return true;
383: } else if (digits[maximumDigits] == '5') {
384: for (int i = maximumDigits + 1; i < count; ++i) {
385: if (digits[i] != '0') {
386: return true;
387: }
388: }
389: return maximumDigits > 0
390: && (digits[maximumDigits - 1] % 2 != 0);
391: }
392: }
393: return false;
394: }
395:
396: /**
397: * Utility routine to set the value of the digit list from a long
398: */
399: public final void set(long source) {
400: set(source, 0);
401: }
402:
403: /**
404: * Set the digit list to a representation of the given long value.
405: * @param source Value to be converted; must be >= 0 or ==
406: * Long.MIN_VALUE.
407: * @param maximumDigits The most digits which should be converted.
408: * If maximumDigits is lower than the number of significant digits
409: * in source, the representation will be rounded. Ignored if <= 0.
410: */
411: public final void set(long source, int maximumDigits) {
412: // This method does not expect a negative number. However,
413: // "source" can be a Long.MIN_VALUE (-9223372036854775808),
414: // if the number being formatted is a Long.MIN_VALUE. In that
415: // case, it will be formatted as -Long.MIN_VALUE, a number
416: // which is outside the legal range of a long, but which can
417: // be represented by DigitList.
418: if (source <= 0) {
419: if (source == Long.MIN_VALUE) {
420: decimalAt = count = MAX_COUNT;
421: System.arraycopy(LONG_MIN_REP, 0, digits, 0, count);
422: } else {
423: decimalAt = count = 0; // Values <= 0 format as zero
424: }
425: } else {
426: // Rewritten to improve performance. I used to call
427: // Long.toString(), which was about 4x slower than this code.
428: int left = MAX_COUNT;
429: int right;
430: while (source > 0) {
431: digits[--left] = (char) ('0' + (source % 10));
432: source /= 10;
433: }
434: decimalAt = MAX_COUNT - left;
435: // Don't copy trailing zeros. We are guaranteed that there is at
436: // least one non-zero digit, so we don't have to check lower bounds.
437: for (right = MAX_COUNT - 1; digits[right] == '0'; --right)
438: ;
439: count = right - left + 1;
440: System.arraycopy(digits, left, digits, 0, count);
441: }
442: if (maximumDigits > 0)
443: round(maximumDigits);
444: }
445:
446: /**
447: * equality test between two digit lists.
448: */
449: public boolean equals(Object obj) {
450: if (this == obj) // quick check
451: return true;
452: if (!(obj instanceof DigitList)) // (1) same object?
453: return false;
454: DigitList other = (DigitList) obj;
455: if (count != other.count || decimalAt != other.decimalAt)
456: return false;
457: for (int i = 0; i < count; i++)
458: if (digits[i] != other.digits[i])
459: return false;
460: return true;
461: }
462:
463: /**
464: * Generates the hash code for the digit list.
465: */
466: public int hashCode() {
467: int hashcode = decimalAt;
468:
469: for (int i = 0; i < count; i++)
470: hashcode = hashcode * 37 + digits[i];
471:
472: return hashcode;
473: }
474:
475: /**
476: * Creates a copy of this object.
477: * @return a clone of this instance.
478: */
479: public Object clone() {
480: try {
481: DigitList other = (DigitList) super .clone();
482: char[] newDigits = new char[digits.length];
483: System.arraycopy(digits, 0, newDigits, 0, digits.length);
484: other.digits = newDigits;
485: return other;
486: } catch (CloneNotSupportedException e) {
487: throw new InternalError();
488: }
489: }
490:
491: /**
492: * Returns true if this DigitList represents Long.MIN_VALUE;
493: * false, otherwise. This is required so that getLong() works.
494: */
495: private boolean isLongMIN_VALUE() {
496: if (decimalAt != count || count != MAX_COUNT)
497: return false;
498:
499: for (int i = 0; i < count; ++i) {
500: if (digits[i] != LONG_MIN_REP[i])
501: return false;
502: }
503:
504: return true;
505: }
506:
507: private static final int parseInt(char[] str, int offset) {
508: char c;
509: boolean positive = true;
510: if ((c = str[offset]) == '-') {
511: positive = false;
512: offset++;
513: } else if (c == '+') {
514: offset++;
515: }
516:
517: int value = 0;
518: while (offset < str.length) {
519: c = str[offset++];
520: if (c >= '0' && c <= '9') {
521: value = value * 10 + (c - '0');
522: } else {
523: break;
524: }
525: }
526: return positive ? value : -value;
527: }
528:
529: // The digit part of -9223372036854775808L
530: private static final char[] LONG_MIN_REP = "9223372036854775808"
531: .toCharArray();
532:
533: public String toString() {
534: if (isZero())
535: return "0";
536: StringBuffer buf = getStringBuffer();
537: buf.append("0.").append(digits, 0, count);
538: buf.append("x10^");
539: buf.append(decimalAt);
540: return buf.toString();
541: }
542:
543: private StringBuffer tempBuffer;
544:
545: private StringBuffer getStringBuffer() {
546: if (tempBuffer == null) {
547: tempBuffer = new StringBuffer(MAX_COUNT);
548: } else {
549: tempBuffer.setLength(0);
550: }
551: return tempBuffer;
552: }
553: }
|