这个简单的“双重”计算有什么问题?

时间:2013-05-23 06:52:25

标签: java double-precision

在java中这个简单的“双重”计算有什么问题?

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

5 个答案:

答案 0 :(得分:17)

问题

在二进制2.64中,重复出现10.10100011110101110000101000111101,换句话说,二进制不能完全表示,因此错误。 Java对d3很友善,但只要涉及实际计算,它就必须依赖真实的表示。

Binary Calculator

更多:

2.64= 10.10100011110101110000101000111101
4.64=100.1010001111010111000010100011110 

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

N.B。我这里没有使用double个有效数字,无论二进制计算器会产生什么,但无论有效数字是多少,效果都是一样的

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


浮点数不是精确的,但仅从小数的角度来看

虽然你应该总是期望双精度数会在最后几位小数位上出现小错误,但将二进制表示视为"错误"或更糟糕的小数。

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

答案 1 :(得分:3)

d1 - d2返回二进制浮点运算的精确结果,它是2.6399999999999997,因此它被打印。如果要对其进行舍入,则可以在打印期间执行此操作

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

或使用Commons-Math

d4 = Precision.round(d4, 2);

答案 2 :(得分: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。

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

答案 3 :(得分:1)

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

new BigDecimal("2.64")

答案 4 :(得分:0)

没有错。但是尝试使用BigDecimal

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

注意:double和float在内部表示为符合IEEE标准754的二进制分数,因此不能精确表示小数分数