如何优雅地将浮点数格式化为字符串,避免不必要的小数0

612

一个64位的双精度浮点数可以确切地表示整数+/-253

基于这个事实,我选择使用双精度浮点类型作为我的所有类型的单一类型,因为我的最大整数是一个无符号32位数字。

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

那么我如何在Java中漂亮地打印这些双精度浮点数呢?

我尝试过String.format("%f", value),接近了,但对于小值,我得到了很多尾随零。

这里有一个%f的输出示例:

232.00000000
0.18000000000
1237875192.0
4.5800000000
0.00000000
1.23450000

我想要的是:

232
0.18
1237875192
4.58
0
1.2345

当然,我可以编写一个函数来裁剪那些零,但由于字符串操作而导致的性能损失太大了。我能用其他格式代码更好地做到吗?


Tom E.和Jeremy S.的答案都不可接受,因为它们都会将值任意舍入到两个小数位。请在回答前仔细理解问题。


请注意,String.format(format, args...)是与语言环境有关的(请参见下面的答案)。


如果你只需要整数,为什么不使用long呢?它能够达到2^63-1,没有尴尬的格式,性能更好。 - basszero
18
因为有些数值实际上是双精度浮点数。 - Pyrolistical
2
这个问题出现的一些情况是在JDK 7中修复的一个错误:https://dev59.com/BGsz5IYBdhLWcg3w8Mcb - Pyrolistical
System.out.println("YOUR STRING" + YOUR_DOUBLE_VARIABLE); - Shayan Amani
这是正确的答案 - Ibrahim Disouki
29个回答

453

如果想要将存储为双精度浮点数的整数打印成整数形式,否则只需以最小必要精度打印双精度浮点数:

public static String fmt(double d)
{
    if(d == (long) d)
        return String.format("%d",(long)d);
    else
        return String.format("%s",d);
}

生成:

232
0.18
1237875192
4.58
0
1.2345

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


13
同意,这是一个不好的答案,不要使用它。它无法处理比最大int值更大的double。即使使用long也会在处理超大数字时失败。此外,对于大数值,它将返回指数形式的字符串,例如"1.0E10",这可能不是提问者想要的结果。在第二个格式化字符串中,使用%f代替%s可以解决这个问题。 - jlh
32
原文:The OP stated explicitly that they did not want the output formatted using %f. The answer is specific to the situation described, and the desired output. The OP suggested their maximum value was a 32-bit unsigned int, which I took to mean that int was acceptable (unsigned not actually existing in Java, and no exemplar was problematic), but changing int to long is a trivial fix if the situation is different.翻译:提问者明确表示他们不希望使用 %f 格式输出结果。回答是根据所描述的情况和期望的输出而具体的。提问者建议他们的最大值是一个 32 位无符号整数,我理解为 int 是可以接受的(Java 中实际上不存在无符号整数,也没有示例出错),但如果情况不同,将 int 改为 long 是一个微不足道的修复。 - JasonD
1
这个问题的哪里说不应该那么做了? - JasonD
11
String.format("%s",d)这样做有些多余了。可以使用Double.toString(d)来代替。同样的,对于Long.toString((long)d)也是一样的。 - Andreas
22
问题在于 %s 无法与本地化环境(Locale)一起使用。例如,在德语中,小数点使用 "," 而不是 "." 。尽管 String.format(Locale.GERMAN, "%f", 1.5) 返回 "1,500000",但 String.format(Locale.GERMAN, "%s", 1.5) 返回 "1.5",这是错误的,因为在德语中应该使用 ","。是否存在一个基于本地化环境的 "%s" 版本呢? - Felix Edelmann
显示剩余5条评论

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

21
是的,但它总是打印尾随零,即使没有小数部分。 String.format(“%.2f,1.0005”)会打印1.00而不是1。是否有任何格式说明符可以不打印不存在的小数部分? - Emre Yazici
137
因为该问题要求去掉所有尾随的零,而这个答案无论是否为零都会保留两个浮点数,所以被下投票了。 - Zulaxia
3
使用"g"而不是"f",我认为你可以正确处理尾随零。 - Peter Ajtai
4
我曾在一个生产系统中使用“%.5f”的解决方案,结果很糟糕,不建议使用... 因为它输出了这样的内容:5.12E-4,而不是0.000512。 - hamish
4
我真的不明白为什么这个答案会被投票赞成 :( 它与问题无关。 - aligur
显示剩余2条评论

139

简而言之:

如果您想要去掉尾随的零以及避免与区域设置有关的问题,那么您应该使用:

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.toString()System.out.printlnFloatingDecimal.toJavaFormatString 如果 double 小于 10^-3 或大于等于 10^7,将使用科学计数法。

 double myValue = 0.00000021d;
 String.format("%s", myvalue); //output: 2.1E-7
  • 使用 %f,默认的小数精度为6位。否则你可以硬编码它,但如果你有更少的小数位,会导致额外的零被添加。例如:

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

  •  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,你的结果会受本地环境影响。在法语环境下,小数点是逗号而不是点:

  •  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个数字可以确保不会舍入您的double并失去精度。

    这对整数无效,例如“2”变成“2.” - kap
    谢谢,我已经通过使用模式0而不是#.来修复答案。 - JBE
    你没有使用常量DecimalFormat.DOUBLE_FRACTION_DIGITS,而是使用了值340,并提供了一个注释来说明它等于DecimalFormat.DOUBLE_FRACTION_DIGITS。为什么不直接使用常量呢??? - Maarten Bodewes
    3
    因为这个属性不是公共的,所以它是"包友好的"。 - JBE
    5
    谢谢!实际上,这个答案是唯一真正符合问题中提到的所有要求的答案——它不显示不必要的零,不四舍五入数字,并且具有地区依赖性。很棒! - Felix Edelmann
    显示剩余2条评论

    49

    使用:

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

    这应该可以处理 Double 支持的极端值。它产生的结果是:

    0.12
    12
    12.144252
    0
    

    5
    我更喜欢这个答案,因为我们不需要进行类型转换。 - Jeff T.
    简短解释:"%s"基本上调用了d.toString(),但是它不能与int一起使用,或者如果d==null - Neph
    非常喜欢这个。 - Eugene

    33

    在我的机器上,以下函数比JasonD's answer提供的函数快大约7倍,因为它避免使用String.format

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

    2
    嗯,这个没有考虑到本地化,但 JasonD 的也是一样。 - TWiStErRob

    25

    我个人的看法:

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

    3
    或者只需返回以下格式化字符串:String.format(Locale.US, (n % 1 == 0 ? "%.0f" : "%.1f"), n);。这将使代码返回一个整数,如果输入的数字是整数,否则返回一个保留一位小数的浮点数。 - MC Emperor
    2
    失败的例子:23.00123 ==> 23.00 - aswzen
    2
    你在做什么?它总是四舍五入到小数点后一位,这不是问题的答案。为什么有些人无法阅读? - user924
    1
    你的错误答案没有返回 232 0.18 1237875192 4.58 0 1.2345 - user924
    它真的能工作吗?'n'是什么?一种浮点数还是整数? - Peter Mortensen

    21

    需要解释一下。 - Peter Mortensen
    很好的答案,它不需要解释,因为它可以自己完成。 - Fabrizio Valencia
    希望这篇解释能够值得至少再获得两个赞;-) - fop6316

    20
    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));
    

    这是一些测试的结果:

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

    1.23450000怎么样? - Alex78191
    1.23450000 => 1.23 - Ahmed Mihoub
    2
    唯一让我满意的解决方案 - BekaBot
    1
    DecimalFormat 不是线程安全的。在使用它时必须小心。 - yaboong
    1
    终于成功了。谢谢。 - Angad Cheema

    17

    算了,不用管了。字符串操作导致的性能损失为零。

    这里是截取 %f 后面部分的代码:

    private static String trimTrailingZeros(String number) {
        if(!number.contains(".")) {
            return number;
        }
    
        return number.replaceAll("\\.?0*$", "");
    }
    

    7
    我给你的解决方案不是最佳方式,所以我进行了投票。请查看String.format,您需要使用正确的格式类型,这种情况下应该使用float型。请参考我之前的回答。 - jjnguy
    9
    我投了赞成票,因为我遇到了同样的问题,而且这里似乎没有人理解这个问题。 - Obay
    1
    由于Tom帖子中提到的DecimalFormat正是您要寻找的内容,因此您的帖子已被点踩。它可以有效地去除零。 - Steve Pomeroy
    5
    也许他想去掉末尾的零,而不进行四舍五入?附注:@Pyrolistical,你可以使用number.replaceAll(".?0*$", "");(当然,在包含“.”之后) - Rehno Lindeque
    1
    赞同你对问题的深刻理解。但正则表达式应该是“\.?0*$”。否则,对于34.23298424000,它将返回34.2329842而不是34.23298424。 - autra
    显示剩余6条评论

    11

    使用DecimalFormat并调用setMinimumFractionDigits(0)方法。


    我会添加 setMaximumFractionDigits(2)setGroupingUsed(false)(OP没有提到,但从例子中看来这是必需的)。另外,一个小的测试用例也不会有什么坏处,因为在这种情况下它很简单。尽管如此,由于我认为这是最简单的解决方案,所以赞成票就是赞成票 :) - acrespo

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接