Я понимаю, что в C ++, когда я конвертирую float
/double
в int
где число с плавающей запятой выходит за пределы диапазона int
может содержать, результат не определен как часть языка C ++. Результат зависит от реализации / компилятора. Какие стратегии используют общие компиляторы для решения этой проблемы?
преобразование 7.2E12
для int
может дать значения 1634811904
или же 2147483647
, Например, кто-нибудь знает, что делает компилятор в каждом из этих случаев?
Компилятор генерирует последовательности инструкций, которые дают правильный результат для всех входных данных, которые не вызывают переполнение. Это все, о чем нужно беспокоиться (потому что переполнение при преобразовании с плавающей точкой в целое число — неопределенное поведение). Компилятор не «справляется» с переполнениями, а полностью их игнорирует. Если нижележащие инструкции по сборке на платформе вызывают исключение, хорошо. Если они обернутся, хорошо. Если они дают бессмысленные результаты, опять же, хорошо.
В качестве примера, константные выражения могут быть преобразованы в целые числа во время компиляции с правилами, которые отличаются от поведения инструкций по сборке, генерируемых на платформе. Мой пост в блоге дает пример:
int printf(const char *, ...);
volatile double v = 0;
int main()
{
int i1 = 2147483648.0;
int i2 = 2147483648.0 + v;
printf("%d %d\n", i1, i2);
}
которая производит программу, которая печатает два разных значения для i1
а также i2
, Это потому, что преобразование в вычислении i1
был применен во время компиляции, тогда как преобразование в вычислении i2
был применен во время выполнения.
В качестве другого примера, в частном случае преобразования из double
до 32-разрядного unsigned int
на платформе x86-64 результаты могут быть забавными:
В наборах команд x86 нет инструкций по преобразованию целого числа без знака в целое число без знака.
В Mac OS X для Intel при компиляции 64-битной программы преобразование из двойного в 32-битное беззнаковое int скомпилировано в одну инструкцию: инструкция для 64-битных преобразований, cvttsd2siq
с назначением 64-битный регистр, из которого только 32-битный нижний будет впоследствии использоваться как 32-битное целое число без знака, которое он представляет:
$ cat t.c
#include <stdio.h>
#include <stdlib.h>
int main(int c, char **v)
{
unsigned int i = 4294967296.0 + strtod(v[1], 0);
printf("%u\n", i);
}
$ gcc -m64 -S -std=c99 -O t.c && cat t.s
…
addsd LCPI1_0(%rip), %xmm0 ; this is the + from the C program
cvttsd2siq %xmm0, %rsi ; one-instruction conversion
…
Это объясняет, как на этой платформе получается результат по модулю 232 может быть получено для двойников, которые достаточно малы (в частности, достаточно малы, чтобы поместиться в 64-разрядное целое число со знаком).
В старом наборе команд IA-32 нет инструкции по преобразованию double
в 64-разрядное целое число со знаком (и нет инструкции для преобразования double
к 32-битному unsigned int
или). Преобразование в 32-разрядное unsigned int
должно быть сделано путем объединения нескольких инструкций, которые существуют, в том числе две инструкции cvttsd2si
преобразовать из двойного в 32-разрядное целое число со знаком:
$ gcc -m32 -S -std = c99 -O t.c кошка т.с ... добавлен LCPI1_0-L1 $ pb (% esi),% xmm0; это + из программы C movsd LCPI1_1-L1 $ pb (% esi),% xmm1; преобразование в unsigned int начинается здесь movapd% xmm0,% xmm2 subd% xmm1,% xmm2 cvttsd2si% xmm2,% eax xorl $ -2147483648,% eax ucomisd% xmm1,% xmm0 cvttsd2si% xmm0,% edx cmovael% eax,% edx ...
Два альтернативных решения вычисляются соответственно в %eax
И в %edx
, Каждая из альтернатив правильна в разных областях определения. Если число для преобразования, в %xmm0
больше константы 231 в %xmm1
, тогда выбирается одна альтернатива, в противном случае — другая. Алгоритм высокого уровня, использующий только преобразования от двойного к целому, будет:
если (д < 231) затем (без знака int) (int) d еще (231 + (без знака int) (int) (d - 231))
Этот перевод преобразования C из double в unsigned int дает такое же насыщающее поведение, как и инструкция 32-разрядного преобразования, на которую он опирается:
$ gcc -m32 -std=c99 -O t.c && ./a.out 123456
0