My code is below:
int main(int argc, char *argv[])
{
double f = 18.40;
printf("%d\n", (int)(10 * f));
return 0;
}
The result is 184 in VC6.0, while the result in Codeblock is 183. Why?
My code is below:
int main(int argc, char *argv[])
{
double f = 18.40;
printf("%d\n", (int)(10 * f));
return 0;
}
The result is 184 in VC6.0, while the result in Codeblock is 183. Why?
这样做的原因是 GCC 试图使代码尽可能地向后兼容旧的 CPU 架构,而 MSVC 试图利用架构的新未来。
MSVC 生成的代码将这两个数字相乘,10.0 × 18.40:
.text:00401006 fld ds:dbl_40D168
.text:0040100C fstp [ebp+var_8]
.text:0040100F fld ds:dbl_40D160
.text:00401015 fmul [ebp+var_8]
.text:00401018 call __ftol2_sse
然后调用一个名为 的函数__ftol2_sse
,在该函数内部,它使用一些名为 的指令将结果转换为整数cvttsd2si
:
.text:00401189 push ebp
.text:0040118A mov ebp, esp
.text:0040118C sub esp, 8
.text:0040118F and esp, 0FFFFFFF8h
.text:00401192 fstp [esp+0Ch+var_C]
.text:00401195 cvttsd2si eax, [esp+0Ch+var_C]
.text:0040119A leave
.text:0040119B retn
这个指令,,cvttsd2si
是根据这个页面:
将标量双精度浮点值(带截断)转换为四字整数的有符号双字 (SSE2)
它基本上将双精度转换为整数。该指令是Intel Pentium 4 引入的称为SSE2的指令集的一部分。
GCC 默认不使用此指令集,并尝试使用 i386 中的可用指令来执行此操作:
fldl 0x28(%esp)
fldl 0x403070
fmulp %st,%st(1)
fnstcw 0x1e(%esp)
mov 0x1e(%esp),%ax
mov $0xc,%ah
mov %ax,0x1c(%esp)
fldcw 0x1c(%esp)
fistpl 0x18(%esp)
fldcw 0x1e(%esp)
mov 0x18(%esp),%eax
mov %eax,0x4(%esp)
movl $0x403068,(%esp)
call 0x401b44 <printf>
mov $0x0,%eax
如果你想使用 GCC,cvttsd2si
你需要通过编译 flag 来告诉它使用 SSE2 提供的期货-msse2
,但这也意味着一些仍在使用旧计算机的人将无法运行该程序。有关更多选项,请参阅此处的Intel 386 和 AMD x86-64 选项。
-msse2
所以用它编译后cvttsd2si
会将结果转换为 32 位整数:
0x004013ac <+32>: movsd 0x18(%esp),%xmm1
0x004013b2 <+38>: movsd 0x403070,%xmm0
0x004013ba <+46>: mulsd %xmm1,%xmm0
0x004013be <+50>: cvttsd2si %xmm0,%eax
0x004013c2 <+54>: mov %eax,0x4(%esp)
0x004013c6 <+58>: movl $0x403068,(%esp)
0x004013cd <+65>: call 0x401b30 <printf>
0x004013d2 <+70>: mov $0x0,%eax
现在 MSVC 和 GCC 都应该给出相同的数字:
> type test.c
#include <stdio.h>
int main(int argc, char *argv[])
{
double f = 18.40;
printf("%d\n", (int) (10.0 * f));
return 0;
}
> gcc -Wall test.c -o gcctest.exe -msse2
> cl test.c /W3 /link /out:msvctest.exe
> gcctest.exe
184
> msvctest.exe
184
>
重点是,0.4
就是2/5
。分母中除了 2 的幂之外的任何分数都不能用浮点数精确表示,就像 1/3 不能精确地表示为十进制数一样。因此,您的编译器必须选择一个附近的、可表示的数字,结果10*18.4
不是精确184
的,而是183.999
...
现在,一切都取决于将浮点数转换为整数时采用的舍入模式。用四舍五入到最接近或四舍五入到无穷大,你得到,184
四舍五入到零或四舍五入到负无穷大183
。
Codeblocks 编译器的浮点值可能类似于 18.39999999999。如果你想要一个一致的结果,我认为你应该四舍五入。
不同的编译器和不同的架构以不同的方式实现浮点计算。即使是同一个编译器也可以有不同的操作模式,从而产生不同的结果。
例如,如果我使用你的程序和我安装的 gcc (MinGW, 4.6.2) 并像这样编译:
gcc main.c
那么输出是,作为你的报告,183。
但是,如果我这样编译:
gcc main.c -ffloat-store
那么输出是184。
如果您真的想了解差异,则需要指定精确的编译器版本,并指定要传递给编译器的选项。
更根本的是,您应该意识到该值18.4
不能完全表示为二进制浮点值。最接近18.4
的可表示双精度值是:
18.39999 99999 99998 57891 45284 79799 62825 77514 64843 75
所以我怀疑你在推理你的程序的正确输出是184
. 但我怀疑推理是有缺陷的,无法解释可表示性、舍入等问题。