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

时间:2016-04-06 17:27:25

标签: 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;
}

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

/test 174 # ./a.out
210009

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

test 176 # ./a.out
210010

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

我知道这里有些东西可疑,但无法将其固定下来。

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

1 个答案:

答案 0 :(得分:10)

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

 210.009999999999990905052982270717620849609375

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

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

% gcc -m32 -std=c11 test.c; ./a.out
210010

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

然而,有趣并不止于此。

% gcc -m32 test.c; ./a.out
210009
% gcc -m32 -O3 test.c; ./a.out
210010

如果使用-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;
}

现在,即使-m64将其舍入为210009。

% gcc -m64 test.c; ./a.out
210009