奇怪的Java行为:为什么添加两个小数点后面的双精度数会导致双倍超过两位小数?

时间:2012-03-28 11:03:40

标签: java

如果我有一个双精度数组,每个都有两个小数位,通过一个循环完全添加它们,并打印出总数,出来的是一个超过两位小数的数字。这很奇怪,因为从理论上讲,添加两个数字,每个数字只有2个,只有2个小数位,这样就不会产生一个超过百分位数的非零数字。

尝试执行此代码:

double[] d = new double[2000];
for (int i = 0; i < d.length; i++) {
    d[i] = 9.99;
}

double total = 0,00;
for (int i = 0; i < d.length; i++) {
    total += d[i];
    if (("" + total).matches("[0-9]+\\.[0-9]{3,}")) { // if there are 3 or more decimal places in the total
        System.out.println("total: " + total + ", " + i); // print the total and the iteration when it occured
    }
}

在我的电脑中打印出来:

total: 59.940000000000005, 5

如果我将总数舍入到小数点后两位,那么如果我在计算器上手动添加9.99六次,我会获得相同的数字。但是为什么会发生这种情况呢?额外的小数位从何而来?我做错了什么或(我怀疑这很可能)这是一个Java错误吗?

7 个答案:

答案 0 :(得分:12)

您是否熟悉分数的基数10到基数2转换(十进制到二进制)?如果没有,请查阅。

然后你会看到虽然在基数10中9.99看起来很正常,但它在二进制文件中看起来并不好看;它看起来像重复的十进制,但是是二进制的。我确定你之前看过重复的小数,对吗?它并没有结束。但Java(或任何语言)必须将无限的数字序列保存为有限的字节数。这就是额外数字出现的时候。当您将截断的二进制文件转换回十进制时,您实际上正在处理不同的数字。存储在变量中的数字不是9.99,就像9.9999999991(只是一个例子,我没算算数学)。

但你可能对如何解决这个问题感兴趣,对吧?查找BigDecimal类。这就是您想要用于计算的内容,尤其是在处理货币时。另外,查找DecimalFormat,它是一个用于将数字写为格式正确的字符串的类。例如,当你只想显示2位小数而且你的数字还有很多时,我想它会为你舍入。

答案 1 :(得分:9)

  

如果我有一个双打数组,每个都有两个小数位

让我们停在那里,因为我怀疑你没有。例如,您在示例代码中给出9.99。那不是9.99。这是“最接近9.99的两倍”,因为9.99本身不能用二进制浮点精确表示。

此时,你的其他推理就会消失。

如果您希望具有精确数字十进制数字的值,则应使用以以十进制为中心的方式存储值的类型,例如{{1} }。或者,将所有内容存储为整数并“知道”您实际上正在记住“值* 100”。

答案 2 :(得分:4)

双打在计算机上以二进制格式表示()。这意味着某些数字无法准确表示,因此计算机将使用可以表示的最接近的数字。

E.g。 10.5 = 2 ^ 3 + 2 + 2 ^( - 1)= 1.0101 * 2 ^ 3(这里的尾数是二进制)
但10.1 = 2 ^ 3 + 2 + 2 ^( - 4)+2 ^( - 5)+(此处为无限级数)= 1.0100001 ... * 2 ^ 3

9.99是这样一个具有无限表示的数字。因此,当您将它们一起添加时,计算机使用的有限表示将用于计算,结果将远远超出数学和,而不是原始来自其真实表示。这就是为什么您会看到显示的数字比原始数字中使用的数字更多。

答案 3 :(得分:3)

这是因为浮点算术。

双精度和浮点数并不是实数,有无限数量的位来表示它们,而实数有无数[在任何范围内],所以你不能代表所有实数 - 您将获得与浮点表示最接近的数字。

每当你处理浮点数时 - 请记住它们只是你所寻求的数字的近似值。如果您想要确切的数字[或至少控制错误],您可能需要使用BigDecimal

可以在this article

找到更多信息

答案 4 :(得分:3)

使用BigDecimal精确执行浮点计算。在涉及到金钱时,这是必须的。

这是一个已知问题,因为二进制计算不允许精确的浮点运算。请查看"floating point arithmetics"了解更多详情。

答案 5 :(得分:1)

这是由于使用二进制浮点值表示十进制数时的不准确性。换句话说,双字面0.99实际上并不代表数学值9.99。

要准确显示某个值,例如9.99代表您可以让BigDecimal打印该值。

显示确切值的代码:

System.out.println(new BigDecimal(9.99));

<强>输出:

9.9900000000000002131628207280300557613372802734375

顺便说一句,如果您使用的是二进制位而不是小数位,那么您的推理将是完全准确的,因为具有两个二进制位的数字可以完全由二进制浮点值表示。

答案 6 :(得分:1)

我不知道如何标记为重复,但是短搜索引导我访问此页面: How to resolve a Java Rounding Double issue 这是你在找什么?