java 这个简单的“双重”计算有什么问题?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16707397/
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
Whats wrong with this simple 'double' calculation?
提问by vi.su.
Whats wrong with this simple 'double' calculation in java?
java中这个简单的“双重”计算有什么问题?
I know some decimal numbers can not be represented in float / double binary formats properly, but with the variable d3, java is able to store and display 2.64 with no problems.
我知道一些十进制数不能以浮点/双二进制格式正确表示,但是使用变量 d3,java 能够毫无问题地存储和显示 2.64。
double d1 = 4.64;
double d2 = 2.0;
double d3 = 2.64;
double d4 = d1 - d2;
System.out.println("d1 : " + d1);
System.out.println("d2 : " + d2);
System.out.println("d3 : " + d3);
System.out.println("d4 : " + d4);
System.out.println("d1 - d2 : " + (d1 - d2));
Answer,
回答,
d1 : 4.64
d2 : 2.0
d3 : 2.64
d4 : 2.6399999999999997
d1 - d2 : 2.6399999999999997
回答by Richard Tingle
The problem
问题
In binary 2.64 is 10.10100011110101110000101000111101 recurring, in other words not exactly representable in binary, hence the smallerror. Java is being kind to you with d3 but as soon as actual calculations are involved it has to fall back on the real representation.
在二进制中 2.64 是 10.10100011110101110000101000111101 重复出现,换句话说不能完全用二进制表示,因此误差很小。Java 对 d3 很友好,但是一旦涉及实际计算,它就必须退回到真实表示上。
Further more:
此外:
2.64= 10.10100011110101110000101000111101
4.64=100.1010001111010111000010100011110
Now, even though the .64 is the same in both cases, it is held to different precisionsbecause the 4=100 uses up more of the double significant figures than 2=10, sowhen you say 4.64-2.0 and 2.64 the .64 is represented with a different rounding error in both cases, this lost information cannot be recovered for the final answer.
现在,即使 .64 在两种情况下都相同,但它保持不同的精度,因为 4=100 比 2=10 使用更多的双有效数字,所以当你说 4.64-2.0 和 2.64 时,.64在两种情况下都用不同的舍入误差表示,因此无法为最终答案恢复丢失的信息。
N.B. I'm not using the double
number of significant figures here, just whatever the binary calculator would produce, however the effect is the same whatever the number of significant figures
注意,我在这里没有使用double
有效数字的数量,只是使用二进制计算器会产生的任何值,但是无论有效数字的数量如何,效果都是一样的
Never assume double values are exact(although their inaccuracies are microscopic and caused only because certain numbers can't be exactly expressed in binary).
永远不要假设双精度值是精确的(尽管它们的不准确性是微观的,并且仅仅是因为某些数字不能用二进制精确表示)。
Floating point numbers aren't exact, but only from a decimal point of view
浮点数不准确,仅从小数点来看
While you should always expect that doubles will have small errors for the last few decimal places it would be wrong to think of binary representations as "bad" or worse that decimal.
虽然您应该总是期望双精度数的最后几个小数位会有小错误,但将二进制表示视为“坏”或更糟的十进制表示是错误的。
We are all used to certain numbers (like 1/3 for example) not being exactly representable in decimal and we accept that such a number will end up as 0.333333333333 rather than the true value (which I cannot write down without infinite space); it is within that context that binary numbers cannot be exactly expressed. 1/10 is such a number that cannot be exactly expressed in binary; this suprises us only because we are used to decimal
我们都习惯于某些数字(例如 1/3)不能完全用十进制表示,我们接受这样的数字最终会是 0.333333333333 而不是真实值(没有无限空间我无法写下);正是在这种情况下,二进制数无法准确表达。1/10 是一个不能用二进制精确表达的数字;这让我们感到惊讶只是因为我们习惯了十进制
回答by Evgeniy Dorofeev
d1 - d2 returns the exact result of binary float arithmetic and it is 2.6399999999999997 and so it is printed. If you want to round it, you can do it during printing
d1 - d2 返回二进制浮点运算的准确结果,它是 2.6399999999999997,因此它被打印出来。如果你想圆它,你可以在打印的时候做
System.out.printf("d1 - d2 : %.2f", d4);
or with Commons-Math
或使用 Commons-Math
d4 = Precision.round(d4, 2);
回答by jpmc26
It's because the errors in the internal representations of 4.64 and 2.0 combine constructively (meaning they make a larger error).
这是因为 4.64 和 2.0 的内部表示中的错误建设性地结合在一起(意味着它们会产生更大的错误)。
Technically speaking, 2.64 isn't stored exactly, either. However, there is a particular representation that corresponds to 2.64. Think about the fact that 4.64 and 2.0 aren't stored exactly, either, though. The errors in 4.64 and 2.0 are combining to produce an even larger error, one large enough that their subtraction does not give the representation of 2.64.
从技术上讲,2.64 也没有完全存储。但是,有一个特定的表示对应于 2.64。不过,想想 4.64 和 2.0 也没有完全存储。4.64 和 2.0 中的错误组合在一起会产生更大的错误,这个错误大到它们的减法不能给出 2.64 的表示。
The answer is off by 3*10^-16. To give something of an example of how that can happen, let's pretend the representation for 4.64 is 2*10^-16 too small and the representation for 2.0 is 1*10^-16 too large. Then you would get
答案是 3*10^-16。为了举例说明这种情况是如何发生的,让我们假设 4.64 的表示是 2*10^-16 太小了,而 2.0 的表示是 1*10^-16 太大了。然后你会得到
(4.64 - 2*10^-16) - (2.0 + 1*10^-16) = 2.64 - 3*10^-16
So when the calculation is done, the two errors have combined to create an even bigger error. But if the representation for 2.64 is only off by 1*10^-16, then this would notbe considered equal to 2.64 by the computer.
因此,当计算完成时,这两个错误结合起来产生了更大的错误。但是,如果 2.64 的表示仅相差 1*10^-16,那么计算机将认为这不等于 2.64。
It's also possible that 4.64 just has a larger error than 2.64 even if 2.0 has no error. If 4.64's representation is 3*10^-16 too small, you get the same thing:
即使 2.0 没有错误,4.64 也有可能比 2.64 有更大的错误。如果 4.64 的表示太小了 3*10^-16,你会得到同样的结果:
(4.64 - 3*10^-16) - 2.0 = 2.64 - 3*10^-16
Again, if the representation of 2.64 is only off by 1*10^-16, then this result would not be considered equal to 2.64.
同样,如果 2.64 的表示仅偏离 1*10^-16,则该结果不会被视为等于 2.64。
I don't know the exact errors in the real representations, but something similar to that is happening, just with different values. Hope that makes sense. Feel free to ask for clarification.
我不知道真实表示中的确切错误,但类似的事情正在发生,只是值不同。希望这是有道理的。随时要求澄清。
回答by Erik Pragt
Mainly because of the fact that double is a double-precision 64-bit IEEE 754 floating point. It's not meant for keeping exact decimal values. That's why doubles are not recommended for exact calculations. Use the String constructor of BigDecimalinstead, like:
主要是因为 double 是双精度 64 位 IEEE 754 浮点数。它并不意味着保持精确的十进制值。这就是为什么不建议使用双精度进行精确计算的原因。改用BigDecimal的 String 构造函数,例如:
new BigDecimal("2.64")
回答by Scary Wombat
Nothing wrong with it. But try using BigDecimal
没什么不对的。但是尝试使用 BigDecimal
http://docs.oracle.com/javase/6/docs/api/java/math/BigDecimal.html
http://docs.oracle.com/javase/6/docs/api/java/math/BigDecimal.html
Note: double and float are internally represented as binary fractions according to the IEEE standard 754 and can therefore not represent decimal fractions exactly
注意:根据 IEEE 标准 754,double 和 float 在内部表示为二进制小数,因此不能准确表示十进制小数