Beware of DecimalFormat
Parsing user input into numbers (especially numbers representing money) is a very common task. Such parsing is often implemented with DecimalFormat. However, parsing with DecimalFormat should be done with care. You may even want to consider an alternative. Here's why:
- surprisingly, a Format does not need to parse all of its input. That is, if it finds a match at the start of an input String, it will cease parsing, and return a value. When parsing user input, this kind of behavior is not usually desired. To accomodate this behavior, an extra step is needed - a ParsePosition object needs to be passed as an output parameter to DecimalFormat.
- the class seems to be rather buggy.
- the class is harder than most to understand.
Here's an example of parsing with DecimalFormat. An example run appears below. As you can see,
- parsing user input will always require the caller to use ParsePosition to detect if the whole input has been parsed.
- there are cases when the parsing seems just plain wrong.
import java.text.*; public final class BewareDecimalFormat { /** * parseUserInput DecimalFormat. */ public static void main (String... aArguments) { String patternOne = "#,##0.00"; log("Format : " + patternOne); parseUserInput("1000", patternOne); parseUserInput("1,000.00", patternOne); parseUserInput("1000.33", patternOne); parseUserInput(".20", patternOne); parseUserInput(".2", patternOne); parseUserInput(".222", patternOne); parseUserInput(".222333444", patternOne); parseUserInput("100.222333444", patternOne); parseUserInput(".22A", patternOne); parseUserInput(".22BlahA", patternOne); parseUserInput("22Blah", patternOne); parseUserInput("Blah22", patternOne); parseUserInput("100000,000.00", patternOne); parseUserInput("10,0000,000.00", patternOne); parseUserInput("1,0000,0000.00", patternOne); String patternTwo= "#,###0.00"; //group every 4 digits, not 3 log(" "); log("Format : " + patternTwo); parseUserInput("1000.00", patternTwo); parseUserInput("1,000.00", patternTwo); parseUserInput("1,0000.00", patternTwo); parseUserInput("1,00000.00", patternTwo); log("Done."); } // PRIVATE private static void parseUserInput(String aValue, String aDecimalFormatPattern) { DecimalFormat format = new DecimalFormat(aDecimalFormatPattern); ParsePosition parsePosition = new ParsePosition(0); Object object = format.parse(aValue, parsePosition); if(object == null) { log("Failed to parse: " + aValue); } else if(parsePosition.getIndex() < aValue.length()) { log( aValue + " parsed OK (not whole input) ParsePos:" + parsePosition.getIndex() + ", Parse Result: " + object ); } else { log( aValue + " parsed OK. ParsePos: " + parsePosition.getIndex() + ", Parse Result: " + object ); } } private static void log(Object aMessage){ System.out.println(String.valueOf(aMessage)); } }
Example run:
>java -cp . BewareDecimalFormat Format : #,##0.00 1000 parsed OK. ParsePos: 4, Parse Result: 1000 1,000.00 parsed OK. ParsePos: 8, Parse Result: 1000 1000.33 parsed OK. ParsePos: 7, Parse Result: 1000.33 .20 parsed OK. ParsePos: 3, Parse Result: 0.2 .2 parsed OK. ParsePos: 2, Parse Result: 0.2 .222 parsed OK. ParsePos: 4, Parse Result: 0.222 .222333444 parsed OK. ParsePos: 10, Parse Result: 0.222333444 100.222333444 parsed OK. ParsePos: 13, Parse Result: 100.222333444 .22A parsed OK (not whole input) ParsePos:3, Parse Result: 0.22 .22BlahA parsed OK (not whole input) ParsePos:3, Parse Result: 0.22 22Blah parsed OK (not whole input) ParsePos:2, Parse Result: 22 Failed to parse: Blah22 100000,000.00 parsed OK. ParsePos: 13, Parse Result: 100000000 10,0000,000.00 parsed OK. ParsePos: 14, Parse Result: 100000000 1,0000,0000.00 parsed OK. ParsePos: 14, Parse Result: 100000000 Format : #,###0.00 1000.00 parsed OK. ParsePos: 7, Parse Result: 1000 1,000.00 parsed OK. ParsePos: 8, Parse Result: 1000 1,0000.00 parsed OK. ParsePos: 9, Parse Result: 10000 1,00000.00 parsed OK. ParsePos: 10, Parse Result: 100000 Done.
Would you use this technique?