示例代码
#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 ++都可以看到上述行为。
答案 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