我想要的是一种将双精度转换为字符串的方法,该字符串使用半向上舍入方法进行舍入-即,如果要舍入的小数为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中实现这一点的最佳方法是什么?


当前回答

您可以使用以下实用程序方法-

public static double round(double valueToRound, int numberOfDecimalPlaces)
{
    double multipicationFactor = Math.pow(10, numberOfDecimalPlaces);
    double interestedInZeroDPs = valueToRound * multipicationFactor;
    return Math.round(interestedInZeroDPs) / multipicationFactor;
}

其他回答

您可以使用DecimalFormat类。

double d = 3.76628729;

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

正如其他人所指出的,正确的答案是使用DecimalFormat或BigDecimal。浮点没有小数位数,因此您不可能在第一位舍入/截断为小数位数。你必须使用十进制基数,这就是这两个类的作用。

我发布以下代码作为本线程中所有答案的反例,实际上在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

编辑:为了解决下面的一些问题,我使用BigDecimal和新的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

我同意使用DecimalFormat或BigDecimal的答案。

请先阅读下面的更新!

但是,如果您确实希望舍入双值并获得双值结果,则可以使用上述org.apache.commons.math3.util.Precision.round(..)。该实现使用BigDecimal,速度慢,并且会产生垃圾。

decimal4j库中的DoubleRounder实用程序提供了一种类似但快速且无垃圾的方法:

 double a = DoubleRounder.round(2.0/3.0, 3);
 double b = DoubleRounder.round(2.0/3.0, 3, RoundingMode.DOWN);
 double c = DoubleRounder.round(1000.0d, 17);
 double d = DoubleRounder.round(90080070060.1d, 9);
 System.out.println(a);
 System.out.println(b);
 System.out.println(c);
 System.out.println(d);

将输出

 0.667
 0.666
 1000.0
 9.00800700601E10

看见https://github.com/tools4j/decimal4j/wiki/DoubleRounder-Utility

披露:我参与了decimal4j项目。

更新:正如@iaforek指出的那样,DoubleRounder有时会返回违反直觉的结果。原因是它执行数学上正确的舍入。例如,DoubleRounder.round(256.025d,2)将向下舍入到256.02,因为表示为256.025d的双精度值略小于有理值256.025,因此将向下舍入。

笔记:

这种行为与BigDecimal(double)构造函数非常相似(但与使用字符串构造函数的valueOf(double)不同)。这个问题可以先用双倍的舍入步骤以更高的精度来解决,但它很复杂,我不打算在这里讨论细节

出于这些原因以及本文中提到的所有内容,我不建议使用DoubleRounder。

这里有一个更好的函数,它可以正确地舍入像1.005这样的边缘情况。

简单地说,我们在舍入之前将最小的浮点值(=1 ulp;单位在最后一位)添加到数字上。这将移动到数字之后的下一个可表示值,远离零。

这是一个测试它的小程序:ideone.com

/**
 * Round half away from zero ('commercial' rounding)
 * Uses correction to offset floating-point inaccuracies.
 * Works symmetrically for positive and negative numbers.
 */
public static double round(double num, int digits) {

    // epsilon correction
    double n = Double.longBitsToDouble(Double.doubleToLongBits(num) + 1);
    double p = Math.pow(10, digits);
    return Math.round(n * p) / p;
}

// test rounding of half
System.out.println(round(0.5, 0));   // 1
System.out.println(round(-0.5, 0));  // -1

// testing edge cases
System.out.println(round(1.005, 2));   // 1.01
System.out.println(round(2.175, 2));   // 2.18
System.out.println(round(5.015, 2));   // 5.02

System.out.println(round(-1.005, 2));  // -1.01
System.out.println(round(-2.175, 2));  // -2.18
System.out.println(round(-5.015, 2));  // -5.02

以防有人仍需要帮助。这个解决方案非常适合我。

private String withNoTrailingZeros(final double value, final int nrOfDecimals) {
return new BigDecimal(String.valueOf(value)).setScale(nrOfDecimals,  BigDecimal.ROUND_HALF_UP).stripTrailingZeros().toPlainString();

}

返回具有所需输出的字符串。