3

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?

4

4 回答 4

8

这样做的原因是 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
>
于 2013-09-03T11:02:27.623 回答
2

重点是,0.4就是2/5。分母中除了 2 的幂之外的任何分数都不能用浮点数精确表示,就像 1/3 不能精确地表示为十进制数一样。因此,您的编译器必须选择一个附近的、可表示的数字,结果10*18.4不是精确184的,而是183.999...

现在,一切都取决于将浮点数转换为整数时采用的舍入模式。用四舍五入到最接近四舍五入到无穷大,你得到,184四舍五入到零四舍五入到负无穷大183

于 2013-09-03T08:44:14.850 回答
2

Codeblocks 编译器的浮点值可能类似于 18.39999999999。如果你想要一个一致的结果,我认为你应该四舍五入。

于 2013-09-03T08:28:59.743 回答
1

不同的编译器和不同的架构以不同的方式实现浮点计算。即使是同一个编译器也可以有不同的操作模式,从而产生不同的结果。

例如,如果我使用你的程序和我安装的 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. 但我怀疑推理是有缺陷的,无法解释可表示性、舍入等问题。

于 2013-09-03T09:26:19.097 回答