[java] How to nicely format floating numbers to string without unnecessary decimal 0's

A 64-bit double can represent integer +/- 253 exactly.

Given this fact, I choose to use a double type as a single type for all my types, since my largest integer is an unsigned 32-bit number.

But now I have to print these pseudo integers, but the problem is they are also mixed in with actual doubles.

So how do I print these doubles nicely in Java?

I have tried String.format("%f", value), which is close, except I get a lot of trailing zeros for small values.

Here's an example output of of %f

232.00000000
0.18000000000
1237875192.0
4.5800000000
0.00000000
1.23450000

What I want is:

232
0.18
1237875192
4.58
0
1.2345

Sure I can write a function to trim those zeros, but that's lot of performance loss due to string manipulation. Can I do better with other format code?


The answers by Tom E. and Jeremy S. are unacceptable as they both arbitrarily rounds to two decimal places. Please understand the problem before answering.


Please note that String.format(format, args...) is locale-dependent (see answers below).

This question is related to java string floating-point format double

The answer is


public static String fmt(double d) {
    String val = Double.toString(d);
    String[] valArray = val.split("\\.");
    long valLong = 0;
    if(valArray.length == 2) {
        valLong = Long.parseLong(valArray[1]);
    }
     if (valLong == 0)
        return String.format("%d", (long) d);
    else
        return String.format("%s", d);
}

I had to use this because d == (long)d was giving me violation in a SonarQube report.


Format price with grouping, rounding, and no unnecessary zeroes (in double).

Rules:

  1. No zeroes at the end (2.0000 = 2; 1.0100000 = 1.01)
  2. Two digits maximum after a point (2.010 = 2.01; 0.20 = 0.2)
  3. Rounding after the 2nd digit after a point (1.994 = 1.99; 1.995 = 2; 1.006 = 1.01; 0.0006 -> 0)
  4. Returns 0 (null/-0 = 0)
  5. Adds $ (= $56/-$56)
  6. Grouping (101101.02 = $101,101.02)

More examples:

-99.985 = -$99.99

10 = $10

10.00 = $10

20.01000089 = $20.01

It is written in Kotlin as a fun extension of Double (because it is used in Android), but it can be converted to Java easily, because Java classes were used.

/**
 * 23.0 -> $23
 *
 * 23.1 -> $23.1
 *
 * 23.01 -> $23.01
 *
 * 23.99 -> $23.99
 *
 * 23.999 -> $24
 *
 * -0.0 -> $0
 *
 * -5.00 -> -$5
 *
 * -5.019 -> -$5.02
 */
fun Double?.formatUserAsSum(): String {
    return when {
        this == null || this == 0.0 -> "$0"
        this % 1 == 0.0 -> DecimalFormat("$#,##0;-$#,##0").format(this)
        else -> DecimalFormat("$#,##0.##;-$#,##0.##").format(this)
    }
}

How to use:

var yourDouble: Double? = -20.00
println(yourDouble.formatUserAsSum()) // will print -$20

yourDouble = null
println(yourDouble.formatUserAsSum()) // will print $0

About DecimalFormat: https://docs.oracle.com/javase/6/docs/api/java/text/DecimalFormat.html


if (d == Math.floor(d)) {
    return String.format("%.0f", d);
} else {
    return Double.toString(d);
}

Use a DecimalFormat and setMinimumFractionDigits(0).


new DecimalFormat("00.#").format(20.236)
//out =20.2

new DecimalFormat("00.#").format(2.236)
//out =02.2
  1. 0 for minimum number of digits
  2. Renders # digits

You said you choose to store your numbers with the double type. I think this could be the root of the problem, because it forces you to store integers into doubles (and therefore losing the initial information about the value's nature). What about storing your numbers in instances of the Number class (superclass of both Double and Integer) and rely on polymorphism to determine the correct format of each number?

I know it may not be acceptable to refactor a whole part of your code due to that, but it could produce the desired output without extra code/casting/parsing.

Example:

import java.util.ArrayList;
import java.util.List;

public class UseMixedNumbers {

    public static void main(String[] args) {
        List<Number> listNumbers = new ArrayList<Number>();

        listNumbers.add(232);
        listNumbers.add(0.18);
        listNumbers.add(1237875192);
        listNumbers.add(4.58);
        listNumbers.add(0);
        listNumbers.add(1.2345);

        for (Number number : listNumbers) {
            System.out.println(number);
        }
    }

}

Will produce the following output:

232
0.18
1237875192
4.58
0
1.2345

This one will gets the job done nicely:

    public static String removeZero(double number) {
        DecimalFormat format = new DecimalFormat("#.###########");
        return format.format(number);
    }

I am using this for formatting numbers without trailing zeroes in our JSF application. The original built-in formatters required you to specify max numbers of fractional digits which could be useful here also in case you have too many fractional digits.

/**
 * Formats the given Number as with as many fractional digits as precision
 * available.<br>
 * This is a convenient method in case all fractional digits shall be
 * rendered and no custom format / pattern needs to be provided.<br>
 * <br>
 * This serves as a workaround for {@link NumberFormat#getNumberInstance()}
 * which by default only renders up to three fractional digits.
 *
 * @param number
 * @param locale
 * @param groupingUsed <code>true</code> if grouping shall be used
 *
 * @return
 */
public static String formatNumberFraction(final Number number, final Locale locale, final boolean groupingUsed)
{
    if (number == null)
        return null;

    final BigDecimal bDNumber = MathUtils.getBigDecimal(number);

    final NumberFormat numberFormat = NumberFormat.getNumberInstance(locale);
    numberFormat.setMaximumFractionDigits(Math.max(0, bDNumber.scale()));
    numberFormat.setGroupingUsed(groupingUsed);

    // Convert back for locale percent formatter
    return numberFormat.format(bDNumber);
}

/**
 * Formats the given Number as percent with as many fractional digits as
 * precision available.<br>
 * This is a convenient method in case all fractional digits shall be
 * rendered and no custom format / pattern needs to be provided.<br>
 * <br>
 * This serves as a workaround for {@link NumberFormat#getPercentInstance()}
 * which does not renders fractional digits.
 *
 * @param number Number in range of [0-1]
 * @param locale
 *
 * @return
 */
public static String formatPercentFraction(final Number number, final Locale locale)
{
    if (number == null)
        return null;

    final BigDecimal bDNumber = MathUtils.getBigDecimal(number).multiply(new BigDecimal(100));

    final NumberFormat percentScaleFormat = NumberFormat.getPercentInstance(locale);
    percentScaleFormat.setMaximumFractionDigits(Math.max(0, bDNumber.scale() - 2));

    final BigDecimal bDNumberPercent = bDNumber.multiply(new BigDecimal(0.01));

    // Convert back for locale percent formatter
    final String strPercent = percentScaleFormat.format(bDNumberPercent);

    return strPercent;
}

String s = String.valueof("your int variable");
while (g.endsWith("0") && g.contains(".")) {
    g = g.substring(0, g.length() - 1);
    if (g.endsWith("."))
    {
        g = g.substring(0, g.length() - 1);
    }
}

In short:

If you want to get rid of trailing zeros and locale problems, then you should use:

double myValue = 0.00000021d;

DecimalFormat df = new DecimalFormat("0", DecimalFormatSymbols.getInstance(Locale.ENGLISH));
df.setMaximumFractionDigits(340); //340 = DecimalFormat.DOUBLE_FRACTION_DIGITS

System.out.println(df.format(myValue)); //output: 0.00000021

Explanation:

Why other answers did not suit me:

  • Double.toString() or System.out.println or FloatingDecimal.toJavaFormatString uses scientific notations if double is less than 10^-3 or greater than or equal to 10^7

     double myValue = 0.00000021d;
     String.format("%s", myvalue); //output: 2.1E-7
    
  • by using %f, the default decimal precision is 6, otherwise you can hardcode it, but it results in extra zeros added if you have fewer decimals. Example:

     double myValue = 0.00000021d;
     String.format("%.12f", myvalue); // Output: 0.000000210000
    
  • by using setMaximumFractionDigits(0); or %.0f you remove any decimal precision, which is fine for integers/longs but not for double

     double myValue = 0.00000021d;
     System.out.println(String.format("%.0f", myvalue)); // Output: 0
     DecimalFormat df = new DecimalFormat("0");
     System.out.println(df.format(myValue)); // Output: 0
    
  • by using DecimalFormat, you are local dependent. In the French locale, the decimal separator is a comma, not a point:

     double myValue = 0.00000021d;
     DecimalFormat df = new DecimalFormat("0");
     df.setMaximumFractionDigits(340);
     System.out.println(df.format(myvalue)); // Output: 0,00000021
    

    Using the ENGLISH locale makes sure you get a point for decimal separator, wherever your program will run.

Why using 340 then for setMaximumFractionDigits?

Two reasons:

  • setMaximumFractionDigits accepts an integer, but its implementation has a maximum digits allowed of DecimalFormat.DOUBLE_FRACTION_DIGITS which equals 340
  • Double.MIN_VALUE = 4.9E-324 so with 340 digits you are sure not to round your double and lose precision

Please note that String.format(format, args...) is locale-dependent because it formats using the user's default locale, that is, probably with commas and even spaces inside like 123 456,789 or 123,456.789, which may be not exactly what you expect.

You may prefer to use String.format((Locale)null, format, args...).

For example,

    double f = 123456.789d;
    System.out.println(String.format(Locale.FRANCE,"%f",f));
    System.out.println(String.format(Locale.GERMANY,"%f",f));
    System.out.println(String.format(Locale.US,"%f",f));

prints

123456,789000
123456,789000
123456.789000

and this is what will String.format(format, args...) do in different countries.

EDIT Ok, since there has been a discussion about formalities:

    res += stripFpZeroes(String.format((Locale) null, (nDigits!=0 ? "%."+nDigits+"f" : "%f"), value));
    ...

protected static String stripFpZeroes(String fpnumber) {
    int n = fpnumber.indexOf('.');
    if (n == -1) {
        return fpnumber;
    }
    if (n < 2) {
        n = 2;
    }
    String s = fpnumber;
    while (s.length() > n && s.endsWith("0")) {
        s = s.substring(0, s.length()-1);
    }
    return s;
}

This is what I came up with:

  private static String format(final double dbl) {
    return dbl % 1 != 0 ? String.valueOf(dbl) : String.valueOf((int) dbl);
  }

It is a simple one-liner and only casts to int if it really needs to.


String.format("%.2f", value);

On my machine, the following function is roughly 7 times faster than the function provided by JasonD's answer, since it avoids String.format:

public static String prettyPrint(double d) {
  int i = (int) d;
  return d == i ? String.valueOf(i) : String.valueOf(d);
}

The best way to do this is as below:

public class Test {

    public static void main(String args[]){
        System.out.println(String.format("%s something", new Double(3.456)));
        System.out.println(String.format("%s something", new Double(3.456234523452)));
        System.out.println(String.format("%s something", new Double(3.45)));
        System.out.println(String.format("%s something", new Double(3)));
    }
}

Output:

3.456 something
3.456234523452 something
3.45 something
3.0 something

The only issue is the last one where .0 doesn't get removed. But if you are able to live with that then this works best. %.2f will round it to the last two decimal digits. So will DecimalFormat. If you need all the decimal places, but not the trailing zeros then this works best.


float price = 4.30;
DecimalFormat format = new DecimalFormat("0.##"); // Choose the number of decimal places to work with in case they are different than zero and zero value will be removed
format.setRoundingMode(RoundingMode.DOWN); // Choose your Rounding Mode
System.out.println(format.format(price));

This is the result of some tests:

4.30     => 4.3
4.39     => 4.39  // Choose format.setRoundingMode(RoundingMode.UP) to get 4.4
4.000000 => 4
4        => 4

new DecimalFormat("#.##").format(1.199); //"1.2"

As pointed in the comments, this is not the right answer to the original question.
That said, it is a very useful way to format numbers without unnecessary trailing zeros.


Here are two ways to achieve it. First, the shorter (and probably better) way:

public static String formatFloatToString(final float f)
{
  final int i = (int)f;
  if(f == i)
    return Integer.toString(i);
  return Float.toString(f);
}

And here's the longer and probably worse way:

public static String formatFloatToString(final float f)
{
  final String s = Float.toString(f);
  int dotPos = -1;
  for(int i=0; i<s.length(); ++i)
    if(s.charAt(i) == '.')
    {
      dotPos = i;
      break;
    }

  if(dotPos == -1)
    return s;

  int end = dotPos;
  for(int i = dotPos + 1; i<s.length(); ++i)
  {
    final char c = s.charAt(i);
    if(c != '0')
      end = i + 1;
  }
  final String result = s.substring(0, end);
  return result;
}

String s = "1.210000";
while (s.endsWith("0")){
    s = (s.substring(0, s.length() - 1));
}

This will make the string to drop the tailing 0-s.


For Kotlin you can use an extension like:

fun Double.toPrettyString() =
    if(this - this.toLong() == 0.0)
        String.format("%d", this.toLong())
    else
        String.format("%s", this)

I made a DoubleFormatter to efficiently convert a great numbers of double values to a nice/presentable string:

double horribleNumber = 3598945.141658554548844;
DoubleFormatter df = new DoubleFormatter(4, 6); // 4 = MaxInteger, 6 = MaxDecimal
String beautyDisplay = df.format(horribleNumber);
  • If the integer part of V has more than MaxInteger => display V in scientific format (1.2345E+30). Otherwise, display in normal format (124.45678).
  • the MaxDecimal decide numbers of decimal digits (trim with bankers' rounding)

Here the code:

import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Locale;

import com.google.common.base.Preconditions;
import com.google.common.base.Strings;

/**
 * Convert a double to a beautiful String (US-local):
 *
 * double horribleNumber = 3598945.141658554548844;
 * DoubleFormatter df = new DoubleFormatter(4,6);
 * String beautyDisplay = df.format(horribleNumber);
 * String beautyLabel = df.formatHtml(horribleNumber);
 *
 * Manipulate 3 instances of NumberFormat to efficiently format a great number of double values.
 * (avoid to create an object NumberFormat each call of format()).
 *
 * 3 instances of NumberFormat will be reused to format a value v:
 *
 * if v < EXP_DOWN, uses nfBelow
 * if EXP_DOWN <= v <= EXP_UP, uses nfNormal
 * if EXP_UP < v, uses nfAbove
 *
 * nfBelow, nfNormal and nfAbove will be generated base on the precision_ parameter.
 *
 * @author: DUONG Phu-Hiep
 */
public class DoubleFormatter
{
    private static final double EXP_DOWN = 1.e-3;
    private double EXP_UP; // always = 10^maxInteger
    private int maxInteger_;
    private int maxFraction_;
    private NumberFormat nfBelow_;
    private NumberFormat nfNormal_;
    private NumberFormat nfAbove_;

    private enum NumberFormatKind {Below, Normal, Above}

    public DoubleFormatter(int maxInteger, int maxFraction){
        setPrecision(maxInteger, maxFraction);
    }

    public void setPrecision(int maxInteger, int maxFraction){
        Preconditions.checkArgument(maxFraction>=0);
        Preconditions.checkArgument(maxInteger>0 && maxInteger<17);

        if (maxFraction == maxFraction_ && maxInteger_ == maxInteger) {
            return;
        }

        maxFraction_ = maxFraction;
        maxInteger_ = maxInteger;
        EXP_UP =  Math.pow(10, maxInteger);
        nfBelow_ = createNumberFormat(NumberFormatKind.Below);
        nfNormal_ = createNumberFormat(NumberFormatKind.Normal);
        nfAbove_ = createNumberFormat(NumberFormatKind.Above);
    }

    private NumberFormat createNumberFormat(NumberFormatKind kind) {

        // If you do not use the Guava library, replace it with createSharp(precision);
        final String sharpByPrecision = Strings.repeat("#", maxFraction_);

        NumberFormat f = NumberFormat.getInstance(Locale.US);

        // Apply bankers' rounding:  this is the rounding mode that
        // statistically minimizes cumulative error when applied
        // repeatedly over a sequence of calculations
        f.setRoundingMode(RoundingMode.HALF_EVEN);

        if (f instanceof DecimalFormat) {
            DecimalFormat df = (DecimalFormat) f;
            DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();

            // Set group separator to space instead of comma

            //dfs.setGroupingSeparator(' ');

            // Set Exponent symbol to minus 'e' instead of 'E'
            if (kind == NumberFormatKind.Above) {
                dfs.setExponentSeparator("e+"); //force to display the positive sign in the exponent part
            } else {
                dfs.setExponentSeparator("e");
            }

            df.setDecimalFormatSymbols(dfs);

            // Use exponent format if v is outside of [EXP_DOWN,EXP_UP]

            if (kind == NumberFormatKind.Normal) {
                if (maxFraction_ == 0) {
                    df.applyPattern("#,##0");
                } else {
                    df.applyPattern("#,##0."+sharpByPrecision);
                }
            } else {
                if (maxFraction_ == 0) {
                    df.applyPattern("0E0");
                } else {
                    df.applyPattern("0."+sharpByPrecision+"E0");
                }
            }
        }
        return f;
    }

    public String format(double v) {
        if (Double.isNaN(v)) {
            return "-";
        }
        if (v==0) {
            return "0";
        }
        final double absv = Math.abs(v);

        if (absv<EXP_DOWN) {
            return nfBelow_.format(v);
        }

        if (absv>EXP_UP) {
            return nfAbove_.format(v);
        }

        return nfNormal_.format(v);
    }

    /**
     * Format and higlight the important part (integer part & exponent part)
     */
    public String formatHtml(double v) {
        if (Double.isNaN(v)) {
            return "-";
        }
        return htmlize(format(v));
    }

    /**
     * This is the base alogrithm: create a instance of NumberFormat for the value, then format it. It should
     * not be used to format a great numbers of value
     *
     * We will never use this methode, it is here only to understanding the Algo principal:
     *
     * format v to string. precision_ is numbers of digits after decimal.
     * if EXP_DOWN <= abs(v) <= EXP_UP, display the normal format: 124.45678
     * otherwise display scientist format with: 1.2345e+30
     *
     * pre-condition: precision >= 1
     */
    @Deprecated
    public String formatInefficient(double v) {

        // If you do not use Guava library, replace with createSharp(precision);
        final String sharpByPrecision = Strings.repeat("#", maxFraction_);

        final double absv = Math.abs(v);

        NumberFormat f = NumberFormat.getInstance(Locale.US);

        // Apply bankers' rounding:  this is the rounding mode that
        // statistically minimizes cumulative error when applied
        // repeatedly over a sequence of calculations
        f.setRoundingMode(RoundingMode.HALF_EVEN);

        if (f instanceof DecimalFormat) {
            DecimalFormat df = (DecimalFormat) f;
            DecimalFormatSymbols dfs = df.getDecimalFormatSymbols();

            // Set group separator to space instead of comma

            dfs.setGroupingSeparator(' ');

            // Set Exponent symbol to minus 'e' instead of 'E'

            if (absv>EXP_UP) {
                dfs.setExponentSeparator("e+"); //force to display the positive sign in the exponent part
            } else {
                dfs.setExponentSeparator("e");
            }
            df.setDecimalFormatSymbols(dfs);

            //use exponent format if v is out side of [EXP_DOWN,EXP_UP]

            if (absv<EXP_DOWN || absv>EXP_UP) {
                df.applyPattern("0."+sharpByPrecision+"E0");
            } else {
                df.applyPattern("#,##0."+sharpByPrecision);
            }
        }
        return f.format(v);
    }

    /**
     * Convert "3.1416e+12" to "<b>3</b>.1416e<b>+12</b>"
     * It is a html format of a number which highlight the integer and exponent part
     */
    private static String htmlize(String s) {
        StringBuilder resu = new StringBuilder("<b>");
        int p1 = s.indexOf('.');

        if (p1>0) {
            resu.append(s.substring(0, p1));
            resu.append("</b>");
        } else {
            p1 = 0;
        }

        int p2 = s.lastIndexOf('e');
        if (p2>0) {
            resu.append(s.substring(p1, p2));
            resu.append("<b>");
            resu.append(s.substring(p2, s.length()));
            resu.append("</b>");
        } else {
            resu.append(s.substring(p1, s.length()));
            if (p1==0){
                resu.append("</b>");
            }
        }
        return resu.toString();
    }
}

Note: I used two functions from the Guava library. If you don't use Guava, code it yourself:

/**
 * Equivalent to Strings.repeat("#", n) of the Guava library:
 */
private static String createSharp(int n) {
    StringBuilder sb = new StringBuilder();
    for (int i=0; i<n; i++) {
        sb.append('#');
    }
    return sb.toString();
}

Use:

if (d % 1.0 != 0)
    return String.format("%s", d);
else
    return String.format("%.0f", d);

This should work with the extreme values supported by Double. It yields:

0.12
12
12.144252
0

Here is an answer that actually works (combination of different answers here)

public static String removeTrailingZeros(double f)
{
    if(f == (int)f) {
        return String.format("%d", (int)f);
    }
    return String.format("%f", f).replaceAll("0*$", "");
}

Naw, never mind. The performance loss due to string manipulation is zero.

And here's the code to trim the end after %f:

private static String trimTrailingZeros(String number) {
    if(!number.contains(".")) {
        return number;
    }

    return number.replaceAll("\\.?0*$", "");
}

My two cents:

if(n % 1 == 0) {
    return String.format(Locale.US, "%.0f", n));
} else {
    return String.format(Locale.US, "%.1f", n));
}

Here's another answer that has an option to append decimal ONLY IF decimal was not zero.

   /**
     * Example: (isDecimalRequired = true)
     * d = 12345
     * returns 12,345.00
     *
     * d = 12345.12345
     * returns 12,345.12
     *
     * ==================================================
     * Example: (isDecimalRequired = false)
     * d = 12345
     * returns 12,345 (notice that there's no decimal since it's zero)
     *
     * d = 12345.12345
     * returns 12,345.12
     *
     * @param d float to format
     * @param zeroCount number decimal places
     * @param isDecimalRequired true if it will put decimal even zero,
     * false will remove the last decimal(s) if zero.
     */
    fun formatDecimal(d: Float? = 0f, zeroCount: Int, isDecimalRequired: Boolean = true): String {
        val zeros = StringBuilder()

        for (i in 0 until zeroCount) {
            zeros.append("0")
        }

        var pattern = "#,##0"

        if (zeros.isNotEmpty()) {
            pattern += ".$zeros"
        }

        val numberFormat = DecimalFormat(pattern)

        var formattedNumber = if (d != null) numberFormat.format(d) else "0"

        if (!isDecimalRequired) {
            for (i in formattedNumber.length downTo formattedNumber.length - zeroCount) {
                val number = formattedNumber[i - 1]

                if (number == '0' || number == '.') {
                    formattedNumber = formattedNumber.substring(0, formattedNumber.length - 1)
                } else {
                    break
                }
            }
        }

        return formattedNumber
    }

Examples related to java

Under what circumstances can I call findViewById with an Options Menu / Action Bar item? How much should a function trust another function How to implement a simple scenario the OO way Two constructors How do I get some variable from another class in Java? this in equals method How to split a string in two and store it in a field How to do perspective fixing? String index out of range: 4 My eclipse won't open, i download the bundle pack it keeps saying error log

Examples related to string

How to split a string in two and store it in a field String method cannot be found in a main class method Kotlin - How to correctly concatenate a String Replacing a character from a certain index Remove quotes from String in Python Detect whether a Python string is a number or a letter How does String substring work in Swift How does String.Index work in Swift swift 3.0 Data to String? How to parse JSON string in Typescript

Examples related to floating-point

Convert list or numpy array of single element to float in python Convert float to string with precision & number of decimal digits specified? Float and double datatype in Java C convert floating point to int Convert String to Float in Swift How do I change data-type of pandas data frame to string with a defined format? How to check if a float value is a whole number Convert floats to ints in Pandas? Converting Float to Dollars and Cents Format / Suppress Scientific Notation from Python Pandas Aggregation Results

Examples related to format

Brackets.io: Is there a way to auto indent / format <html> Oracle SQL - DATE greater than statement What does this format means T00:00:00.000Z? How to format date in angularjs How do I change data-type of pandas data frame to string with a defined format? How to pad a string to a fixed length with spaces in Python? How to format current time using a yyyyMMddHHmmss format? java.util.Date format SSSSSS: if not microseconds what are the last 3 digits? Formatting a double to two decimal places How enable auto-format code for Intellij IDEA?

Examples related to double

Round up double to 2 decimal places Convert double to float in Java Float and double datatype in Java Rounding a double value to x number of decimal places in swift How to round a Double to the nearest Int in swift? Swift double to string How to get the Power of some Integer in Swift language? Difference between int and double How to cast the size_t to double or int C++ How to get absolute value from double - c-language