奇怪的 Java 行为:如何将精确到小数点后两位的双精度加到小数点多于两位的双精度数?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9906169/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 22:50:00  来源:igfitidea点击:

Weird Java behavior: How come adding doubles with EXACTLY two decimal places result to a double with MORE THAN two decimal places?

java

提问by Matthew Quiros

If I have an array of doubles that each have EXACTLY two decimal places, add them up altogether via a loop, and print out the total, what comes out is a number with MORE THAN two decimal places. Which is weird, because theoretically, adding two numbers that each have 2 and only 2 decimal places will NEVER produce a number that has a non-zero digit beyond the hundredths place.

如果我有一个双精度数组,每个数组都有两个小数位,通过循环将它们加在一起,然后打印出总数,结果是一个多于两个小数位的数字。这很奇怪,因为从理论上讲,将两个数字相加,每个数字都有 2 个小数位并且只有 2 个小数位,永远不会产生一个在百分位之外具有非零数字的数字。

Try executing this code:

尝试执行此代码:

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
    }
}

In my computer, this prints out:

在我的电脑上,打印出来的是:

total: 59.940000000000005, 5

If I round off the total to two decimal places then I'd get the same number as I would if I manually added 9.99 six times on a calculator. But how come this is happening and where are the extra decimal places coming from? Am I doing something wrong or (I doubt this is likely) is this a Java bug?

如果我将总数四舍五入到小数点后两位,那么我得到的数字与我在计算器上手动添加 9.99 六次时得到的数字相同。但是这是怎么发生的,额外的小数位来自哪里?我做错了什么或者(我怀疑这很可能)这是一个 Java 错误吗?

采纳答案by Andre

Are you familiar with base 10 to base 2 conversion (decimal to binary) for fractions? If not, look it up.

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

Then you'll see that although 9.99 looks pretty normal in base 10, it doesn't really look that nice in binary; It looks like a repeating decimal, but in binary. I'm sure you've seen a repeating decimal before, right? It doesn't end. But Java (or any language for that matter) has to save that infinite sequence of digits into a limited number of bytes. And that's when the extra digits appear. When you convert that truncated binary back to decimal, you're really dealing with a different number. The number stored in the variable isn't 9.99 exactly, it something like 9.9999999991 (just an example, I didn't work out the math).

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

But you're probably interested on how to solve this, right? Look up the BigDecimal class. That's what you want to use for your calculations, especially when dealing with currency. Also, look up DecimalFormat, which is a class for writing a number as a properly formatted string. I think it does rounding for you when you want to show only 2 decimal digits and your number has a lot more, for example.

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

回答by Jon Skeet

If I have an array of doubles that each have EXACTLY two decimal places

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

Let's stop right there, because I suspect you don't. For example, you give 9.99 in your sample code. That isn't really 9.99. That's "the closest double to 9.99" as 9.99 itself can't be exactly represented in binary floating point.

让我们就此打住,因为我怀疑你没有。例如,您在示例代码中给出了 9.99。那不是真的 9.99。那是“最接近 9.99 的双精度数”,因为 9.99 本身不能用二进制浮点数精确表示。

At that point, the rest of your reasoning goes out of the window.

到那时,你的其余推理就消失了。

If you want values with an exact number of decimaldigits, you should use a type which stores values in a decimal-centricmanner, such as BigDecimal. Alternatively, store everything as integers and "know" that you're actually remembering "the value * 100" instead.

如果您想要具有精确十进制数的值,则应使用以十进制为中心的方式存储值的类型,例如BigDecimal. 或者,将所有内容存储为整数并“知道”您实际上记住的是“值 * 100”。

回答by Attila

Doubles are represented in a binary format on the computer (). This means that certain numbers cannot be represented accurately, so the computer will use the closest number that can be represented.

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

E.g. 10.5 = 2^3+2+2^(-1) = 1.0101 * 2^3 (here the mantissa is in binary)
but 10.1 = 2^3+2+2^(-4)+2^(-5)+(infinite series here) = 1.0100001... * 2^3

例如 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 is such a number with infinite representation. Thus when you add them together, the finite representation used by the computer is used in the calculation and the result will be even more further away from the mathematical sum than the originals were from their true representation. This is why you see more digits displayed than used in the original numbers.

9.99 就是这样一个具有无限表示的数字。因此,当您将它们加在一起时,计算机使用的有限表示将用于计算,结果与数学总和的距离甚至比原始表示与真实表示的距离更远。这就是为什么您看到显示的数字比原始数字中使用的数字多的原因。

回答by amit

this is because of floating point arithmetics.

这是因为浮点运算。

doubles and floats are not exactly real numbers, there are finite number of bits to represent them while there are infinite number of real numbers [in any range], so you cannot represent all real numbers - You are getting the closest number you can have with the floating point representation.

doubles 和 floats 不是完全实数,有有限数量的位来表示它们,而有无限数量的实数 [在任何范围内],所以你不能代表所有的实数 - 你得到了最接近的数字浮点表示。

Whenever you deal with floating points - remember that they are only an approximation to the number you are seeking. You might want to use BigDecimalif you want the exact number [or at least control the error].

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

More info can be found at this article

更多信息可以在这篇文章中找到

回答by Bozho

Use BigDecimalto perform floating point calculations with precision. It's a must when it comes to money.

用于BigDecimal精确执行浮点计算。说到钱,这是必须的。

This is a known issue that stems in the fact that binary calculations don't allow for precise floating point operations. Look at "floating point arithmetics"for more details.

这是一个已知问题,其根源在于二进制计算不允许精确的浮点运算。有关更多详细信息,请查看“浮点算法”

回答by aioobe

This is due to inaccuracies when it comes to representing decimal numbers using a binary floating point value. In other words, the double literal 0.99does not actually represent the mathematical value 9.99.

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

To reveal exactly what number a value, such as 9.99represents you could let BigDecimalprint the value.

要准确显示值的数字,例如9.99代表,您可以BigDecimal打印该值。

Code to reveal the exact value:

显示确切值的代码:

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

Output:

输出:

9.9900000000000002131628207280300557613372802734375

Btw, your reasoning would be completely accurate if you were taking about binary places instead of decimal places, since a number with two binary places can be exactlyrepresented by a binary floating point value.

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