关于java:如何很好地将浮动数字格式化为字符串而没有不必要的小数0?

How to nicely format floating numbers to String without unnecessary decimal 0?

64位双精度数可以精确地表示整数+/-253

考虑到这个事实,我选择使用double类型作为所有类型的单个类型,因为我最大的整数是无符号32位整数。

但现在我必须打印这些伪整数,但问题是它们也与实际的双精度数混合在一起。

那么,如何在Java中更好地打印这些双打呢?

我试过String.format("%f", value),它很接近,只是小值有很多尾随的零。

下面是%f的一个输出示例

1
2
3
4
5
6
232.00000000
0.18000000000
1237875192.0
4.5800000000
0.00000000
1.23450000

我想要的是:

1
2
3
4
5
6
232
0.18
1237875192
4.58
0
1.2345

当然,我可以编写一个函数来修剪这些零,但这会导致大量的性能损失,因为字符串操作。我能用另一种格式代码做得更好吗?

编辑

汤姆E.和杰里米S.的答案是不可接受的,因为它们都任意四舍五入到小数点后两位。回答前请先了解问题。

编辑2

请注意,String.format(format, args...)取决于地区(见下面的答案)。


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

正如评论中指出的,这不是对原始问题的正确回答。也就是说,它是一种非常有用的方法来格式化数字,而不需要不必要的尾随零。


如果要打印存储为双精度的整数,就好像它们是整数一样,否则以最小必要精度打印双精度:

1
2
3
4
5
6
7
public static String fmt(double d)
{
    if(d == (long) d)
        return String.format("%d",(long)d);
    else
        return String.format("%s",d);
}

生产:

1
2
3
4
5
6
232
0.18
1237875192
4.58
0
1.2345

而且不依赖于字符串操作。


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


简而言之:

如果要消除尾随零和区域设置问题,则应使用:

1
2
3
4
5
6
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

说明:

为什么其他答案不适合我:

  • 如果double小于10^3或大于或等于10^7,则Double.toString()System.out.printlnFloatingDecimal.toJavaFormatString使用科学符号。

    1
    2
    double myValue = 0.00000021d;
    String.format("%s", myvalue); //output: 2.1E-7
  • 通过使用%f,默认的十进制精度是6,否则您可以对其进行硬编码,但如果您有较少的小数,则会导致额外的零添加。例子:

    1
    2
    double myValue = 0.00000021d;
    String.format("%.12f", myvalue); //output: 0.000000210000
  • 通过使用setMaximumFractionDigits(0);%.0f,您可以删除任何十进制精度,对于整数/长整型是可以的,但不适用于双精度型。

    1
    2
    3
    4
    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
  • 通过使用decimalformat,您是本地依赖的。在法语区域设置中,小数分隔符是逗号,而不是点:

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

    使用英语区域设置可以确保在程序运行的任何位置都获得小数点分隔符。

为什么要把340用于setMaximumFractionDigits

原因有二:

  • setMaximumFractionDigits接受整数,但它的实现允许的最大位数为DecimalFormat.DOUBLE_FRACTION_DIGITS,等于340。
  • Double.MIN_VALUE = 4.9E-324,所以340位数字,你肯定不会四舍五入你的双精度和松散精度。


为什么不:

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

这应该适用于double支持的极端值。产量:

1
2
3
4
0.12
12
12.144252
0


我的2分钱:

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


在我的机器上,以下函数大约比Jasond的答案提供的函数快7倍,因为它避免了String.format

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


不,没关系。

字符串操作导致的性能损失为零。

这是在%f之后修剪末端的代码

1
2
3
4
5
6
7
private static String trimTrailingZeros(String number) {
    if(!number.contains(".")) {
        return number;
    }

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


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


请注意,String.format(format, args...)依赖于区域设置,因为它使用用户的默认区域设置进行格式化,也就是说,可能包含逗号甚至空格,如123 456789或123456.789,这可能不是您所期望的。

您可能更喜欢使用String.format((Locale)null, format, args...)

例如,

1
2
3
4
    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));

印刷品

1
2
3
123456,789000
123456,789000
123456.789000

这就是String.format(format, args...)在不同国家所做的。

编辑好,因为已经讨论过手续:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    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;
}


我制作了一个DoubleFormatter来有效地将大量的双值转换为一个漂亮的/可显示的字符串:

1
2
3
double horribleNumber = 3598945.141658554548844;
DoubleFormatter df = new DoubleFormatter(4,6); //4 = MaxInteger, 6 = MaxDecimal
String beautyDisplay = df.format(horribleNumber);
  • 如果v的整数部分大于maxinteger=>以科学家格式显示v(1.2345e+30)否则以正常格式124.45678显示。
  • 最大小数点决定小数位数(随银行四舍五入调整)

这里的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
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) {
        final String sharpByPrecision = Strings.repeat("#", maxFraction_); //if you do not use Guava library, replace with createSharp(precision);
        NumberFormat f = NumberFormat.getInstance(Locale.US);

        //Apply banker's 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 out side 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) {

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

        final double absv = Math.abs(v);

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

        //Apply banker's 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"3.1416e+12"
     * 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("");
        int p1 = s.indexOf('.');

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

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

注意:我使用了两个来自guava库的函数。如果您不使用番石榴,请自己编写代码:

1
2
3
4
5
6
7
8
9
10
/**
 * 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();
}


这一个会很好地完成工作,我知道这个话题已经过时了,但我一直在同一个问题上挣扎,直到我谈到这个问题。我希望有人发现它有用。

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

1
2
3
4
5
new DecimalFormat("00.#").format(20.236)
//out =20.2

new DecimalFormat("00.#").format(2.236)
//out =02.2
  • 最小位数为0
  • 呈现数字

  • 1
    2
    3
    4
    5
    6
    7
    8
    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);
        }
    }


    使用DecimalFormatsetMinimumFractionDigits(0)


    回答迟了,但是…

    你说你选择用双精度类型来存储你的号码。我认为这可能是问题的根源,因为它迫使您将整数存储到double中(因此会丢失有关值性质的初始信息)。将数字存储在number类(double和integer的超类)的实例中,并依赖多态性来确定每个数字的正确格式如何?

    我知道重构代码的一部分可能是不可接受的,因为这样做了,但是它可以在没有额外代码/强制转换/解析的情况下生成所需的输出。

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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);
            }
        }

    }

    将产生以下输出:

    1
    2
    3
    4
    5
    6
    232
    0.18
    1237875192
    4.58
    0
    1.2345


    这就是我想到的:

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

    简单的一行程序,如果真的需要,只将其强制转换为int


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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);
    }

    我不得不使用这个原因,因为d == (long)d在声纳报告中给了我违反规定的行为。


    实现这一目标有两种方法。首先,更短(可能更好)的方法:

    1
    2
    3
    4
    5
    6
    7
    public static String formatFloatToString(final float f)
      {
      final int i=(int)f;
      if(f==i)
        return Integer.toString(i);
      return Float.toString(f);
      }

    这是一个较长甚至更糟的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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;
      }


    下面是一个实际有效的答案(这里是不同答案的组合)

    1
    2
    3
    4
    5
    6
    7
    public static String removeTrailingZeros(double f)
    {
        if(f == (int)f) {
            return String.format("%d", (int)f);
        }
        return String.format("%f", f).replaceAll("0*$","");
    }


    我知道这是一条很古老的线。但我认为最好的方法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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)));
        }
    }

    输出:

    1
    2
    3
    4
    3.456 something
    3.456234523452 something
    3.45 something
    3.0 something

    唯一的问题是最后一个不能删除.0的问题。但如果你能忍受,那么这是最好的办法。%.2f将把它四舍五入到最后两位十进制数字。decimalformat也是如此。如果您需要所有的小数位,但不需要尾随的零,那么这是最好的方法。


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

    这将使字符串落下尾随0-s。