如何在 Java 中将数字四舍五入到小数点后 n 位

我想要的是一种将双精度数转换为使用 Half-up 方法取整的字符串的方法 - 即,如果要取整的小数为 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 实现此目标的最佳方法是什么?

答案

使用setRoundingMode ,显式设置RoundingMode以处理半个半个回合的问题,然后将格式模式用于所需的输出。

例:

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;

要四舍五入,请减去精度。

假设valuedouble ,则可以执行以下操作:

(double)Math.round(value * 100000d) / 100000d

这是 5 位数字的精度。零数表示小数位数。

new BigDecimal(String.valueOf(double)).setScale(yourScale, BigDecimal.ROUND_HALF_UP);

会给你一个BigDecimal 。要从中获取字符串,只需调用该BigDecimaltoString方法,或为 Java 5 + 使用toPlainString方法获取纯格式字符串。

示例程序:

package trials;
import java.math.BigDecimal;

public class Trials {

    public static void main(String[] args) {
        int yourScale = 10;
        System.out.println(BigDecimal.valueOf(0.42344534534553453453-0.42324534524553453453).setScale(yourScale, BigDecimal.ROUND_HALF_UP));
    }

您也可以使用

DecimalFormat df = new DecimalFormat("#.00000");
df.format(0.912385);

确保您具有尾随的 0。

正如其他一些人指出的那样,正确的答案是使用DecimalFormatBigDecimal 。浮点没有小数所以你不可能圆 / 截形到他们摆在首位的具体数量。您必须使用十进制基数,这就是这两个类的作用。

我将以下代码作为该线程中所有答案的反示例,甚至在整个 StackOverflow(以及其他地方)上发布,以作为建议,建议相乘然后截断再除法。该技术的倡导者有责任解释为什么以下代码在超过 92%的情况下会产生错误的输出。

public class RoundingCounterExample
{

    static float roundOff(float x, int position)
    {
        float a = x;
        double temp = Math.pow(10.0, position);
        a *= temp;
        a = Math.round(a);
        return (a / (float)temp);
    }

    public static void main(String[] args)
    {
        float a = roundOff(0.0009434f,3);
        System.out.println("a="+a+" (a % .001)="+(a % 0.001));
        int count = 0, errors = 0;
        for (double x = 0.0; x < 1; x += 0.0001)
        {
            count++;
            double d = x;
            int scale = 2;
            double factor = Math.pow(10, scale);
            d = Math.round(d * factor) / factor;
            if ((d % 0.01) != 0.0)
            {
                System.out.println(d + " " + (d % 0.01));
                errors++;
            }
        }
        System.out.println(count + " trials " + errors + " errors");
    }
}

该程序的输出:

10001 trials 9251 errors

编辑:为了解决下面的一些评论,我使用BigDecimalnew MathContext(16)测试循环的模数部分,如下所示:

public static void main(String[] args)
{
    int count = 0, errors = 0;
    int scale = 2;
    double factor = Math.pow(10, scale);
    MathContext mc = new MathContext(16, RoundingMode.DOWN);
    for (double x = 0.0; x < 1; x += 0.0001)
    {
        count++;
        double d = x;
        d = Math.round(d * factor) / factor;
        BigDecimal bd = new BigDecimal(d, mc);
        bd = bd.remainder(new BigDecimal("0.01"), mc);
        if (bd.multiply(BigDecimal.valueOf(100)).remainder(BigDecimal.ONE, mc).compareTo(BigDecimal.ZERO) != 0)
        {
            System.out.println(d + " " + bd);
            errors++;
        }
    }
    System.out.println(count + " trials " + errors + " errors");
}

结果:

10001 trials 4401 errors

假设你有

double d = 9232.129394d;

您可以使用BigDecimal

BigDecimal bd = new BigDecimal(d).setScale(2, RoundingMode.HALF_EVEN);
d = bd.doubleValue();

是否有 BigDecimal

d = Math.round(d*100)/100.0d;

两种解决方案d == 9232.13

您可以使用 DecimalFormat 类。

double d = 3.76628729;

DecimalFormat newFormat = new DecimalFormat("#.##");
double twoDecimal =  Double.valueOf(newFormat.format(d));

Real 的 Java How-to 发布了此解决方案,该解决方案也与 Java 1.6 之前的版本兼容。

BigDecimal bd = new BigDecimal(Double.toString(d));
bd = bd.setScale(decimalPlace, BigDecimal.ROUND_HALF_UP);
return bd.doubleValue();
double myNum = .912385;
int precision = 10000; //keep 4 digits
myNum= Math.floor(myNum * precision +.5)/precision;

@Milhous:十进制的十进制格式非常好:

您也可以使用

DecimalFormat df = new DecimalFormat("#.00000");
df.format(0.912385);

确保您具有尾随的 0。

我要补充一点,这种方法非常擅长提供实际的数字舍入机制 - 不仅在视觉上,而且在处理时。

假设:您必须在 GUI 程序中实现舍入机制。要更改结果输出的准确性 / 精度,只需更改插入符号的格式(即括号内)。以便:

DecimalFormat df = new DecimalFormat("#0.######");
df.format(0.912385);

将作为输出返回: 0.912385

DecimalFormat df = new DecimalFormat("#0.#####");
df.format(0.912385);

将作为输出返回: 0.91239

DecimalFormat df = new DecimalFormat("#0.####");
df.format(0.912385);

将作为输出返回: 0.9124

[编辑:如果插入符号的格式也是如此(“#0。#############”),并且您输入了一个小数,例如 3.1415926,出于参数的考虑,DecimalFormat 不会产生任何垃圾(例如尾随零),然后返回: 3.1415926 .. 如果您这样倾斜。当然,对于某些开发人员而言,它有点冗长 - 但是,嘿,它在处理过程中的内存占用很少,并且易于实现。]

因此,从本质上讲,DecimalFormat 的优点在于,它可以同时处理字符串外观 - 以及舍入精度设置的级别。因此:以一个代码实现的价格,您将获得两个好处。 ;)