为什么这个显式演员的结果与隐式演员的结果不同?
#include <stdio.h>
double a;
double b;
double c;
long d;
double e;
int main() {
a = 1.0;
b = 2.0;
c = .1;
d = (b - a + c) / c;
printf("%li\n", d); // 10
e = (b - a + c) / c;
d = (long) e;
printf("%li\n", d); // 11
}
如果我做d =(长)((b - a + c)/ c);我也得到10.为什么双重赋值有所不同?
答案 0 :(得分:16)
我怀疑差异是从80位浮点值转换为从80位浮点值转换为64位浮点值到然后转换为很久了。
(80位的原因在于它是用于实际算术的典型精度,以及浮点寄存器的宽度。)
假设80位结果类似于10.999999999999999 - 从那里转换为long得到10.然而,最接近的64位浮点值到80位值实际上是11.0,所以两阶段转换最终屈服于11。
编辑:为此增加一点重量......
这是一个使用任意精度算术进行相同计算的Java程序。请注意,它将最接近0.1的double值转换为BigDecimal - 该值为0.1000000000000000055511151231257827021181583404541015625。 (换句话说,无论如何,计算的确切结果是不 11。)
import java.math.*;
public class Test
{
public static void main(String[] args)
{
BigDecimal c = new BigDecimal(0.1d);
BigDecimal a = new BigDecimal(1d);
BigDecimal b = new BigDecimal(2d);
BigDecimal result = b.subtract(a)
.add(c)
.divide(c, 40, RoundingMode.FLOOR);
System.out.println(result);
}
}
结果如下:
10.9999999999999994448884876874217606030632
换句话说,这对于大约40个十进制数字是正确的(超过64或80位浮点数可以处理的方式)。
现在,让我们考虑这个数字在二进制文件中的含义。我没有任何工具可以轻松地进行转换,但我们再次使用Java来提供帮助。假设标准化数字,“10”部分最终使用三位(比11位= 1011少一位)。留下60位尾数用于扩展精度(80位)和48位用于双精度(64位)。
那么,每个精度中最接近11的数字是多少?再次,让我们使用Java:
import java.math.*;
public class Test
{
public static void main(String[] args)
{
BigDecimal half = new BigDecimal("0.5");
BigDecimal eleven = new BigDecimal(11);
System.out.println(eleven.subtract(half.pow(60)));
System.out.println(eleven.subtract(half.pow(48)));
}
}
结果:
10.999999999999999999132638262011596452794037759304046630859375
10.999999999999996447286321199499070644378662109375
所以,我们得到的三个数字是:
Correct value: 10.999999999999999444888487687421760603063...
11-2^(-60): 10.999999999999999999132638262011596452794037759304046630859375
11-2^(-48): 10.999999999999996447286321199499070644378662109375
现在为每个精度计算出最接近正确值的值 - 为了扩展精度,它小于11.将每个值舍入为long,最后分别为10和11。
希望这足以说服怀疑者;
答案 1 :(得分:2)
我得到10&amp; 11在我的32位x86 linux系统上运行gcc 4.3.2。
相关的C / asm在这里:
26:foo.c **** d = (b - a + c) / c;
42 .loc 1 26 0
43 0031 DD050000 fldl b
43 0000
44 0037 DD050000 fldl a
44 0000
45 003d DEE9 fsubrp %st, %st(1)
46 003f DD050000 fldl c
46 0000
47 0045 DEC1 faddp %st, %st(1)
48 0047 DD050000 fldl c
48 0000
49 004d DEF9 fdivrp %st, %st(1)
50 004f D97DFA fnstcw -6(%ebp)
51 0052 0FB745FA movzwl -6(%ebp), %eax
52 0056 B40C movb $12, %ah
53 0058 668945F8 movw %ax, -8(%ebp)
54 005c D96DF8 fldcw -8(%ebp)
55 005f DB5DF4 fistpl -12(%ebp)
56 0062 D96DFA fldcw -6(%ebp)
57 0065 8B45F4 movl -12(%ebp), %eax
58 0068 A3000000 movl %eax, d
58 00
27:foo.c ****
28:foo.c **** printf("%li\n", d);
59 .loc 1 28 0
60 006d A1000000 movl d, %eax
60 00
61 0072 89442404 movl %eax, 4(%esp)
62 0076 C7042400 movl $.LC3, (%esp)
62 000000
63 007d E8FCFFFF call printf
63 FF
29:foo.c **** // 10
30:foo.c ****
31:foo.c **** e = (b - a + c) / c;
64 .loc 1 31 0
65 0082 DD050000 fldl b
65 0000
66 0088 DD050000 fldl a
66 0000
67 008e DEE9 fsubrp %st, %st(1)
68 0090 DD050000 fldl c
68 0000
69 0096 DEC1 faddp %st, %st(1)
70 0098 DD050000 fldl c
70 0000
71 009e DEF9 fdivrp %st, %st(1)
72 00a0 DD1D0000 fstpl e
72 0000
32:foo.c ****
33:foo.c **** d = (long) e;
73 .loc 1 33 0
74 00a6 DD050000 fldl e
74 0000
75 00ac D97DFA fnstcw -6(%ebp)
76 00af 0FB745FA movzwl -6(%ebp), %eax
77 00b3 B40C movb $12, %ah
78 00b5 668945F8 movw %ax, -8(%ebp)
79 00b9 D96DF8 fldcw -8(%ebp)
80 00bc DB5DF4 fistpl -12(%ebp)
81 00bf D96DFA fldcw -6(%ebp)
82 00c2 8B45F4 movl -12(%ebp), %eax
83 00c5 A3000000 movl %eax, d
83 00
答案留给感兴趣的读者练习。
答案 2 :(得分:1)
codepad.org(gcc 4.1.2)颠倒了你的例子的结果,而在我的本地系统(gcc 4.3.2)上,我在两种情况下得到11。这告诉我,这是一个浮点问题。或者,它理论上可以截断(b - a + c),在整数上下文中将评估为(2 - 1 + 0)/ .1,这将是10,而在浮点上下文中(2.0 - 1.0 + 0.1) )/ .1 = 1.1 / .1 = 11.但这很奇怪。
答案 3 :(得分:0)
在Linux上直接复制/粘贴和编译为我们提供了11个。添加d = (long) ((b - a + c) / c);
也给出了11.在OpenBSD上也是如此。
答案 4 :(得分:0)
Here is a bunch of detail on floating point issues and a really good article.但基本上,并非所有浮点值都可以用一定数量的位(32位或64位或其他)表示。这是一个深刻的主题,但我喜欢它,因为它让我想起Prof. Kahan。 :)