RoundingMode.HALF_UP 在不同情况下的结果不同

3

请自由评论我的问题。以下程序有什么问题,它会给出不同的四舍五入结果。

public class Test {
    public static String round(double value, int places) {
        BigDecimal bd = new BigDecimal(value);
        bd = bd.setScale(places, RoundingMode.HALF_UP);
        return bd.toPlainString();
    }

    public static void main(String[] args) throws Exception {
        double value1 = 1.1234565;
        System.out.println(round(value1, 6));
        double value2 = 1.1235;
        System.out.println(round(value2, 3));
    }
}

为什么输出结果是那样的呢?
1.123457
1.123   --> Actually, I expect 1.124

在 Doc(eclipse)中:

在此输入图片描述

2个回答

9
您正在调用 BigDecimal(double)构造函数。文档中说明:

将double转换为BigDecimal,它是double的二进制浮点值的精确十进制表示。返回的BigDecimal的比例是最小值,使得(10scale×val)是整数。

说明也很有启发性,建议您阅读一下。

您传递的值恰好是1.12349999999999994315658113919198513031005859375,因为这是最接近1.1235的 double 。如果在调用 setScale 之前打印 bd.toPlainString(),则可以看到这一点。因此,它不是介于1.123和1.124之间的一半 - 它最接近1.123。

如果您想要一个完全为1.1235的 BigDecimal 值,我建议您将其作为 String 传递:

import java.math.*;

public class Test {
    public static String round(String value, int places) {
        BigDecimal bd = new BigDecimal(value);
        bd = bd.setScale(places, RoundingMode.HALF_UP);
        return bd.toPlainString();
    }

    public static void main(String[] args) throws Exception {
        String value1 = "1.1234565";
        System.out.println(round(value1, 6));
        String value2 = "1.1235";
        System.out.println(round(value2, 3));
    }
}

或者您可以使用BigDecimal.valueOf(double)代替new BigDecimal(double) - 但是这样您仍然面临一个问题,即您已将您的真实原始数据(源代码中的文本)首先转换为二进制浮点数,可能会丢失信息。


只有在使用 BigDecimal.valueOf 时,如果您执行 Double.toString(Double.parseDouble(string)) 会得到不同的值,才会丢失信息。如果此转换是无损的,则可以使用 double - Peter Lawrey
@PeterLawrey,我必须使用解析后的双精度字符串。 - Zaw Than oo
@PeterLawrey:是的,但你从源代码中能轻松看出来吗?我宁愿不与编译器玩鸡蛋游戏 :) 如果你真正感兴趣的是一系列十进制数字(和一个小数分隔符),那么在可用的编译时常量中,我认为字符串是最好的选择。 - Jon Skeet
@JonSkeet 如果您不知道可能输入多少位数字,并且在这种情况下确切地舍入值非常重要,则此说法是正确的。 但是,如果您有一些想法,即舍入后的数字位数小于15位,并且大多数理智的数字(例如货币)都没有问题。 如果Java像C#一样具有“decimal”类型,那么它就不会成为问题,因为该语言具有本机支持,但是误用“BigDecimal”导致的错误与使用double IMHO相比更加微妙。 - Peter Lawrey
1
@PeterLawrey:是的,我怀疑在现实中很少会出现问题——但使用“double”来表示十进制数字序列似乎本质上是一种错误的方式。当然,我肯定会同意,“BigDecimal”可能会导致其他一些有趣的问题——当然,“C#”中的“decimal”也会如此……可惜数字通常比较难处理。当然,不像文本那样完全简单 ;) - Jon Skeet

1
问题是您正在使用完全表示双精度浮点数的方式,包括任何表示误差。一个简单的解决方案是使用从打印双精度浮点数得到的值。
public static String round(double value, int places) {
    return BigDecimal.valueOf(value)
                     .setScale(places, RoundingMode.HALF_UP);
                     .toPlainString();
}

BigDecimal.valueOf 使用最简单的值来表示您所拥有的 double


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