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

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

我知道一些十进制数字不能正确地用float / 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 

问题

在二进制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在两种情况下都表示不同的舍入误差,这些丢失的信息无法在最终答案中恢复。

NB我这里没有使用double数的有效数字,无论二进制计算器会产生什么,不管有效数字的数量是多少都是相同的

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

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

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

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

d1 – d2返回二进制浮点运算的精确结果,它是2.6399999999999997,因此它被打印。 如果要将其圆整,可以在打印期间进行

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

或者使用Commons-Math

 d4 = Precision.round(d4, 2); 

这是因为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。

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

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

 new BigDecimal("2.64") 

它没有错。 但是尝试使用BigDecimal

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

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