Search code examples
javadesign-patternsdecimalformat

Java DecimalFormat pattern, differences between # and 0


I have the following code:

public class Main {
    public static void main(String[] args) {
        String pattern = ".00";         // a) => |.46|
//        String pattern = ".##";       // b) => |.46|
//        String pattern = ".0#";       // c) => |.46|
//        String pattern = "#.00";      // d) => |.46|
//        String pattern = "#.0#";      // e) => |.46|
//        String pattern = "#.##";      // f) => |0.46|
//        String pattern = ".#0";       // g) => IllegalArgumentException: Malformed pattern ".#0"

    double value = 0.456;
    DecimalFormat df = (DecimalFormat) NumberFormat.getNumberInstance(Locale.US);
    df.applyPattern(pattern);
    String output = df.format(value);
    System.out.printf("|%s|", output);
   }  
}

Could someone explain that why in f) the zero before decimal point still displays while d) and e) don't even though they are all have a '#' before decimal point in the pattern? Also, what could be the reason for the exception in g) as it seems to me a valid pattern?


Solution

  • In case "f", the zero shows up because Java prints a 0 if it's not guaranteed that there is a fraction part to print. The below code is taken from JDK 17 source code for DecimalFormat. Because you have all # after the decimal point, minFraDigits is 0 so it can't assume that a fraction is present. It apparently doesn't see that there is a fraction part to print yet. It guards against printing nothing if the value is zero.

    
    // Determine whether or not there are any printable fractional
    // digits.  If we've used up the digits we know there aren't.
    boolean fractionPresent = (minFraDigits > 0) ||
            (!isInteger && digitIndex < digitList.count);
    
    // If there is no fraction present, and we haven't printed any
    // integer digits, then print a zero.  Otherwise we won't print
    // _any_ digits, and we won't be able to parse this string.
    if (!fractionPresent && result.length() == sizeBeforeIntegerPart) {
        result.append(zero);
    }
    

    The case "g" format is an invalid format. You can't have a 0 further from the decimal point than a #. It's enforced in the parsing grammar in the DecimalFormat javadocs. For the Fraction part, the zeros must come first if they exist, followed by optional #s.

    Fraction:
            MinimumFractionopt OptionalFractionopt

    MinimumFraction:
            0 MinimumFractionopt

    OptionalFraction:
            # OptionalFractionopt

    Another part of the grammar similar to this enforces that before the decimal point, any #s must be before any 0s, the reverse of after the decimal point.