1 /* DecimalFormat.java -- Formats and parses numbers
2 Copyright (C) 1999, 2000, 2001 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
21 As a special exception, if you link this library with other files to
22 produce an executable, this library does not by itself cause the
23 resulting executable to be covered by the GNU General Public License.
24 This exception does not however invalidate any other reasons why the
25 executable file might be covered by the GNU General Public License. */
29 import java.util.Locale;
30 import java.util.MissingResourceException;
31 import java.util.ResourceBundle;
32 import java.io.ObjectInputStream;
33 import java.io.IOException;
36 * @author Tom Tromey <tromey@cygnus.com>
39 /* Written using "Java Class Libraries", 2nd edition, plus online
40 * API docs for JDK 1.2 from http://www.javasoft.com.
41 * Status: Believed complete and correct to 1.2.
42 * Note however that the docs are very unclear about how format parsing
43 * should work. No doubt there are problems here.
45 public class DecimalFormat extends NumberFormat
47 // This is a helper for applyPatternWithSymbols. It reads a prefix
48 // or a suffix. It can cause some side-effects.
49 private final int scanFix (String pattern, int index, StringBuffer buf,
50 String patChars, DecimalFormatSymbols syms,
53 int len = pattern.length();
55 boolean multiplierSet = false;
58 char c = pattern.charAt(index);
59 if (c == '\'' && index + 1 < len
60 && pattern.charAt(index + 1) == '\'')
65 else if (c == '\'' && index + 2 < len
66 && pattern.charAt(index + 2) == '\'')
68 buf.append(pattern.charAt(index + 1));
71 else if (c == '\u00a4')
73 if (index + 1 < len && pattern.charAt(index + 1) == '\u00a4')
75 buf.append(syms.getInternationalCurrencySymbol());
79 buf.append(syms.getCurrencySymbol());
81 else if (is_suffix && c == syms.getPercent())
84 throw new IllegalArgumentException ("multiplier already set " +
90 else if (is_suffix && c == syms.getPerMill())
93 throw new IllegalArgumentException ("multiplier already set " +
99 else if (patChars.indexOf(c) != -1)
101 // This is a pattern character.
112 // A helper which reads a number format.
113 private final int scanFormat (String pattern, int index,
114 String patChars, DecimalFormatSymbols syms,
117 int max = pattern.length();
119 int countSinceGroup = 0;
121 boolean saw_group = false;
124 // Scan integer part.
128 char c = pattern.charAt(index);
130 if (c == syms.getDigit())
133 throw new IllegalArgumentException ("digit mark following " +
134 "zero - index: " + index);
137 else if (c == syms.getZeroDigit())
142 else if (c == syms.getGroupingSeparator())
153 // We can only side-effect when parsing the positive format.
156 groupingUsed = saw_group;
157 groupingSize = (byte) countSinceGroup;
158 minimumIntegerDigits = zeroCount;
161 // Early termination.
162 if (index == max || pattern.charAt(index) == syms.getGroupingSeparator())
165 decimalSeparatorAlwaysShown = false;
169 if (pattern.charAt(index) == syms.getDecimalSeparator())
174 // Scan fractional part.
180 char c = pattern.charAt(index);
181 if (c == syms.getZeroDigit())
184 throw new IllegalArgumentException ("zero mark " +
185 "following digit - index: " + index);
188 else if (c == syms.getDigit())
192 else if (c != syms.getExponential()
193 && c != syms.getPatternSeparator()
194 && patChars.indexOf(c) != -1)
195 throw new IllegalArgumentException ("unexpected special " +
196 "character - index: " + index);
205 maximumFractionDigits = hashCount + zeroCount;
206 minimumFractionDigits = zeroCount;
213 if (pattern.charAt(index) == syms.getExponential())
216 // Scan exponential format.
222 char c = pattern.charAt(index);
223 if (c == syms.getZeroDigit())
225 else if (c == syms.getDigit())
229 IllegalArgumentException ("digit mark following zero " +
230 "in exponent - index: " +
233 else if (patChars.indexOf(c) != -1)
234 throw new IllegalArgumentException ("unexpected special " +
235 "character - index: " +
245 useExponentialNotation = true;
246 minExponentDigits = (byte) zeroCount;
253 // This helper function creates a string consisting of all the
254 // characters which can appear in a pattern and must be quoted.
255 private final String patternChars (DecimalFormatSymbols syms)
257 StringBuffer buf = new StringBuffer ();
258 buf.append(syms.getDecimalSeparator());
259 buf.append(syms.getDigit());
260 buf.append(syms.getExponential());
261 buf.append(syms.getGroupingSeparator());
262 // Adding this one causes pattern application to fail.
263 // Of course, omitting is causes toPattern to fail.
264 // ... but we already have bugs there. FIXME.
265 // buf.append(syms.getMinusSign());
266 buf.append(syms.getPatternSeparator());
267 buf.append(syms.getPercent());
268 buf.append(syms.getPerMill());
269 buf.append(syms.getZeroDigit());
270 buf.append('\u00a4');
271 return buf.toString();
274 private final void applyPatternWithSymbols (String pattern,
275 DecimalFormatSymbols syms)
277 // Initialize to the state the parser expects.
282 decimalSeparatorAlwaysShown = false;
284 minExponentDigits = 0;
286 useExponentialNotation = false;
287 groupingUsed = false;
288 maximumFractionDigits = 0;
289 maximumIntegerDigits = 309;
290 minimumFractionDigits = 0;
291 minimumIntegerDigits = 1;
293 StringBuffer buf = new StringBuffer ();
294 String patChars = patternChars (syms);
296 int max = pattern.length();
297 int index = scanFix (pattern, 0, buf, patChars, syms, false);
298 positivePrefix = buf.toString();
300 index = scanFormat (pattern, index, patChars, syms, true);
302 index = scanFix (pattern, index, buf, patChars, syms, true);
303 positiveSuffix = buf.toString();
305 if (index == pattern.length())
308 negativePrefix = null;
309 negativeSuffix = null;
313 if (pattern.charAt(index) != syms.getPatternSeparator())
314 throw new IllegalArgumentException ("separator character " +
315 "expected - index: " + index);
317 index = scanFix (pattern, index + 1, buf, patChars, syms, false);
318 negativePrefix = buf.toString();
320 // We parse the negative format for errors but we don't let
321 // it side-effect this object.
322 index = scanFormat (pattern, index, patChars, syms, false);
324 index = scanFix (pattern, index, buf, patChars, syms, true);
325 negativeSuffix = buf.toString();
327 if (index != pattern.length())
328 throw new IllegalArgumentException ("end of pattern expected " +
329 "- index: " + index);
333 public void applyLocalizedPattern (String pattern)
335 // JCL p. 638 claims this throws a ParseException but p. 629
336 // contradicts this. Empirical tests with patterns of "0,###.0"
337 // and "#.#.#" corroborate the p. 629 statement that an
338 // IllegalArgumentException is thrown.
339 applyPatternWithSymbols (pattern, symbols);
342 public void applyPattern (String pattern)
344 // JCL p. 638 claims this throws a ParseException but p. 629
345 // contradicts this. Empirical tests with patterns of "0,###.0"
346 // and "#.#.#" corroborate the p. 629 statement that an
347 // IllegalArgumentException is thrown.
348 applyPatternWithSymbols (pattern, nonLocalizedSymbols);
351 public Object clone ()
353 return new DecimalFormat (this);
356 private DecimalFormat (DecimalFormat dup)
358 decimalSeparatorAlwaysShown = dup.decimalSeparatorAlwaysShown;
359 groupingSize = dup.groupingSize;
360 minExponentDigits = dup.minExponentDigits;
361 multiplier = dup.multiplier;
362 negativePrefix = dup.negativePrefix;
363 negativeSuffix = dup.negativeSuffix;
364 positivePrefix = dup.positivePrefix;
365 positiveSuffix = dup.positiveSuffix;
366 symbols = (DecimalFormatSymbols) dup.symbols.clone();
367 useExponentialNotation = dup.useExponentialNotation;
370 public DecimalFormat ()
375 public DecimalFormat (String pattern)
377 this (pattern, new DecimalFormatSymbols ());
380 public DecimalFormat (String pattern, DecimalFormatSymbols symbols)
382 this.symbols = symbols;
383 applyPattern (pattern);
386 private final boolean equals (String s1, String s2)
388 if (s1 == null || s2 == null)
390 return s1.equals(s2);
393 public boolean equals (Object obj)
395 if (! (obj instanceof DecimalFormat))
397 DecimalFormat dup = (DecimalFormat) obj;
398 return (decimalSeparatorAlwaysShown == dup.decimalSeparatorAlwaysShown
399 && groupingSize == dup.groupingSize
400 && minExponentDigits == dup.minExponentDigits
401 && multiplier == dup.multiplier
402 && equals(negativePrefix, dup.negativePrefix)
403 && equals(negativeSuffix, dup.negativeSuffix)
404 && equals(positivePrefix, dup.positivePrefix)
405 && equals(positiveSuffix, dup.positiveSuffix)
406 && symbols.equals(dup.symbols)
407 && useExponentialNotation == dup.useExponentialNotation);
410 public StringBuffer format (double number, StringBuffer dest,
411 FieldPosition fieldPos)
413 // A very special case.
414 if (Double.isNaN(number))
416 dest.append(symbols.getNaN());
417 if (fieldPos != null && fieldPos.getField() == INTEGER_FIELD)
419 int index = dest.length();
420 fieldPos.setBeginIndex(index - symbols.getNaN().length());
421 fieldPos.setEndIndex(index);
426 boolean is_neg = number < 0;
429 if (negativePrefix != null)
430 dest.append(negativePrefix);
433 dest.append(symbols.getMinusSign());
434 dest.append(positivePrefix);
439 dest.append(positivePrefix);
441 int integerBeginIndex = dest.length();
442 int integerEndIndex = 0;
443 if (Double.isInfinite (number))
445 dest.append(symbols.getInfinity());
446 integerEndIndex = dest.length();
450 number *= multiplier;
455 if (useExponentialNotation)
457 exponent = (long) (Math.log(number) / Math.log(10));
458 if (minimumIntegerDigits > 0)
459 exponent -= minimumIntegerDigits - 1;
460 baseNumber = (long) (number / Math.pow(10.0, exponent));
465 // Round to the correct number of digits.
466 baseNumber += 5 * Math.pow(10.0, - maximumFractionDigits - 1);
468 int index = dest.length();
469 double intPart = Math.floor(baseNumber);
471 while (count < maximumIntegerDigits
472 && (intPart > 0 || count < minimumIntegerDigits))
474 long dig = (long) (intPart % 10);
475 intPart = Math.floor(intPart / 10);
477 // Append group separator if required.
478 if (groupingUsed && count > 0 && count % groupingSize == 0)
479 dest.insert(index, symbols.getGroupingSeparator());
481 dest.insert(index, (char) (symbols.getZeroDigit() + dig));
486 integerEndIndex = dest.length();
488 int decimal_index = integerEndIndex;
489 int consecutive_zeros = 0;
490 int total_digits = 0;
492 // Strip integer part from NUMBER.
493 double fracPart = baseNumber - Math.floor(baseNumber);
495 count < maximumFractionDigits
496 && (fracPart != 0 || count < minimumFractionDigits);
501 long dig = (long) fracPart;
505 consecutive_zeros = 0;
506 dest.append((char) (symbols.getZeroDigit() + dig));
508 // Strip integer part from FRACPART.
509 fracPart = fracPart - Math.floor (fracPart);
512 // Strip extraneous trailing `0's. We can't always detect
513 // these in the loop.
514 int extra_zeros = Math.min (consecutive_zeros,
515 total_digits - minimumFractionDigits);
518 dest.setLength(dest.length() - extra_zeros);
519 total_digits -= extra_zeros;
522 // If required, add the decimal symbol.
523 if (decimalSeparatorAlwaysShown
526 dest.insert(decimal_index, symbols.getDecimalSeparator());
527 if (fieldPos != null && fieldPos.getField() == FRACTION_FIELD)
529 fieldPos.setBeginIndex(decimal_index + 1);
530 fieldPos.setEndIndex(dest.length());
534 // Finally, print the exponent.
535 if (useExponentialNotation)
537 dest.append(symbols.getExponential());
538 dest.append(exponent < 0 ? '-' : '+');
539 index = dest.length();
541 exponent > 0 || count < minExponentDigits;
544 long dig = exponent % 10;
546 dest.insert(index, (char) (symbols.getZeroDigit() + dig));
551 if (fieldPos != null && fieldPos.getField() == INTEGER_FIELD)
553 fieldPos.setBeginIndex(integerBeginIndex);
554 fieldPos.setEndIndex(integerEndIndex);
557 dest.append((is_neg && negativeSuffix != null)
563 public StringBuffer format (long number, StringBuffer dest,
564 FieldPosition fieldPos)
566 // If using exponential notation, we just format as a double.
567 if (useExponentialNotation)
568 return format ((double) number, dest, fieldPos);
570 boolean is_neg = number < 0;
573 if (negativePrefix != null)
574 dest.append(negativePrefix);
577 dest.append(symbols.getMinusSign());
578 dest.append(positivePrefix);
583 dest.append(positivePrefix);
585 int integerBeginIndex = dest.length();
586 int index = dest.length();
588 while (count < maximumIntegerDigits
589 && (number > 0 || count < minimumIntegerDigits))
591 long dig = number % 10;
593 // NUMBER and DIG will be less than 0 if the original number
594 // was the most negative long.
601 // Append group separator if required.
602 if (groupingUsed && count > 0 && count % groupingSize == 0)
603 dest.insert(index, symbols.getGroupingSeparator());
605 dest.insert(index, (char) (symbols.getZeroDigit() + dig));
610 if (fieldPos != null && fieldPos.getField() == INTEGER_FIELD)
612 fieldPos.setBeginIndex(integerBeginIndex);
613 fieldPos.setEndIndex(dest.length());
616 if (decimalSeparatorAlwaysShown || minimumFractionDigits > 0)
618 dest.append(symbols.getDecimalSeparator());
619 if (fieldPos != null && fieldPos.getField() == FRACTION_FIELD)
621 fieldPos.setBeginIndex(dest.length());
622 fieldPos.setEndIndex(dest.length() + minimumFractionDigits);
626 for (count = 0; count < minimumFractionDigits; ++count)
627 dest.append(symbols.getZeroDigit());
629 dest.append((is_neg && negativeSuffix != null)
635 public DecimalFormatSymbols getDecimalFormatSymbols ()
640 public int getGroupingSize ()
645 public int getMultiplier ()
650 public String getNegativePrefix ()
652 return negativePrefix;
655 public String getNegativeSuffix ()
657 return negativeSuffix;
660 public String getPositivePrefix ()
662 return positivePrefix;
665 public String getPositiveSuffix ()
667 return positiveSuffix;
670 public int hashCode ()
672 int hash = (negativeSuffix.hashCode() ^ negativePrefix.hashCode()
673 ^positivePrefix.hashCode() ^ positiveSuffix.hashCode());
678 public boolean isDecimalSeparatorAlwaysShown ()
680 return decimalSeparatorAlwaysShown;
683 public Number parse (String str, ParsePosition pos)
685 // Our strategy is simple: copy the text into a buffer,
686 // translating or omitting locale-specific information. Then
687 // let Double or Long convert the number for us.
689 boolean is_neg = false;
690 int index = pos.getIndex();
691 StringBuffer buf = new StringBuffer ();
693 // We have to check both prefixes, because one might be empty.
694 // We want to pick the longest prefix that matches.
695 boolean got_pos = str.startsWith(positivePrefix, index);
696 String np = (negativePrefix != null
698 : positivePrefix + symbols.getMinusSign());
699 boolean got_neg = str.startsWith(np, index);
701 if (got_pos && got_neg)
703 // By checking this way, we preserve ambiguity in the case
704 // where the negative format differs only in suffix. We
705 // check this again later.
706 if (np.length() > positivePrefix.length())
709 index += np.length();
712 index += positivePrefix.length();
717 index += np.length();
720 index += positivePrefix.length();
723 pos.setErrorIndex (index);
727 // FIXME: handle Inf and NaN.
729 // FIXME: do we have to respect minimum/maxmimum digit stuff?
730 // What about leading zeros? What about multiplier?
732 int start_index = index;
733 int max = str.length();
734 char zero = symbols.getZeroDigit();
736 boolean int_part = true;
737 boolean exp_part = false;
738 for (; index < max; ++index)
740 char c = str.charAt(index);
742 // FIXME: what about grouping size?
743 if (groupingUsed && c == symbols.getGroupingSeparator())
746 && (index - last_group) % groupingSize != 0)
748 pos.setErrorIndex(index);
753 else if (c >= zero && c <= zero + 9)
755 buf.append((char) (c - zero + '0'));
758 else if (parseIntegerOnly)
760 else if (c == symbols.getDecimalSeparator())
763 && (index - last_group) % groupingSize != 0)
765 pos.setErrorIndex(index);
771 else if (c == symbols.getExponential())
778 && (c == '+' || c == '-' || c == symbols.getMinusSign()))
780 // For exponential notation.
787 if (index == start_index)
789 // Didn't see any digits.
790 pos.setErrorIndex(index);
794 // Check the suffix. We must do this before converting the
795 // buffer to a number to handle the case of a number which is
796 // the most negative Long.
797 boolean got_pos_suf = str.startsWith(positiveSuffix, index);
798 String ns = (negativePrefix == null ? positiveSuffix : negativeSuffix);
799 boolean got_neg_suf = str.startsWith(ns, index);
804 pos.setErrorIndex(index);
808 else if (got_pos && got_neg && got_neg_suf)
812 else if (got_pos != got_pos_suf && got_neg != got_neg_suf)
814 pos.setErrorIndex(index);
818 String suffix = is_neg ? ns : positiveSuffix;
822 String t = buf.toString();
823 Number result = null;
826 result = new Long (t);
828 catch (NumberFormatException x1)
832 result = new Double (t);
834 catch (NumberFormatException x2)
840 pos.setErrorIndex(index);
844 pos.setIndex(index + suffix.length());
849 public void setDecimalFormatSymbols (DecimalFormatSymbols newSymbols)
851 symbols = newSymbols;
854 public void setDecimalSeparatorAlwaysShown (boolean newValue)
856 decimalSeparatorAlwaysShown = newValue;
859 public void setGroupingSize (int groupSize)
861 groupingSize = (byte) groupSize;
864 public void setMaximumFractionDigits (int newValue)
866 maximumFractionDigits = Math.min(newValue, 340);
869 public void setMaximumIntegerDigits (int newValue)
871 maximumIntegerDigits = Math.min(newValue, 309);
874 public void setMinimumFractionDigits (int newValue)
876 minimumFractionDigits = Math.min(newValue, 340);
879 public void setMinimumIntegerDigits (int newValue)
881 minimumIntegerDigits = Math.min(newValue, 309);
884 public void setMultiplier (int newValue)
886 multiplier = newValue;
889 public void setNegativePrefix (String newValue)
891 negativePrefix = newValue;
894 public void setNegativeSuffix (String newValue)
896 negativeSuffix = newValue;
899 public void setPositivePrefix (String newValue)
901 positivePrefix = newValue;
904 public void setPositiveSuffix (String newValue)
906 positiveSuffix = newValue;
909 private final void quoteFix (StringBuffer buf, String text, String patChars)
911 int len = text.length();
912 for (int index = 0; index < len; ++index)
914 char c = text.charAt(index);
915 if (patChars.indexOf(c) != -1)
926 private final String computePattern (DecimalFormatSymbols syms)
928 StringBuffer mainPattern = new StringBuffer ();
929 // We have to at least emit a zero for the minimum number of
930 // digits. Past that we need hash marks up to the grouping
931 // separator (and one beyond).
932 int total_digits = Math.max(minimumIntegerDigits,
933 groupingUsed ? groupingSize + 1: 0);
934 for (int i = 0; i < total_digits - minimumIntegerDigits; ++i)
935 mainPattern.append(syms.getDigit());
936 for (int i = total_digits - minimumIntegerDigits; i < total_digits; ++i)
937 mainPattern.append(syms.getZeroDigit());
938 // Inserting the gropuing operator afterwards is easier.
940 mainPattern.insert(mainPattern.length() - groupingSize,
941 syms.getGroupingSeparator());
942 // See if we need decimal info.
943 if (minimumFractionDigits > 0 || maximumFractionDigits > 0
944 || decimalSeparatorAlwaysShown)
945 mainPattern.append(syms.getDecimalSeparator());
946 for (int i = 0; i < minimumFractionDigits; ++i)
947 mainPattern.append(syms.getZeroDigit());
948 for (int i = minimumFractionDigits; i < maximumFractionDigits; ++i)
949 mainPattern.append(syms.getDigit());
950 if (useExponentialNotation)
952 mainPattern.append(syms.getExponential());
953 for (int i = 0; i < minExponentDigits; ++i)
954 mainPattern.append(syms.getZeroDigit());
955 if (minExponentDigits == 0)
956 mainPattern.append(syms.getDigit());
959 String main = mainPattern.toString();
960 String patChars = patternChars (syms);
961 mainPattern.setLength(0);
963 quoteFix (mainPattern, positivePrefix, patChars);
964 mainPattern.append(main);
965 quoteFix (mainPattern, positiveSuffix, patChars);
967 if (negativePrefix != null)
969 quoteFix (mainPattern, negativePrefix, patChars);
970 mainPattern.append(main);
971 quoteFix (mainPattern, negativeSuffix, patChars);
974 return mainPattern.toString();
977 public String toLocalizedPattern ()
979 return computePattern (symbols);
982 public String toPattern ()
984 return computePattern (nonLocalizedSymbols);
987 // These names are fixed by the serialization spec.
988 private boolean decimalSeparatorAlwaysShown;
989 private byte groupingSize;
990 private byte minExponentDigits;
991 private int multiplier;
992 private String negativePrefix;
993 private String negativeSuffix;
994 private String positivePrefix;
995 private String positiveSuffix;
996 private int serialVersionOnStream = 1;
997 private DecimalFormatSymbols symbols;
998 private boolean useExponentialNotation;
999 private static final long serialVersionUID = 864413376551465018L;
1001 private void readObject(ObjectInputStream stream)
1002 throws IOException, ClassNotFoundException
1004 stream.defaultReadObject();
1005 if (serialVersionOnStream < 1)
1007 useExponentialNotation = false;
1008 serialVersionOnStream = 1;
1012 // The locale-independent pattern symbols happen to be the same as
1014 private static final DecimalFormatSymbols nonLocalizedSymbols
1015 = new DecimalFormatSymbols (Locale.US);