以下程序在INTEL平台上提供8191。在ARM平台上,它给出了8192(正确的答案)。
// g++ -o test test.cpp
#include <stdio.h>
int main( int argc, char *argv[])
{
double a = 8192.0 / (4 * 510);
long x = (long) (a * (4 * 510));
printf("%ld\n", x);
return 0;
}
任何人都可以解释原因吗?如果我使用任何-O,-O2或-O3编译开关,问题就会消失。
提前致谢!
答案 0 :(得分:0)
long fun ( void )
{
double a = 8192.0 / (4 * 510);
long x = (long) (a * (4 * 510));
return(x);
}
g ++ -c -O2 fun.c -o fun.o objdump -D fun.o
0000000000000000 <_Z3funv>:
0: b8 00 20 00 00 mov $0x2000,%eax
5: c3 retq
没有数学,编译器完成了所有数学操作,删除了你提供的所有死代码。
gcc同样的交易。
0000000000000000 <fun>:
0: b8 00 20 00 00 mov $0x2000,%eax
5: c3 retq
手臂gcc优化
00000000 <fun>:
0: e3a00a02 mov r0, #8192 ; 0x2000
4: e12fff1e bx lr
双a的原始二进制是
0x40101010 0x10101010
和double(4 * 510)
0x409FE000 0x00000000
那些是在编译时完成的计算,即使没有优化。
通用软浮臂
00000000 <fun>:
0: e92d4810 push {r4, fp, lr}
4: e28db008 add fp, sp, #8
8: e24dd014 sub sp, sp, #20
c: e28f404c add r4, pc, #76 ; 0x4c
10: e8940018 ldm r4, {r3, r4}
14: e50b3014 str r3, [fp, #-20]
18: e50b4010 str r4, [fp, #-16]
1c: e24b1014 sub r1, fp, #20
20: e8910003 ldm r1, {r0, r1}
24: e3a02000 mov r2, #0
28: e59f3038 ldr r3, [pc, #56] ; 68 <fun+0x68>
2c: ebfffffe bl 0 <__aeabi_dmul>
30: e1a03000 mov r3, r0
34: e1a04001 mov r4, r1
38: e1a00003 mov r0, r3
3c: e1a01004 mov r1, r4
40: ebfffffe bl 0 <__aeabi_d2iz>
44: e1a03000 mov r3, r0
48: e50b3018 str r3, [fp, #-24]
4c: e51b3018 ldr r3, [fp, #-24]
50: e1a00003 mov r0, r3
54: e24bd008 sub sp, fp, #8
58: e8bd4810 pop {r4, fp, lr}
5c: e12fff1e bx lr
60: 10101010 andsne r1, r0, r0, lsl r0
64: 40101010 andsmi r1, r0, r0, lsl r0
68: 409fe000 addsmi lr, pc, r0
6c: e1a00000 nop ; (mov r0,
英特尔
0000000000000000 <fun>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 b8 10 10 10 10 10 movabs $0x4010101010101010,%rax
b: 10 10 40
e: 48 89 45 f0 mov %rax,-0x10(%rbp)
12: f2 0f 10 4d f0 movsd -0x10(%rbp),%xmm1
17: f2 0f 10 05 00 00 00 movsd 0x0(%rip),%xmm0 # 1f <fun+0x1f>
1e: 00
1f: f2 0f 59 c1 mulsd %xmm1,%xmm0
23: f2 48 0f 2c c0 cvttsd2si %xmm0,%rax
28: 48 89 45 f8 mov %rax,-0x8(%rbp)
2c: 48 8b 45 f8 mov -0x8(%rbp),%rax
30: 5d pop %rbp
31: c3 retq
0000000000000000 <.rodata>:
0: 00 00 add %al,(%rax)
2: 00 00 add %al,(%rax)
4: 00 e0 add %ah,%al
6: 9f lahf
7: 40 rex
所以你可以在手臂中看到它正在取4.whatever(一个值)而4 * 510转换为double值并将它们传递给aeabi_dmul(双倍乘以毫无疑问)。然后它将它从double转换为整数(d2i)然后你去。
英特尔同样的交易,但硬浮动指令。
因此,如果存在差异(我必须准备并启动一个手臂以查看至少一个手臂结果,已经发布我的英特尔结果,您的程序逐字记录是8192)将在两个浮动中的一个点操作(乘以或加倍或整数)和舍入选项可能起作用。
这显然不是一个可以在基数二(浮点)中清晰表示的值。
0x40101010 0x10101010
开始用数学计算,其中一个可能会导致四舍五入的差异。
最后也是最重要的,只是因为奔腾虫很有名,而且我们认为它们已经固定了它,浮点单元仍然存在错误...但通常程序员在此之前陷入浮点精度陷阱如果你真的在这里看到任何东西,你可能会看到这里......
答案 1 :(得分:0)
如果使用优化标记,问题消失的原因是因为此结果已知 a priori ,即编译器只能替换x
语句中的printf
8192并节省内存。事实上,我愿意赌钱,因为编译器负责你观察到的差异。
这个问题基本上是“how do computers store numbers”,这个问题总是与C ++(或C)中的编程有关。我建议您在阅读之前先查看链接。
让我们看看这两行:
double a = 8192.0 / (4 * 510);
long x = (long) (a * (4 * 510));
首先,请注意您将两个int
个常数相乘 - 隐式地,4 * 510
与(int)(4 * 150)
相同。但是,C(和C ++)有一个快乐的规则,当人们写a/3
之类的东西时,计算是用浮点运算而不是整数运算完成的。计算以double完成,除非两个操作数都是float,在这种情况下,计算是在float中完成的,如果两个操作数都是int
s则是整数。我怀疑您可能会遇到ARM目标的精度问题。我们确定一下。
为了我自己的好奇心,我通过调用gcc -c -g -Wa,-a,-ad test.c > test.s
将你的程序编译成程序集。在两个不同版本的GCC上,它们都是类Unix操作系统,这个代码片段总是弹出8192而不是8191.
这种特殊的标志组合包括相应的C行作为汇编注释,这使得很多更容易阅读正在发生的事情。这是有趣的位,用AT&T Syntax,编写,即命令的格式为command source, destination
。
30 5:testSpeed.c **** double a = 8192.0 / (4 * 510);
31 23 .loc 1 5 0
32 24 000f 48B81010 movabsq $4616207279229767696, %rax
33 24 10101010
34 24 1040
35 25 0019 488945F0 movq %rax, -16(%rbp)
糟糕!让我们稍微分解一下。行30
到36
处理将四字节值4616207279229767696
分配给寄存器rax
,寄存器寄存器保存值。下一行 - movq %rax, -16(%rbp)
- 将该数据移动到rbp
指向的内存中的位置。
因此,换句话说,编译器已经分配了一个内存而忘记了它。
下一组线条有点复杂。
36 6:testSpeed.c **** long x = (long) (a * (4 * 510));
37 26 .loc 1 6 0
38 27 001d F20F104D movsd -16(%rbp), %xmm1
39 27 F0
40 28 0022 F20F1005 movsd .LC1(%rip), %xmm0
41 28 00000000
42 29 002a F20F59C1 mulsd %xmm1, %xmm0
43 30 002e F2480F2C cvttsd2siq %xmm0, %rax
44 30 C0
45 31 0033 488945F8 movq %rax, -8(%rbp)
...
72 49 .LC1:
73 50 0008 00000000 .long 0
74 51 000c 00E09F40 .long 1084219392
75 52 .text
76 53 .Letext0:
这里,我们首先将上面指向的寄存器的内容 - 即a - 移动到寄存器(xmm1
)。然后我们将下面显示的snipped中指向的数据,.LC1,并将其卡入另一个寄存器(xmm0
)。令我惊讶的是,我们做了scalar floating-point double precision multiply(mulsd
)。然后,我们通过调用long
截断结果(这是您向[{1}}实际执行的操作,并将结果放在某处(cvttsd2siq
)。
movq %rax, -8(%rbp)
此代码的其余部分只调用 46 7:testSpeed.c **** printf("%ld\n", x);
47 32 .loc 1 7 0
48 33 0037 488B45F8 movq -8(%rbp), %rax
49 34 003b 4889C6 movq %rax, %rsi
50 35 003e BF000000 movl $.LC2, %edi
51 35 00
52 36 0043 B8000000 movl $0, %eax
53 36 00
54 37 0048 E8000000 call printf
55 37 00
56 8:testSpeed.c **** return 0;
。
现在,让我们再次做同样的事情,但使用printf
,即告诉编译器进行相当积极的优化。以下是生成的程序集中的一些选择代码段:
-O3
在这种情况下,我们看到编译器甚至不打算从您的代码生成指令,而只是简单地勾勒出正确的答案。
为了论证,我对139 22 .loc 2 104 0
140 23 0004 BA002000 movl $8192, %edx
...
154 5:testSpeed.c **** double a = 8192.0 / (4 * 510);
155 6:testSpeed.c **** long x = (long) (a * (4 * 510));
156 7:testSpeed.c **** printf("%ld\n", x);
157 8:testSpeed.c **** return 0;
158 9:testSpeed.c **** }
...
做了同样的事情。装配是相同的。
你的ARM目标会发生类似的情况,但浮点乘法是不同的,因为它是一个不同的处理器(以上所有内容仅适用于x86_64)。 记住,如果你看到C(或C ++)编程时你没想到的东西,你需要停下来思考它。像0.3这样的分数无法在内存中有限地表示!