Мой код ниже:
int main(int argc, char *argv[])
{
double f = 18.40;
printf("%d\n", (int)(10 * f));
return 0;
}
В VC6.0 результат равен 184, а в Codeblock — 183. Почему?
Мой код ниже:
int main(int argc, char *argv[])
{
double f = 18.40;
printf("%d\n", (int)(10 * f));
return 0;
}
В VC6.0 результат равен 184, а в Codeblock — 183. Почему?
Причина этого в том, что GCC пытается сделать код максимально совместимым с более старыми архитектурами ЦП, в то время как 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)
он в основном преобразует двойное число в целое число. Эта инструкция является частью набора инструкций под названием SSE2, который появился в Intel Pentium 4.
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, вам нужно указать ему использовать фьючерсы, доступные из 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
>
Компилятор Codeblocks, вероятно, имеет что-то вроде 18.39999999999 в качестве значения с плавающей запятой. Я думаю, что вам следует округлить, если вы хотите получить постоянный результат.
Дело в том, что 0.4 это 2/5 . Дроби с чем-либо, кроме степени двойки в знаменателе, не могут быть точно представлены в числах с плавающей запятой, так же как 1/3 не может быть точно представлена в виде десятичного числа. Таким образом, ваш компилятор должен выбрать близкое представимое число, в результате чего 10*18.4 будет не точно 184, а 183.999...
Теперь все зависит от режима округления, используемого при преобразовании вашего числа с плавающей запятой в целое число. При округлении до ближайшего или округлении до бесконечности вы получите 184, при округлении до нуля или округлении до минус бесконечности вы получаете 183.
float в int. Округление зафиксировано стандартом C; это округление до нуля. Результат зависит от значения, полученного при преобразовании исходного текста 18.4 в число с плавающей запятой и умножении на 10.
- person Eric Postpischil; 03.09.2013
Вычисления с плавающей запятой реализуются по-разному в разных компиляторах и на разных архитектурах. Даже один и тот же компилятор может иметь разные режимы работы, дающие разные результаты.
Например, если я возьму вашу программу и мою установку 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. Но я подозреваю, что рассуждения ошибочны и не учитывают проблемы представимости, округления и т. д.
184. - person verbose   schedule 03.09.20130.5каждый раз, когда вам нужно округлить двойное число до целого числа:(int)((10. * f)+0.5)будет работать со всеми компиляторами. - person Michael M.   schedule 03.09.2013