Преобразовать двойное число в целое число?

Мой код ниже:

int main(int argc, char *argv[])
{

    double f = 18.40;
    printf("%d\n", (int)(10 * f));

    return 0;
}

В VC6.0 результат равен 184, а в Codeblock — 183. Почему?


person Charles0429    schedule 03.09.2013    source источник
comment
Я просто запустил это на CodeBlocks и в результате получил 184.   -  person verbose    schedule 03.09.2013
comment
Какой компилятор вы используете с CodeBlock?   -  person mathk    schedule 03.09.2013
comment
@mathk мой компилятор g++   -  person Charles0429    schedule 03.09.2013
comment
@mathk есть ли разница между gcc и g++ при преобразовании float в int?   -  person Charles0429    schedule 03.09.2013
comment
Добавляйте 0.5 каждый раз, когда вам нужно округлить двойное число до целого числа: (int)((10. * f)+0.5) будет работать со всеми компиляторами.   -  person Michael M.    schedule 03.09.2013
comment
@ Charles0429 Charles0429 gcc и g++ - это одно и то же, за исключением некоторых параметров, установленных по умолчанию в g++. Таким образом, нет никакой разницы для преобразования float в int. Но это может иметь некоторую разницу между VC6 и gcc.   -  person mathk    schedule 03.09.2013


Ответы (4)


Причина этого в том, что 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
>
person Community    schedule 03.09.2013

Компилятор Codeblocks, вероятно, имеет что-то вроде 18.39999999999 в качестве значения с плавающей запятой. Я думаю, что вам следует округлить, если вы хотите получить постоянный результат.

person tea2code    schedule 03.09.2013
comment
Не могли бы вы сказать, в чем разница между codeblock и vc6.0? Тип float не одинаков в каждом компиляторе? - person Charles0429; 03.09.2013
comment
Он основан на выбранном компиляторе. Code:Blocks поддерживает, наверное, каждый компилятор C на этой планете. Я предполагаю, что у вас есть стандартная версия g++. Я не уверен, что он делает с плавающей запятой, но вообще никогда (!) не предполагайте, что две плавающие точки абсолютно одинаковы, даже если они вычисляются одинаково. - person tea2code; 03.09.2013
comment
есть ли разница между gcc и g++ при преобразовании float в int? - person Charles0429; 03.09.2013
comment
Это очень плохая практика - полагаться на компилятор для чего-то подобного. Пожалуйста, округлите значение. Лучший способ сделать это описан в этом сообщении: stackoverflow.com/questions/9695329/ - person tea2code; 03.09.2013

Дело в том, что 0.4 это 2/5 . Дроби с чем-либо, кроме степени двойки в знаменателе, не могут быть точно представлены в числах с плавающей запятой, так же как 1/3 не может быть точно представлена ​​​​в виде десятичного числа. Таким образом, ваш компилятор должен выбрать близкое представимое число, в результате чего 10*18.4 будет не точно 184, а 183.999...

Теперь все зависит от режима округления, используемого при преобразовании вашего числа с плавающей запятой в целое число. При округлении до ближайшего или округлении до бесконечности вы получите 184, при округлении до нуля или округлении до минус бесконечности вы получаете 183.

person cmaster - reinstate monica    schedule 03.09.2013
comment
есть ли разница между gcc и g++ при преобразовании float в int? - person Charles0429; 03.09.2013
comment
@ Charles0429 Извините, я не знаю о каких-либо особенностях компилятора, я знаю только подробности о формате с плавающей запятой и о том, как вычисления с плавающей запятой выполняются ЦП. Но, может быть, кто-то еще знает? - person cmaster - reinstate monica; 03.09.2013
comment
правильный ответ +1 :) округление до 0 и округление до бесконечности должно быть верным независимо от случаев. - person 0decimal0; 03.09.2013
comment
Предполагая, что компилятору здесь удалось свернуть вычисления в константу, было бы интересно усложнить тестовую программу до такой степени, что она была бы вынуждена вычислять ее во время выполнения. В частности, вы можете прочитать число 10 из стандартного ввода или аргумента командной строки. - person Ambroz Bizjak; 03.09.2013
comment
В этом ответе ложно утверждается, что «все» зависит от режима округления, используемого при преобразовании 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. Но я подозреваю, что рассуждения ошибочны и не учитывают проблемы представимости, округления и т. д.

person David Heffernan    schedule 03.09.2013