为什么这个显式演员的结果与隐式演员的结果不同?

为什么这个显式演员的结果与隐式演员的结果不同?

#include  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.为什么双重赋值有所不同?

我怀疑差异是从80位浮点值转换为长转换与从80位浮点值到64位浮点值的转换然后转换为长转换。

(80位出现的原因是,这是用于实际算术的典型精度,以及浮点寄存器的宽度。)

假设80位结果类似于10.999999999999999 – 从那里转换为长数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。

希望这足以说服怀疑者;)

我在运行gcc 4.3.2的32位x86 linux系统上获得10和11。

相关的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 

答案留给感兴趣的读者练习。

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.但这很奇怪。

在Linux上直接复制/粘贴和编译为我提供了11个。 添加d = (long) ((b - a + c) / c); 也给出了11. OpenBSD也是如此。

这里有一堆关于浮点问题的细节和一篇非常好的文章。 但基本上,并非所有浮点值都可以用一定数量的位(32位或64位或其他)表示。 这是一个深刻的主题,但我喜欢它,因为它让我想起了卡汉教授 。 🙂