Java中将double值向上取整

39

我有一个双精度浮点数 1.068879335,我想将其舍入为只有两位小数,如 1.07。

我尝试了以下代码:

DecimalFormat df=new DecimalFormat("0.00");
String formate = df.format(value);
double finalValue = Double.parseDouble(formate) ;

这让我得到了以下异常

java.lang.NumberFormatException: For input string: "1,07"
     at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1224)
     at java.lang.Double.parseDouble(Double.java:510)

有人能告诉我我的代码哪里出了问题吗。

最终我需要finalValue = 1.07;


1
如果你将1.085879335保留两位小数四舍五入,它会变成1.09,如果向下舍入,则为1.08。那么为什么你想得到1.085879335的0.7部分呢? - fmucar
10个回答

82
请注意字符串中的逗号:“1,07”。DecimalFormat使用特定于语言环境的分隔符字符串,而Double.parseDouble()则不使用。因为您恰好生活在一个小数分隔符为“,”的国家,所以无法将数字解析回去。但是,您可以使用相同的DecimalFormat来解析它:
DecimalFormat df=new DecimalFormat("0.00");
String formate = df.format(value); 
double finalValue = (Double)df.parse(formate) ;

但你真的应该这样做:

double finalValue = Math.round( value * 100.0 ) / 100.0;

注意:正如已经指出的那样,只有在不需要精确控制精度时才应使用浮点数。(金融计算是使用它们的主要例子。)


2
+1 是为了明确指出问题。您还可以明确告诉 DecimalFormat 使用 C 区域设置。 - DJClayworth
正如我在我的答案中指出的那样,这个算法通常是不起作用的。只要Java坚持使用IEEE浮点数,这样的算法就不存在。 - Waldheinz
@Waldheinz 没关系,我只是觉得你已经很好地解释了这个重要的问题,所以编辑我的答案是不必要的。下次我不会这么懒了。 :) - biziclop
@biziclop,感谢你的回答,我正在尝试类似的方法来解决这个问题。http://stackoverflow.com/questions/33692331/storing-a-string-arraylist-into-a-double-arraylist. 当我添加51073+51073时,结果是153219.0,而不是102,146。 - user3848444
@biziclop 你好,我想要像这样四舍五入,如果小数值为453.65,则将其舍入为454,该怎么做? - sameer joshi
显示剩余2条评论

20

参考 @Sergey 的解决方案,但使用整数除法。

double value = 23.8764367843;
double rounded = (double) Math.round(value * 100) / 100;
System.out.println(value +" rounded is "+ rounded);

打印

23.8764367843 rounded is 23.88

编辑:正如Sergey指出的那样,double*int和double*double的乘法以及double/int和double/double的除法之间不应该有任何区别。我找不到结果不同的示例。然而,在x86/x64和其他系统上有一个特定的机器码指令,用于混合double、int值,我相信JVM使用它。

for (int j = 0; j < 11; j++) {
    long start = System.nanoTime();
    for (double i = 1; i < 1e6; i *= 1.0000001) {
        double rounded = (double) Math.round(i * 100) / 100;
    }
    long time = System.nanoTime() - start;
    System.out.printf("double,int operations %,d%n", time);
}
for (int j = 0; j < 11; j++) {
    long start = System.nanoTime();
    for (double i = 1; i < 1e6; i *= 1.0000001) {
        double rounded = (double) Math.round(i * 100.0) / 100.0;
    }
    long time = System.nanoTime() - start;
    System.out.printf("double,double operations %,d%n", time);
}

打印

double,int operations 613,552,212
double,int operations 661,823,569
double,int operations 659,398,960
double,int operations 659,343,506
double,int operations 653,851,816
double,int operations 645,317,212
double,int operations 647,765,219
double,int operations 655,101,137
double,int operations 657,407,715
double,int operations 654,858,858
double,int operations 648,702,279
double,double operations 1,178,561,102
double,double operations 1,187,694,386
double,double operations 1,184,338,024
double,double operations 1,178,556,353
double,double operations 1,176,622,937
double,double operations 1,169,324,313
double,double operations 1,173,162,162
double,double operations 1,169,027,348
double,double operations 1,175,080,353
double,double operations 1,182,830,988
double,double operations 1,185,028,544

1
整数除法在哪里?转换操作符的优先级更高,所以当你将一个double数除以一个整数时,整数会被提升为double类型。因此,这与我的代码本质上是相同的。或者可以写成Math.round(value * 100) / 100.0。这只是一种风格问题。如果你使用真正的整数除法,结果将会是23。 - Sergei Tachenov
@Sergey,理论上你是对的,也许我的答案只适用于JVM,请看我的编辑以获取更多细节。 - Peter Lawrey
真棒。JLS 明确规定,在这种除法中必须将整数提升为双精度,因此在任何符合 Java 实现的情况下,两种情况的结果必须相同,但我不知道 JVM 会优化实际实现方式。 - Sergei Tachenov
1
在编写高性能游戏时,您应始终在源代码中静态数字旁明确设置本机数据类型,而不要让编译器为您决定。你这样做了吗?我希望你没有。 - hamish

8
问题在于您使用了本地化格式化程序,生成了与当前地区相关的小数点“,”。但是Double.parseDouble()方法需要非本地化的双精度字面量。您可以通过使用特定于区域设置的解析方法或更改格式化程序的区域设置为使用小数点“。”来解决问题。甚至更好的方法是避免不必要的格式化,例如使用以下代码:
double rounded = (double) Math.round(value * 100.0) / 100.0;

1
这个问题怎么样,将23.8764367843转换为23.88000000002?我认为使用字符串处理会更好。 - Manidip Sengupta
@Manidip,嗯,如果将“23.88”解析为double,你也无法得到精确的23.88,因为23.88在二进制中无法被准确表示。如果需要精确表示,应该使用某种任意精度库。 - Sergei Tachenov
你说得没错,但我认为我们谈论的是“文本”和“近似”表示,而不是二进制。Waldheinz和Peter在上面已经回答了这个问题。DecimalFormat是一个不错的选择,但如果它不起作用,我会尝试一个String roundedStr = String.valueOf(rounded); int dotPoint = roundedStr.indexOf("."); return (roundedStr).substring(0,dotPoint < 0 ? roundedStr.length() : dotPoint+2); 这并不完全正确,如果roundedStr小数点右侧只有1位数字,你可能需要向右添加0填充-但你明白我之前的评论意思。 - Manidip Sengupta
@Manidip,如果需要一个文本表示,那当然是另外一回事。但是我想就没有必要解析它了。最后,这取决于值将如何使用,当然了。 - Sergei Tachenov

4
你所尝试的操作存在根本性错误。 二进制浮点数值没有小数位。 将其四舍五入到指定的小数位是没有意义的,因为大多数“四舍五入”十进制值简单地不能表示为二进制分数。 这就是为什么不应该使用floatdouble来表示货币。
因此,如果您想要结果中的小数位,那么该结果必须是String(您已经使用DecimalFormat得到了)或BigDecimal(它有一个setScale()方法,可以实现您想要的精度)。 否则,结果可能无法满足您的需求。
阅读The Floating-Point Guide获取更多信息。

1
我们能想象他是否要求“最接近保留两位小数值的浮点数”吗? - DJClayworth
@DJClayworth:那有什么可能用处吗? - Michael Borgwardt

3

请尝试以下代码: org.apache.commons.math3.util.Precision.round(double x, int scale)

See: http://commons.apache.org/proper/commons-math/apidocs/org/apache/commons/math3/util/Precision.html

Apache Commons Mathematics Library 的主页是:http://commons.apache.org/proper/commons-math/index.html

该方法的内部实现如下:

public static double round(double x, int scale) {
    return round(x, scale, BigDecimal.ROUND_HALF_UP);
}

public static double round(double x, int scale, int roundingMethod) {
    try {
        return (new BigDecimal
               (Double.toString(x))
               .setScale(scale, roundingMethod))
               .doubleValue();
    } catch (NumberFormatException ex) {
        if (Double.isInfinite(x)) {
            return x;
        } else {
            return Double.NaN;
        }
    }
}

1
您可以尝试定义一个新的DecimalFormat,并将其用作Double结果赋给一个新的double变量。
给出了一个示例,以帮助您理解我刚才说的话。
double decimalnumber = 100.2397;
DecimalFormat dnf = new DecimalFormat( "#,###,###,##0.00" );
double roundednumber = new Double(dnf.format(decimalnumber)).doubleValue();

1
double TotalPrice=90.98989898898;

  DecimalFormat format_2Places = new DecimalFormat("0.00");

    TotalPrice = Double.valueOf(format_2Places.format(TotalPrice));

1

这种方式不可能实现,因为存在带有两位小数的数字,无法使用IEEE浮点数精确表示(例如1/10 = 0.1不能表示为DoubleFloat)。格式化应该始终在呈现结果给用户之前进行。

我猜你问这个问题是因为你想处理货币价值。使用浮点数无法可靠地完成此操作,您应该考虑切换到定点算术。这可能意味着将所有计算都以“分”而不是“元”为单位。


好的观点,如果这确实是一个货币计算,那么浮点数完全是错误的数据类型。 - biziclop
请问您能否解释一下这个例子?为什么不能用0.10来表示1/10呢? - Manidip Sengupta
3
1/10等于0.10,但是浮点数是二进制的,不存在1/10或0.1这样的数字。你只有1/2、1/4、1/8等等这些数字的组合来逼近0.1,但不能完全等同于0.1。 - Peter Lawrey
@Manidip 抱歉,我刚才离开了,没能及时回复你。不过好在 Peter 及时跳出来帮忙了 :-) - Waldheinz
啊,好的,你在谈论精度问题,我现在明白了。 - Manidip Sengupta

0

如果您不想使用 DecimalFormat(例如由于其效率)并且想要一个通用解决方案,则可以尝试使用缩放舍入的方法:

public static double roundToDigits(double value, int digitCount) {
    if (digitCount < 0)
        throw new IllegalArgumentException("Digit count must be positive for rounding!");

    double factor = Math.pow(10, digitCount);
    return (double)(Math.round(value * factor)) / factor;
}

0

你可以使用像这里这样的格式。

  public static double getDoubleValue(String value,int digit){
    if(value==null){
        value="0";
     }
    double i=0;
     try {
         DecimalFormat digitformat = new DecimalFormat("#.##");
         digitformat.setMaximumFractionDigits(digit);
        return Double.valueOf(digitformat.format(Double.parseDouble(value)));

    } catch (NumberFormatException numberFormatExp) {
        return i;   
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接