mac
mac

Reputation: 2724

Format float to maximum N decimal places

I'd like to format a number to maximum N decimal places. There's another similar and popular question here, but that's not exactly what I'm looking for.

I'm looking for something like this, let's say I want max. 2 places, so this would be:

1.00  -> "1"    (not "1.00")
1.20  -> "1.2"  (not "1.20")
1.23  -> "1.23"
1.234 -> "1.23"
1.235 -> "1.24"

The difference to the other question is that I don't want trailing zeros behind the comma if I don't need them.

I'd like to know whether this is doable with String.format(), not with Math.round(), or DecimalFormat. The other question shown above provides a solution with DecimalFormat.

The answer does not need to be variable given N as an argument. I just chose N as an example.

Upvotes: 11

Views: 8493

Answers (5)

sxc731
sxc731

Reputation: 2638

Possibly the most concise way using String.format() would be through the fmt expression shown in the following test program:

static String fmtN(double d) {
    String fmt = String.format("%.2f", d).replaceFirst("\\.?(\\.[1-9])?0+$", "$1");
    System.out.println(fmt);
    return fmt;
}

@Test
public void test() {
    assertThat(fmtN(1), is("1"));
    assertThat(fmtN(1.05), is("1.05"));
    assertThat(fmtN(1.2), is("1.2"));
    assertThat(fmtN(1.23), is("1.23"));
    assertThat(fmtN(1.234), is("1.23"));
    assertThat(fmtN(1.235), is("1.24"));
}

All the gory details

Formatters available to String.format() don't readily allow specifying "maximum N" decimal places (only "exactly 2" for example, as in .2) so we have to use a Regex to strip trailing zeroes. This is a bit harder than it looks for the first case (where there are no decimal places), in order to also suppress the trailing ..

Here are a few key points that make this work:

  • ? makes the preceding group or pattern optional
  • (...) delimits a capturing group, which can be re-introduced in the replacement string using its positional number; so here $1 reproduces the capturing group (\.[1-9])
  • + is for one or more occurrences of the preceding pattern
  • \. escapes the . (decimal point) character, so it's used as a literal rather than its regex meaning of "any character" (backslashes have to themselves to be escaped in Java Strings, so every regex backslash is written \\).

Pfeeew... So the capturing group (\.[1-9]), meaning a . followed by any non-zero digit is marked optional using the ? suffix; so the entire regex doesn't fail to match in case there are no non-zero decimal places, as in the test string 1.00. If that group is present, it gets replicated using $1 as we saw above. If it isn't present (as in 1.00), we want the . (decimal point) stripped so it gets captured by \.?. Trailing zeroes are captured by 0+$ ($ stands for end of candidate string) and discarded.

Finally I used replaceFirst() as opposed to replaceAll() as a minor optimization; once the regex has done it's job, we can stop looking for more matches.

Upvotes: 0

Matthias Gerth
Matthias Gerth

Reputation: 91

Use NumberFormat.

NumberFormat format = NumberFormat.getInstance();
format.setMaximumFractionDigits(2); // or N

System.out.println(format.format(1)); // -> 1
System.out.println(format.format(1.2)); // -> 1.2
System.out.println(format.format(1.23)); // -> 1.23
System.out.println(format.format(1.234)); // -> 1.23
System.out.println(format.format(1.235)); // -> 1.24

NumberFormat also works with Locales and you can change the rounding mode.

NumberFormat format = NumberFormat.getInstance(Locale.GERMANY);
format.setMaximumFractionDigits(2);
format.setRoundingMode(RoundingMode.CEILING);

System.out.println(format.format(1)); // -> 1
System.out.println(format.format(1.2)); // -> 1,2
System.out.println(format.format(1.23)); // -> 1,23
System.out.println(format.format(1.234)); // -> 1,24
System.out.println(format.format(1.235)); // -> 1,24

Upvotes: 2

Federico Piazza
Federico Piazza

Reputation: 30985

You can use DecimalFormat.

Quoting the documentation:

You can use the DecimalFormat class to format decimal numbers into locale-specific strings. This class allows you to control the display of leading and trailing zeros, prefixes and suffixes, grouping (thousands) separators, and the decimal separator.

The pound sign (#) denotes a digit and the period is a placeholder for the decimal separator.

public void test(){
    DecimalFormat df = new DecimalFormat("#.##");
    System.out.println(df.format(1.00));
    System.out.println(df.format(1.20));
    System.out.println(df.format(1.23));
    System.out.println(df.format(1.234));
    System.out.println(df.format(1.235));
}

Output:

1
1.2
1.23
1.23
1.24

Update: since you updated the question and you wanted to use String.format, searching in SO found this thread and leverage a trick plus regex. So, you could use something like this:

public static void main (String[] args) throws java.lang.Exception
{
    System.out.println(fmt(1.00));
    System.out.println(fmt(1.20));
    System.out.println(fmt(1.23));
    System.out.println(fmt(1.234));
    System.out.println(fmt(1.235));
}

public static String fmt(double d)
{
    if(d == (long) d)
        return String.format("%d",(long)d);
    else
        return String.format("%.2f",d).replaceAll("0*$", "");
}

The output is:

1
1.2
1.23
1.23
1.24

Anyway, I would use DecimalFormat instead.

Upvotes: 10

clstrfsck
clstrfsck

Reputation: 14829

You can also control the formatting of DecimalFormat using setMaximumFractionDigits(...) like so:

double d = 1.234567;
DecimalFormat df = new DecimalFormat();
for (int i = 2; i < 6; ++i) {
  df.setMaximumFractionDigits(i);
  System.out.println(df.format(d));
}

This might be better for your use case than generating a format using StringBuilder or similar.

Upvotes: 3

PseudoAj
PseudoAj

Reputation: 5941

Another solution if you still want to do it with String.format(), here is the solution:

Java Code: ParseN.java

public class ParseN{

     public static void main(String []args){
        System.out.println(parseN(1.00));
        System.out.println(parseN(1.20));
        System.out.println(parseN(1.23));
        System.out.println(parseN(1.234));
        System.out.println(parseN(1.235));
     }

     static String parseN(Double d)
     {
       String s = String.format("%.2f", d);
       s = s.indexOf(".") < 0 ? s : s.replaceAll("0*$", "").replaceAll("\\.$", "");
       return s;
     }
}

output:

1
1.2
1.23
1.23
1.24

Hope it fully answers your question. Also, refer this.

Upvotes: 0

Related Questions