Какие общие стратегии используют разные компиляторы, чтобы справиться с переполнением в числовых преобразованиях?

Я понимаю, что в C ++, когда я конвертирую float/double в intгде число с плавающей запятой выходит за пределы диапазона int может содержать, результат не определен как часть языка C ++. Результат зависит от реализации / компилятора. Какие стратегии используют общие компиляторы для решения этой проблемы?

преобразование 7.2E12 для int может дать значения 1634811904 или же 2147483647, Например, кто-нибудь знает, что делает компилятор в каждом из этих случаев?

4

Решение

Компилятор генерирует последовательности инструкций, которые дают правильный результат для всех входных данных, которые не вызывают переполнение. Это все, о чем нужно беспокоиться (потому что переполнение при преобразовании с плавающей точкой в ​​целое число — неопределенное поведение). Компилятор не «справляется» с переполнениями, а полностью их игнорирует. Если нижележащие инструкции по сборке на платформе вызывают исключение, хорошо. Если они обернутся, хорошо. Если они дают бессмысленные результаты, опять же, хорошо.


В качестве примера, константные выражения могут быть преобразованы в целые числа во время компиляции с правилами, которые отличаются от поведения инструкций по сборке, генерируемых на платформе. Мой пост в блоге дает пример:

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
10

Другие решения


По вопросам рекламы [email protected]