16

java中这个简单的'double'计算有什么问题?

我知道一些十进制数不能以浮点/双二进制格式正确表示,但是使用变量 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));

回答,

d1      : 4.64
d2      : 2.0
d3      : 2.64
d4      : 2.6399999999999997
d1 - d2 : 2.6399999999999997
4

5 回答 5

20

问题

二进制 2.64 是 10.10100011110101110000101000111101 重复出现,换句话说,不能完全用二进制表示,因此误差很小。Java 对 d3 很友好,但是一旦涉及到实际计算,它就必须依靠真实的表示。

二进制计算器

此外:

2.64= 10.10100011110101110000101000111101
4.64=100.1010001111010111000010100011110 

现在,即使 .64 在两种情况下都是相同的,但它保持不同的精度,因为 4=100 使用的双有效数字比 2=10 多,所以当你说 4.64-2.0 和 2.64 时,.64在这两种情况下都用不同的舍入误差表示,因此无法为最终答案恢复丢失的信息。

注意我在double这里没有使用有效数字的数量,只是二进制计算器会产生什么,但是无论有效数字的数量是多少,效果都是一样的

永远不要假设 double 值是精确的(尽管它们的不准确性是微观的,并且只是因为某些数字不能以二进制精确表示)。


浮点数不准确,但仅从小数点来看

虽然您应该始终期望双精度数在最后几个小数位上会有小错误,但将二进制表示视为“坏”或更糟的十进制表示是错误的。

我们都习惯了某些数字(例如 1/3)不能用十进制精确表示,我们接受这样的数字最终会是 0.333333333333 而不是真实值(如果没有无限的空间,我无法写下来);正是在这种情况下,二进制数无法准确表达。1/10就是这样一个不能用二进制精确表示的数字;这让我们感到惊讶只是因为我们习惯了十进制

于 2013-05-23T07:31:45.897 回答
4

d1 - d2 返回二进制浮点运算的确切结果,它是 2.6399999999999997,因此被打印出来。如果要四舍五入,可以在打印时进行

System.out.printf("d1 - d2 : %.2f",  d4);

或使用 Commons-Math

d4 = Precision.round(d4, 2);
于 2013-05-23T07:04:03.247 回答
3

主要是因为 double 是双精度 64 位 IEEE 754 浮点。它不是为了保持精确的十进制值。这就是为什么不建议使用双打进行精确计算的原因。请改用BigDecimal的 String 构造函数,例如:

new BigDecimal("2.64")
于 2013-05-23T07:00:04.647 回答
3

这是因为 4.64 和 2.0 的内部表示中的错误建设性地结合在一起(这意味着它们会产生更大的错误)。

从技术上讲,2.64 也不是完全存储的。但是,有一个特定的表示对应于 2.64。不过,想想 4.64 和 2.0 也没有完全存储这一事实。4.64 和 2.0 中的误差结合起来产生了一个更大的误差,这个误差大到它们的减法不能给出 2.64 的表示。

答案是 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

所以当计算完成时,这两个错误结合起来产生了一个更大的错误。但是如果 2.64 的表示仅偏离 1*10^-16,那么计算机将不会认为它等于 2.64。

即使 2.0 没有错误,4.64 也有可能比 2.64 有更大的错误。如果 4.64 的表示是 3*10^-16 太小,你会得到同样的结果:

(4.64 - 3*10^-16) - 2.0 = 2.64 - 3*10^-16

同样,如果 2.64 的表示仅偏离 1*10^-16,则该结果将不被视为等于 2.64。

我不知道真实表示中的确切错误,但类似的事情正在发生,只是值不同。希望这是有道理的。随时要求澄清。

于 2013-05-23T07:11:08.650 回答
0

它没有错。但尝试使用 BigDecimal

http://docs.oracle.com/javase/6/docs/api/java/math/BigDecimal.html

注意:根据 IEEE 标准 754,double 和 float 在内部表示为二进制小数,因此不能准确表示小数

于 2013-05-23T06:53:01.297 回答