How to nicely format floating numbers to String without unnecessary decimal 0?
64位双精度数可以精确地表示整数+/-253
考虑到这个事实,我选择使用double类型作为所有类型的单个类型,因为我最大的整数是无符号32位整数。
但现在我必须打印这些伪整数,但问题是它们也与实际的双精度数混合在一起。
那么,如何在Java中更好地打印这些双打呢?
我试过
下面是
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
请注意,
1 |
正如评论中指出的,这不是对原始问题的正确回答。也就是说,它是一种非常有用的方法来格式化数字,而不需要不必要的尾随零。
如果要打印存储为双精度的整数,就好像它们是整数一样,否则以最小必要精度打印双精度:
1 2 3 4 5 6 7 |
生产:
1 2 3 4 5 6 | 232 0.18 1237875192 4.58 0 1.2345 |
而且不依赖于字符串操作。
1 |
简而言之:
如果要消除尾随零和区域设置问题,则应使用:
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.println 或FloatingDecimal.toJavaFormatString 使用科学符号。1
2通过使用
%f ,默认的十进制精度是6,否则您可以对其进行硬编码,但如果您有较少的小数,则会导致额外的零添加。例子:1
2通过使用
setMaximumFractionDigits(0); 或%.0f ,您可以删除任何十进制精度,对于整数/长整型是可以的,但不适用于双精度型。1
2
3
4double 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
4double myValue = 0.00000021d;
DecimalFormat df = new DecimalFormat("0");
df.setMaximumFractionDigits(340);
System.out.println(df.format(myvalue));//output: 0,00000021使用英语区域设置可以确保在程序运行的任何位置都获得小数点分隔符。
为什么要把340用于
原因有二:
setMaximumFractionDigits 接受整数,但它的实现允许的最大位数为DecimalFormat.DOUBLE_FRACTION_DIGITS ,等于340。Double.MIN_VALUE = 4.9E-324 ,所以340位数字,你肯定不会四舍五入你的双精度和松散精度。
为什么不:
这应该适用于double支持的极端值。产量:
1 2 3 4 | 0.12 12 12.144252 0 |
我的2分钱:
1 2 3 4 5 |
在我的机器上,以下函数大约比Jasond的答案提供的函数快7倍,因为它避免了
1 2 3 4 |
不,没关系。
字符串操作导致的性能损失为零。
这是在
1 2 3 4 5 6 7 |
1 2 3 4 5 |
请注意,
您可能更喜欢使用
例如,
1 2 3 4 |
印刷品
1 2 3 | 123456,789000 123456,789000 123456.789000 |
这就是
编辑好,因为已经讨论过手续:
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; } |
我制作了一个
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 |
1 2 3 4 5 6 7 8 |
使用
回答迟了,但是…
你说你选择用双精度类型来存储你的号码。我认为这可能是问题的根源,因为它迫使您将整数存储到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 |
简单的一行程序,如果真的需要,只将其强制转换为int
1 2 3 4 5 6 7 8 9 10 11 12 |
我不得不使用这个原因,因为
实现这一目标有两种方法。首先,更短(可能更好)的方法:
1 2 3 4 5 6 7 |
这是一个较长甚至更糟的方法:
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 |
我知道这是一条很古老的线。但我认为最好的方法如下:
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 |
这将使字符串落下尾随0-s。