我最近在阅读有关在内存中存储浮点值的内容。我写了一个小程序来测试我读过的内容。我注意到Java处理浮点值的方式有所不同。
public class Test
{
public static void main(String args[])
{
double a = 0.90;
System.out.println(a);
System.out.println(2.00-1.10);
}
}
以上程序正在打印
0.9
0.8999999999999999
为什么这两个语句都没有打印相同的值?我知道一些浮动值无法准确表示。在这种情况下,两者都应该给出相同的值。
答案 0 :(得分:8)
为什么这两个语句都没有打印相同的值?
结果不一样。
我知道一些浮动值无法准确表示。
因此,您应该假设操作的结果可能取决于您使用的值的表示错误量。
for (long l = 1; l <= 1e16; l *= 10) {
double a = l + 2;
double b = l + 1.1;
System.out.println(a + " - " + b + " is " + (a - b));
}
随着值变大,表示错误增加并且与0.9
的结果相比变大3.0 - 2.1 is 0.8999999999999999
12.0 - 11.1 is 0.9000000000000004
102.0 - 101.1 is 0.9000000000000057
1002.0 - 1001.1 is 0.8999999999999773
10002.0 - 10001.1 is 0.8999999999996362
100002.0 - 100001.1 is 0.8999999999941792
1000002.0 - 1000001.1 is 0.9000000000232831
1.0000002E7 - 1.00000011E7 is 0.900000000372529
1.00000002E8 - 1.000000011E8 is 0.9000000059604645
1.000000002E9 - 1.0000000011E9 is 0.8999999761581421
1.0000000002E10 - 1.00000000011E10 is 0.8999996185302734
1.00000000002E11 - 1.000000000011E11 is 0.899993896484375
1.000000000002E12 - 1.0000000000011E12 is 0.9000244140625
1.0000000000002E13 - 1.00000000000011E13 is 0.900390625
1.00000000000002E14 - 1.000000000000011E14 is 0.90625
1.000000000000002E15 - 1.0000000000000011E15 is 0.875
1.0000000000000002E16 - 1.0000000000000002E16 is 0.0
并且关于当表示错误变得如此之大时,你的操作什么都不做的主题。
for (double d = 1; d < Double.MAX_VALUE; d *= 2) {
if (d == d + 1) {
System.out.println(d + " + 1 == " + (d + 1));
break;
}
}
for (double d = 1; d < Double.MAX_VALUE; d *= 2) {
if (d == d - 1) {
System.out.println(d + " - 1 == " + (d - 1));
break;
}
}
打印
9.007199254740992E15 + 1 == 9.007199254740992E15
1.8014398509481984E16 - 1 == 1.8014398509481984E16
答案 1 :(得分:6)
当“0.90”转换为double时,结果为.9加上一些小错误,e 0 。因此a
等于.9 + e 0 。
当“1.10”转换为double时,结果为1.1加上一些小错误,e 1 ,因此结果为1.1 + e 1 。
这两个错误,e 0 和e 1 ,通常彼此无关。简单地说,不同的十进制数与二进制浮点数的距离不同。评估2.00-1.10
时,结果为2-(1.1 + e 1 )=。9-e 1 。所以你的一个数字是.9 + e 0 ,另一个是.9-e 1 ,没有理由期望它们是相同的。< / p>
(在这种情况下,e 0 是.00000000000000002220446049250313080847263336181640625,e 1 是.000000000000000088817841970012523233890533447265625。另外,从2减去1.1不会引入新的错误Sterbenz'Lemma将“1.1”转换为double。)
其他详情:
在二进制文件中,.9 .11100110011001100110011001100110011001100110011001100 11001100 ... 粗体中的位符合双精度格式。尾随位不适合,因此该数字在该点处舍入。这导致.9的精确值与表示为double的“.9”的值之间存在差异。在二进制中,1.1 1.00011001100110011001100110011001100110011001 10011001 ...再次,该数字是四舍五入的。但观察量舍入是不同的。对于.9,1100 1100 ...被舍入到1 0000 0000 ...,在该位置增加了00110011 ....对于1.1,将1001 1001向上舍入为1 0000 0000 ...,在该位置添加01100110 ...(并以粗体位表示进位)。这两个职位不同; 1.1从小数点的左边开始,所以看起来像这样:1。[这里是52位] [发生舍入的地方]。 .9从小数点的右边开始,所以看起来像这样:。[这里是53位] [发生舍入的地方]。所以1.1的舍入,除了01100110 ...而不是00110011 ...,也加倍,因为它出现在.9舍入的左边一位。所以你有两个效果使得e 0 与e 1 不同:舍入的尾随位是不同的,并且舍入发生的位置是不同的。
答案 2 :(得分:1)
你的理由是,即使0.9不能用double
精确表示,它应该具有与2.0 - 1.1完全相同的double
值,因此导致相同的打印值。这就是错误 - 这个减法不会产生由“0.9”(或精确值0.9)表示的double
。
答案 3 :(得分:1)
我知道一些浮动值无法准确表示
那是你的答案(或者更准确地说,正如马克·拜尔斯所指出的,一些十进制值不能完全表示为双精度)! 0.9或1.1都不能表示为double,因此您会出现舍入错误。
您可以使用BigDecimal检查各种双打的确切值:
public static void main(String args[]) {
double a = 0.9d;
System.out.println(a);
System.out.println(new BigDecimal(a));
double b = 2d - 1.1d;
System.out.println(b);
System.out.println(new BigDecimal(2.0d));
System.out.println(new BigDecimal(1.1d));
System.out.println(new BigDecimal(b));
}
输出:
0.9
0.90000000000000002220446049250313080847263336181640625
0.8999999999999999
2
1.100000000000000088817841970012523233890533447265625
0.899999999999999911182158029987476766109466552734375