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 DecimalFormat c
= (DecimalFormat
) super.clone ();
354 c
.symbols
= (DecimalFormatSymbols
) symbols
.clone ();
358 public DecimalFormat ()
363 public DecimalFormat (String pattern
)
365 this (pattern
, new DecimalFormatSymbols ());
368 public DecimalFormat (String pattern
, DecimalFormatSymbols symbols
)
370 this.symbols
= symbols
;
371 applyPattern (pattern
);
374 private final boolean equals (String s1
, String s2
)
376 if (s1
== null || s2
== null)
378 return s1
.equals(s2
);
381 public boolean equals (Object obj
)
383 if (! (obj
instanceof DecimalFormat
))
385 DecimalFormat dup
= (DecimalFormat
) obj
;
386 return (decimalSeparatorAlwaysShown
== dup
.decimalSeparatorAlwaysShown
387 && groupingSize
== dup
.groupingSize
388 && minExponentDigits
== dup
.minExponentDigits
389 && multiplier
== dup
.multiplier
390 && equals(negativePrefix
, dup
.negativePrefix
)
391 && equals(negativeSuffix
, dup
.negativeSuffix
)
392 && equals(positivePrefix
, dup
.positivePrefix
)
393 && equals(positiveSuffix
, dup
.positiveSuffix
)
394 && symbols
.equals(dup
.symbols
)
395 && useExponentialNotation
== dup
.useExponentialNotation
);
398 public StringBuffer
format (double number
, StringBuffer dest
,
399 FieldPosition fieldPos
)
401 // A very special case.
402 if (Double
.isNaN(number
))
404 dest
.append(symbols
.getNaN());
405 if (fieldPos
!= null && fieldPos
.getField() == INTEGER_FIELD
)
407 int index
= dest
.length();
408 fieldPos
.setBeginIndex(index
- symbols
.getNaN().length());
409 fieldPos
.setEndIndex(index
);
414 boolean is_neg
= number
< 0;
417 if (negativePrefix
!= null)
418 dest
.append(negativePrefix
);
421 dest
.append(symbols
.getMinusSign());
422 dest
.append(positivePrefix
);
427 dest
.append(positivePrefix
);
429 int integerBeginIndex
= dest
.length();
430 int integerEndIndex
= 0;
431 if (Double
.isInfinite (number
))
433 dest
.append(symbols
.getInfinity());
434 integerEndIndex
= dest
.length();
438 number
*= multiplier
;
443 if (useExponentialNotation
)
445 exponent
= (long) (Math
.log(number
) / Math
.log(10));
446 if (minimumIntegerDigits
> 0)
447 exponent
-= minimumIntegerDigits
- 1;
448 baseNumber
= (long) (number
/ Math
.pow(10.0, exponent
));
453 // Round to the correct number of digits.
454 baseNumber
+= 5 * Math
.pow(10.0, - maximumFractionDigits
- 1);
456 int index
= dest
.length();
457 double intPart
= Math
.floor(baseNumber
);
459 while (count
< maximumIntegerDigits
460 && (intPart
> 0 || count
< minimumIntegerDigits
))
462 long dig
= (long) (intPart
% 10);
463 intPart
= Math
.floor(intPart
/ 10);
465 // Append group separator if required.
466 if (groupingUsed
&& count
> 0 && count
% groupingSize
== 0)
467 dest
.insert(index
, symbols
.getGroupingSeparator());
469 dest
.insert(index
, (char) (symbols
.getZeroDigit() + dig
));
474 integerEndIndex
= dest
.length();
476 int decimal_index
= integerEndIndex
;
477 int consecutive_zeros
= 0;
478 int total_digits
= 0;
480 // Strip integer part from NUMBER.
481 double fracPart
= baseNumber
- Math
.floor(baseNumber
);
483 count
< maximumFractionDigits
484 && (fracPart
!= 0 || count
< minimumFractionDigits
);
489 long dig
= (long) fracPart
;
493 consecutive_zeros
= 0;
494 dest
.append((char) (symbols
.getZeroDigit() + dig
));
496 // Strip integer part from FRACPART.
497 fracPart
= fracPart
- Math
.floor (fracPart
);
500 // Strip extraneous trailing `0's. We can't always detect
501 // these in the loop.
502 int extra_zeros
= Math
.min (consecutive_zeros
,
503 total_digits
- minimumFractionDigits
);
506 dest
.setLength(dest
.length() - extra_zeros
);
507 total_digits
-= extra_zeros
;
510 // If required, add the decimal symbol.
511 if (decimalSeparatorAlwaysShown
514 dest
.insert(decimal_index
, symbols
.getDecimalSeparator());
515 if (fieldPos
!= null && fieldPos
.getField() == FRACTION_FIELD
)
517 fieldPos
.setBeginIndex(decimal_index
+ 1);
518 fieldPos
.setEndIndex(dest
.length());
522 // Finally, print the exponent.
523 if (useExponentialNotation
)
525 dest
.append(symbols
.getExponential());
526 dest
.append(exponent
< 0 ?
'-' : '+');
527 index
= dest
.length();
529 exponent
> 0 || count
< minExponentDigits
;
532 long dig
= exponent
% 10;
534 dest
.insert(index
, (char) (symbols
.getZeroDigit() + dig
));
539 if (fieldPos
!= null && fieldPos
.getField() == INTEGER_FIELD
)
541 fieldPos
.setBeginIndex(integerBeginIndex
);
542 fieldPos
.setEndIndex(integerEndIndex
);
545 dest
.append((is_neg
&& negativeSuffix
!= null)
551 public StringBuffer
format (long number
, StringBuffer dest
,
552 FieldPosition fieldPos
)
554 // If using exponential notation, we just format as a double.
555 if (useExponentialNotation
)
556 return format ((double) number
, dest
, fieldPos
);
558 boolean is_neg
= number
< 0;
561 if (negativePrefix
!= null)
562 dest
.append(negativePrefix
);
565 dest
.append(symbols
.getMinusSign());
566 dest
.append(positivePrefix
);
571 dest
.append(positivePrefix
);
573 int integerBeginIndex
= dest
.length();
574 int index
= dest
.length();
576 while (count
< maximumIntegerDigits
577 && (number
> 0 || count
< minimumIntegerDigits
))
579 long dig
= number
% 10;
581 // NUMBER and DIG will be less than 0 if the original number
582 // was the most negative long.
589 // Append group separator if required.
590 if (groupingUsed
&& count
> 0 && count
% groupingSize
== 0)
591 dest
.insert(index
, symbols
.getGroupingSeparator());
593 dest
.insert(index
, (char) (symbols
.getZeroDigit() + dig
));
598 if (fieldPos
!= null && fieldPos
.getField() == INTEGER_FIELD
)
600 fieldPos
.setBeginIndex(integerBeginIndex
);
601 fieldPos
.setEndIndex(dest
.length());
604 if (decimalSeparatorAlwaysShown
|| minimumFractionDigits
> 0)
606 dest
.append(symbols
.getDecimalSeparator());
607 if (fieldPos
!= null && fieldPos
.getField() == FRACTION_FIELD
)
609 fieldPos
.setBeginIndex(dest
.length());
610 fieldPos
.setEndIndex(dest
.length() + minimumFractionDigits
);
614 for (count
= 0; count
< minimumFractionDigits
; ++count
)
615 dest
.append(symbols
.getZeroDigit());
617 dest
.append((is_neg
&& negativeSuffix
!= null)
623 public DecimalFormatSymbols
getDecimalFormatSymbols ()
628 public int getGroupingSize ()
633 public int getMultiplier ()
638 public String
getNegativePrefix ()
640 return negativePrefix
;
643 public String
getNegativeSuffix ()
645 return negativeSuffix
;
648 public String
getPositivePrefix ()
650 return positivePrefix
;
653 public String
getPositiveSuffix ()
655 return positiveSuffix
;
658 public int hashCode ()
660 int hash
= (negativeSuffix
.hashCode() ^ negativePrefix
.hashCode()
661 ^positivePrefix
.hashCode() ^ positiveSuffix
.hashCode());
666 public boolean isDecimalSeparatorAlwaysShown ()
668 return decimalSeparatorAlwaysShown
;
671 public Number
parse (String str
, ParsePosition pos
)
673 // Our strategy is simple: copy the text into a buffer,
674 // translating or omitting locale-specific information. Then
675 // let Double or Long convert the number for us.
677 boolean is_neg
= false;
678 int index
= pos
.getIndex();
679 StringBuffer buf
= new StringBuffer ();
681 // We have to check both prefixes, because one might be empty.
682 // We want to pick the longest prefix that matches.
683 boolean got_pos
= str
.startsWith(positivePrefix
, index
);
684 String np
= (negativePrefix
!= null
686 : positivePrefix
+ symbols
.getMinusSign());
687 boolean got_neg
= str
.startsWith(np
, index
);
689 if (got_pos
&& got_neg
)
691 // By checking this way, we preserve ambiguity in the case
692 // where the negative format differs only in suffix. We
693 // check this again later.
694 if (np
.length() > positivePrefix
.length())
697 index
+= np
.length();
700 index
+= positivePrefix
.length();
705 index
+= np
.length();
708 index
+= positivePrefix
.length();
711 pos
.setErrorIndex (index
);
715 // FIXME: handle Inf and NaN.
717 // FIXME: do we have to respect minimum/maxmimum digit stuff?
718 // What about leading zeros? What about multiplier?
720 int start_index
= index
;
721 int max
= str
.length();
722 char zero
= symbols
.getZeroDigit();
724 boolean int_part
= true;
725 boolean exp_part
= false;
726 for (; index
< max
; ++index
)
728 char c
= str
.charAt(index
);
730 // FIXME: what about grouping size?
731 if (groupingUsed
&& c
== symbols
.getGroupingSeparator())
734 && (index
- last_group
) % groupingSize
!= 0)
736 pos
.setErrorIndex(index
);
741 else if (c
>= zero
&& c
<= zero
+ 9)
743 buf
.append((char) (c
- zero
+ '0'));
746 else if (parseIntegerOnly
)
748 else if (c
== symbols
.getDecimalSeparator())
751 && (index
- last_group
) % groupingSize
!= 0)
753 pos
.setErrorIndex(index
);
759 else if (c
== symbols
.getExponential())
766 && (c
== '+' || c
== '-' || c
== symbols
.getMinusSign()))
768 // For exponential notation.
775 if (index
== start_index
)
777 // Didn't see any digits.
778 pos
.setErrorIndex(index
);
782 // Check the suffix. We must do this before converting the
783 // buffer to a number to handle the case of a number which is
784 // the most negative Long.
785 boolean got_pos_suf
= str
.startsWith(positiveSuffix
, index
);
786 String ns
= (negativePrefix
== null ? positiveSuffix
: negativeSuffix
);
787 boolean got_neg_suf
= str
.startsWith(ns
, index
);
792 pos
.setErrorIndex(index
);
796 else if (got_pos
&& got_neg
&& got_neg_suf
)
800 else if (got_pos
!= got_pos_suf
&& got_neg
!= got_neg_suf
)
802 pos
.setErrorIndex(index
);
806 String suffix
= is_neg ? ns
: positiveSuffix
;
810 String t
= buf
.toString();
811 Number result
= null;
814 result
= new Long (t
);
816 catch (NumberFormatException x1
)
820 result
= new Double (t
);
822 catch (NumberFormatException x2
)
828 pos
.setErrorIndex(index
);
832 pos
.setIndex(index
+ suffix
.length());
837 public void setDecimalFormatSymbols (DecimalFormatSymbols newSymbols
)
839 symbols
= newSymbols
;
842 public void setDecimalSeparatorAlwaysShown (boolean newValue
)
844 decimalSeparatorAlwaysShown
= newValue
;
847 public void setGroupingSize (int groupSize
)
849 groupingSize
= (byte) groupSize
;
852 public void setMaximumFractionDigits (int newValue
)
854 maximumFractionDigits
= Math
.min(newValue
, 340);
857 public void setMaximumIntegerDigits (int newValue
)
859 maximumIntegerDigits
= Math
.min(newValue
, 309);
862 public void setMinimumFractionDigits (int newValue
)
864 minimumFractionDigits
= Math
.min(newValue
, 340);
867 public void setMinimumIntegerDigits (int newValue
)
869 minimumIntegerDigits
= Math
.min(newValue
, 309);
872 public void setMultiplier (int newValue
)
874 multiplier
= newValue
;
877 public void setNegativePrefix (String newValue
)
879 negativePrefix
= newValue
;
882 public void setNegativeSuffix (String newValue
)
884 negativeSuffix
= newValue
;
887 public void setPositivePrefix (String newValue
)
889 positivePrefix
= newValue
;
892 public void setPositiveSuffix (String newValue
)
894 positiveSuffix
= newValue
;
897 private final void quoteFix (StringBuffer buf
, String text
, String patChars
)
899 int len
= text
.length();
900 for (int index
= 0; index
< len
; ++index
)
902 char c
= text
.charAt(index
);
903 if (patChars
.indexOf(c
) != -1)
914 private final String
computePattern (DecimalFormatSymbols syms
)
916 StringBuffer mainPattern
= new StringBuffer ();
917 // We have to at least emit a zero for the minimum number of
918 // digits. Past that we need hash marks up to the grouping
919 // separator (and one beyond).
920 int total_digits
= Math
.max(minimumIntegerDigits
,
921 groupingUsed ? groupingSize
+ 1: 0);
922 for (int i
= 0; i
< total_digits
- minimumIntegerDigits
; ++i
)
923 mainPattern
.append(syms
.getDigit());
924 for (int i
= total_digits
- minimumIntegerDigits
; i
< total_digits
; ++i
)
925 mainPattern
.append(syms
.getZeroDigit());
926 // Inserting the gropuing operator afterwards is easier.
928 mainPattern
.insert(mainPattern
.length() - groupingSize
,
929 syms
.getGroupingSeparator());
930 // See if we need decimal info.
931 if (minimumFractionDigits
> 0 || maximumFractionDigits
> 0
932 || decimalSeparatorAlwaysShown
)
933 mainPattern
.append(syms
.getDecimalSeparator());
934 for (int i
= 0; i
< minimumFractionDigits
; ++i
)
935 mainPattern
.append(syms
.getZeroDigit());
936 for (int i
= minimumFractionDigits
; i
< maximumFractionDigits
; ++i
)
937 mainPattern
.append(syms
.getDigit());
938 if (useExponentialNotation
)
940 mainPattern
.append(syms
.getExponential());
941 for (int i
= 0; i
< minExponentDigits
; ++i
)
942 mainPattern
.append(syms
.getZeroDigit());
943 if (minExponentDigits
== 0)
944 mainPattern
.append(syms
.getDigit());
947 String main
= mainPattern
.toString();
948 String patChars
= patternChars (syms
);
949 mainPattern
.setLength(0);
951 quoteFix (mainPattern
, positivePrefix
, patChars
);
952 mainPattern
.append(main
);
953 quoteFix (mainPattern
, positiveSuffix
, patChars
);
955 if (negativePrefix
!= null)
957 quoteFix (mainPattern
, negativePrefix
, patChars
);
958 mainPattern
.append(main
);
959 quoteFix (mainPattern
, negativeSuffix
, patChars
);
962 return mainPattern
.toString();
965 public String
toLocalizedPattern ()
967 return computePattern (symbols
);
970 public String
toPattern ()
972 return computePattern (nonLocalizedSymbols
);
975 // These names are fixed by the serialization spec.
976 private boolean decimalSeparatorAlwaysShown
;
977 private byte groupingSize
;
978 private byte minExponentDigits
;
979 private int multiplier
;
980 private String negativePrefix
;
981 private String negativeSuffix
;
982 private String positivePrefix
;
983 private String positiveSuffix
;
984 private int serialVersionOnStream
= 1;
985 private DecimalFormatSymbols symbols
;
986 private boolean useExponentialNotation
;
987 private static final long serialVersionUID
= 864413376551465018L;
989 private void readObject(ObjectInputStream stream
)
990 throws IOException
, ClassNotFoundException
992 stream
.defaultReadObject();
993 if (serialVersionOnStream
< 1)
995 useExponentialNotation
= false;
996 serialVersionOnStream
= 1;
1000 // The locale-independent pattern symbols happen to be the same as
1002 private static final DecimalFormatSymbols nonLocalizedSymbols
1003 = new DecimalFormatSymbols (Locale
.US
);