m32和m64编译器选项提供不同的输出

kum*_*ran 9 c gcc

示例代码

#include "stdio.h"
#include <stdint.h>

int main()
{
    double d1 = 210.01;
    uint32_t m = 1000;
    uint32_t v1 = (uint32_t) (d1 * m);

    printf("%d",v1);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出
1.使用-m32选项进行编译时(即gcc -g3 -m32 test.c)

/test 174 # ./a.out
210009
Run Code Online (Sandbox Code Playgroud)

2.使用-m64选项进行编译时(即gcc -g3 -m64 test.c)

test 176 # ./a.out
210010
Run Code Online (Sandbox Code Playgroud)

为什么我会有所不同?
我的理解"是", m将被提升为双倍,乘法将向下推unit32_t.此外,由于我们使用stdint类型整数,我们将进一步消除与架构等相关的歧义.

我知道这里有些东西可疑,但不能把它钉住.

更新:
只是为了澄清(对于其中一个评论),gcc和g ++都可以看到上述行为.

Ant*_*ala 10

我可以在我的gcc(Ubuntu 5.2.1-22ubuntu2)上确认结果.似乎发生的是32位未优化代码使用387 FPU和FMUL操作码,而64位使用SSE MULS操作码.(只需gcc -S test.c使用不同的参数执行并查看汇编程序输出).正如众所周知,在387 FPU执行该FMUL更多的精度(80!)超过64位,这样看来,便将不同在这里.当然,原因是64位IEEE double的确切值210.01不是那个,而是

 210.009999999999990905052982270717620849609375
Run Code Online (Sandbox Code Playgroud)

当你乘以1000时,你实际上并不只是移动小数点 - 毕竟没有小数点,而是浮点值中的二进制点 ; 所以价值必须四舍五入.在64位双打上,它被四舍五入.在80位387 FPU寄存器上,计算更精确,最终向下舍入.

在阅读了这篇文章之后,我相信gcc在32位arch上生成的结果并不符合标准.因此,如果你强制标准C99或C11用-std=c99,-std=c11你会得到正确的结果

% gcc -m32 -std=c11 test.c; ./a.out
210010
Run Code Online (Sandbox Code Playgroud)

如果您不想强制使用C99或C11标准,您也可以使用该 -fexcess-precision=standard开关.


不过有趣并不止于此.

% gcc -m32 test.c; ./a.out
210009
% gcc -m32 -O3 test.c; ./a.out
210010
Run Code Online (Sandbox Code Playgroud)

因此,如果您使用编译,则会得到"正确"的结果-O3; 这当然是因为64位编译器使用64位SSE数学来对计算进行常数倍.


要确认额外的精度会影响它,您可以使用long double:

#include "stdio.h"
#include <stdint.h>

int main()
{
    long double d1 = 210.01;  // double constant to long double!
    uint32_t m = 1000;
    uint32_t v1 = (uint32_t) (d1 * m);

    printf("%d",v1);
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

现在甚至-m64将其舍入到210009.

% gcc -m64 test.c; ./a.out
210009
Run Code Online (Sandbox Code Playgroud)