我想要的是一种将双精度转换为字符串的方法,该字符串使用半向上舍入方法进行舍入-即,如果要舍入的小数为5,则始终向上舍入到下一个数字。这是大多数人在大多数情况下所期望的四舍五入的标准方法。
我还希望只显示有效数字,即不应有任何尾随零。
我知道这样做的一种方法是使用String.format方法:
String.format("%.5g%n", 0.912385);
返回:
0.91239
这是很好的,但是它总是显示带有5位小数的数字,即使它们不重要:
String.format("%.5g%n", 0.912300);
返回:
0.91230
另一种方法是使用DecimalFormatter:
DecimalFormat df = new DecimalFormat("#.#####");
df.format(0.912385);
返回:
0.91238
然而,正如您所见,这使用了半偶数舍入。也就是说,如果前一个数字是偶数,它将向下舍入。我想要的是:
0.912385 -> 0.91239
0.912300 -> 0.9123
在Java中实现这一点的最佳方法是什么?
当尝试舍入到小数位数的负数时,Math.round解决方案存在问题。考虑代码
long l = 10;
for(int dp = -1; dp > -10; --dp) {
double mul = Math.pow(10,dp);
double res = Math.round(l * mul) / mul;
System.out.println(""+l+" rounded to "+dp+" dp = "+res);
l *=10;
}
这是结果
10 rounded to -1 dp = 10.0
100 rounded to -2 dp = 100.0
1000 rounded to -3 dp = 1000.0
10000 rounded to -4 dp = 10000.0
100000 rounded to -5 dp = 99999.99999999999
1000000 rounded to -6 dp = 1000000.0
10000000 rounded to -7 dp = 1.0E7
100000000 rounded to -8 dp = 1.0E8
1000000000 rounded to -9 dp = 9.999999999999999E8
当1除以1.0E-5时会出现小数位数为-5的问题,这是不精确的。
可以使用
double mul = Math.pow(10,dp);
double res;
if(dp < 0 ) {
double div = Math.pow(10,-dp);
res = Math.round(l * mul) *div;
} else {
res = Math.round(l * mul) / mul;
}
但这是使用BigDecimal方法的另一个原因。
由于我找不到关于这个主题的完整答案,我已经组建了一个类,该类应该正确处理这个问题,并支持:
格式化:轻松将双精度字符串格式化为具有一定小数位数的字符串解析:将格式化值解析回double区域设置:使用默认区域设置格式化和解析指数表示法:在某个阈值之后开始使用指数表示法
用法很简单:
(对于本示例,我使用的是自定义区域设置)
public static final int DECIMAL_PLACES = 2;
NumberFormatter formatter = new NumberFormatter(DECIMAL_PLACES);
String value = formatter.format(9.319); // "9,32"
String value2 = formatter.format(0.0000005); // "5,00E-7"
String value3 = formatter.format(1324134123); // "1,32E9"
double parsedValue1 = formatter.parse("0,4E-2", 0); // 0.004
double parsedValue2 = formatter.parse("0,002", 0); // 0.002
double parsedValue3 = formatter.parse("3423,12345", 0); // 3423.12345
这是课程:
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.ParseException;
import java.util.Locale;
public class NumberFormatter {
private static final String SYMBOL_INFINITE = "\u221e";
private static final char SYMBOL_MINUS = '-';
private static final char SYMBOL_ZERO = '0';
private static final int DECIMAL_LEADING_GROUPS = 10;
private static final int EXPONENTIAL_INT_THRESHOLD = 1000000000; // After this value switch to exponential notation
private static final double EXPONENTIAL_DEC_THRESHOLD = 0.0001; // Below this value switch to exponential notation
private DecimalFormat decimalFormat;
private DecimalFormat decimalFormatLong;
private DecimalFormat exponentialFormat;
private char groupSeparator;
public NumberFormatter(int decimalPlaces) {
configureDecimalPlaces(decimalPlaces);
}
public void configureDecimalPlaces(int decimalPlaces) {
if (decimalPlaces <= 0) {
throw new IllegalArgumentException("Invalid decimal places");
}
DecimalFormatSymbols separators = new DecimalFormatSymbols(Locale.getDefault());
separators.setMinusSign(SYMBOL_MINUS);
separators.setZeroDigit(SYMBOL_ZERO);
groupSeparator = separators.getGroupingSeparator();
StringBuilder decimal = new StringBuilder();
StringBuilder exponential = new StringBuilder("0.");
for (int i = 0; i < DECIMAL_LEADING_GROUPS; i++) {
decimal.append("###").append(i == DECIMAL_LEADING_GROUPS - 1 ? "." : ",");
}
for (int i = 0; i < decimalPlaces; i++) {
decimal.append("#");
exponential.append("0");
}
exponential.append("E0");
decimalFormat = new DecimalFormat(decimal.toString(), separators);
decimalFormatLong = new DecimalFormat(decimal.append("####").toString(), separators);
exponentialFormat = new DecimalFormat(exponential.toString(), separators);
decimalFormat.setRoundingMode(RoundingMode.HALF_UP);
decimalFormatLong.setRoundingMode(RoundingMode.HALF_UP);
exponentialFormat.setRoundingMode(RoundingMode.HALF_UP);
}
public String format(double value) {
String result;
if (Double.isNaN(value)) {
result = "";
} else if (Double.isInfinite(value)) {
result = String.valueOf(SYMBOL_INFINITE);
} else {
double absValue = Math.abs(value);
if (absValue >= 1) {
if (absValue >= EXPONENTIAL_INT_THRESHOLD) {
value = Math.floor(value);
result = exponentialFormat.format(value);
} else {
result = decimalFormat.format(value);
}
} else if (absValue < 1 && absValue > 0) {
if (absValue >= EXPONENTIAL_DEC_THRESHOLD) {
result = decimalFormat.format(value);
if (result.equalsIgnoreCase("0")) {
result = decimalFormatLong.format(value);
}
} else {
result = exponentialFormat.format(value);
}
} else {
result = "0";
}
}
return result;
}
public String formatWithoutGroupSeparators(double value) {
return removeGroupSeparators(format(value));
}
public double parse(String value, double defValue) {
try {
return decimalFormat.parse(value).doubleValue();
} catch (ParseException e) {
e.printStackTrace();
}
return defValue;
}
private String removeGroupSeparators(String number) {
return number.replace(String.valueOf(groupSeparator), "");
}
}
使用setRoundingMode,显式设置Rounding模式以处理半偶数轮的问题,然后使用所需输出的格式模式。
例子:
DecimalFormat df = new DecimalFormat("#.####");
df.setRoundingMode(RoundingMode.CEILING);
for (Number n : Arrays.asList(12, 123.12345, 0.23, 0.1, 2341234.212431324)) {
Double d = n.doubleValue();
System.out.println(df.format(d));
}
给出输出:
12
123.1235
0.23
0.1
2341234.2125
编辑:最初的答案没有提到双精度值的准确性。如果你不太在乎它是向上还是向下,那就好了。但如果您想要精确舍入,则需要考虑值的预期精度。浮点值在内部具有二进制表示。这意味着像2.7735这样的值实际上在内部没有精确的值。它可以稍大或稍小。如果内部值稍小,则不会舍入到2.7740。要纠正这种情况,您需要了解正在处理的值的准确性,并在舍入之前添加或减去该值。例如,当您知道您的值精确到6位数时,若要向上舍入中间值,请将该精度添加到值中:
Double d = n.doubleValue() + 1e-6;
要向下舍入,请减去精度。