Reputation: 2724
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
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"));
}
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
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
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
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
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