首页 文章

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

提问于
浏览
425

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

鉴于这一事实,我选择将double类型用作所有类型的单一类型,因为我的最大整数是无符号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

当然,我可以编写一个函数来修剪这些零,但由于字符串操作,这会导致很多性能损失 . 我可以用其他格式代码做得更好吗?

EDIT

Tom E.和Jeremy S.的答案是不可接受的,因为它们都可以任意舍入到小数点后两位 . 请在回答之前先了解问题 .

EDIT 2

请注意 String.format(format, args...)locale-dependent (见下面的答案) .

22 回答

  • 5
    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);
        }
    }
    
  • 382

    使用 DecimalFormatsetMinimumFractionDigits(0)

  • 5

    在我的机器上,以下函数大约比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);
    }
    
  • -1

    这个将很好地完成工作,我知道这个话题很老,但在我遇到这个问题之前我一直在努力解决同样的问题 . 我希望有人觉得它很有用 .

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

    我的2美分:

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

    以下是实现它的两种方法 . 首先,更短(可能更好)的方式:

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

    这是更长的,可能更糟的方式:

    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;
      }
    
  • -8

    In short:

    如果你想摆脱尾随零和Locale问题,那么你应该使用:

    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:

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

    如果double小于10 ^ -3或大于或等于10 ^ 7,则

    • Double.toString()System.out.printlnFloatingDecimal.toJavaFormatString 使用科学记数法
    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
    

    使用ENGLISH语言环境可确保在程序运行的任何位置获得小数点分隔符

    为什么使用340然后 setMaximumFractionDigits

    两个原因:

    • setMaximumFractionDigits 接受一个整数,但其实现的最大允许位数为 DecimalFormat.DOUBLE_FRACTION_DIGITS ,等于340

    • Double.MIN_VALUE = 4.9E-324 所以有340位数字,你肯定不会绕过你的双精度和松散精度

  • 4

    Naw,没关系 .

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

    这是在 %f 之后修剪结束的代码

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

    迟到但是......

    你说你 choose 用双重类型存储你的号码 . 我认为这可能是问题的根源,因为它会强制您将整数存储为双精度(因此会丢失有关值的性质的初始信息) . 如何将数字存储在Number类(Double和Integer的超类)的实例中,并依靠多态来确定每个数字的正确格式?

    我知道由于这个原因重构代码的整个部分可能是不可接受的,但它可以产生所需的输出而无需额外的代码/转换/解析 .

    例:

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

    将产生以下输出:

    232
    0.18
    1237875192
    4.58
    0
    1.2345
    
  • 75

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

    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
    

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

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

    请注意 String.format(format, args...)locale-dependent ,因为它使用用户的默认语言环境进行格式化,也就是说,可能使用逗号甚至内部空格如123 456,789或123,456.789,这可能与您的预期完全不同 .

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

    例如,

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

    版画

    123456,789000
    123456,789000
    123456.789000
    

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

    编辑好了,因为有关于手续的讨论:

    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;
    }
    
  • 221
    new DecimalFormat("00.#").format(20.236)
    //out =20.2
    
    new DecimalFormat("00.#").format(2.236)
    //out =02.2
    
    • 0表示最小位数

    • 渲染#位数

  • 346

    我做了一个 DoubleFormatter 来有效地将大量的double值转换为一个漂亮/可呈现的String:

    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显示 .

    • MaxDecimal决定小数位数(与银行家的舍入修剪)

    这里的代码:

    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 "<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();
        }
    }
    

    注意:我使用了GUAVA库中的2个函数 . 如果您不使用GUAVA,请自行编码:

    /**
     * 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();
    }
    
  • 11
    if (d == Math.floor(d)) {
        return String.format("%.0f", d);
    } else {
        return Double.toString(d);
    }
    
  • 3

    我知道这是一个非常古老的线程..但我认为最好的方法如下:

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

    输出:

    3.456 something
    3.456234523452 something
    3.45 something
    3.0 something
    

    唯一的问题是最后一个.0没有被删除 . 但是,如果你能够忍受这种情况,那么这种方法效果最好 . %.2f会将其四舍五入到最后2位小数 . DecimalFormat也是如此 . 如果您需要所有小数位但不需要尾随零,则此方法效果最佳 .

  • 2
    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 在声纳报告中给了我违规

  • 4

    这就是我提出的:

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

    简单的一个班轮,只有演员如果真的需要到int

  • 20

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

    public static String removeTrailingZeros(double f)
    {
        if(f == (int)f) {
            return String.format("%d", (int)f);
        }
        return String.format("%f", f).replaceAll("0*$", "");
    }
    
  • -2
    String s = "1.210000";
    while (s.endsWith("0")){
        s = (s.substring(0, s.length() - 1));
    }
    

    这将使字符串丢弃拖尾0-s .

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

    正如评论中所指出的,这不是原始问题的正确答案 .
    也就是说,这是一种非常有用的格式化数字的方法,没有不必要的尾随零 .

  • 0

    为什么不:

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

    这应该与Double支持的极值一起使用 . 产量:

    0.12
    12
    12.144252
    0
    

相关问题