Неявно листинг постоянной поплавка

Пожалуйста, посмотрите на этот код:

#include <stdio.h>

int main(void)
{
    short s;
    int i = 65696;
    float f = 65696.0F;

    printf("sizeof(short) = %lu\n", sizeof(short));

    s = i;
    printf("s = %hd\n", s);
    s = f;
    printf("s = %hd\n", s);

    s = 65696;
    printf("s = %hd\n", s);
    s = 65696.0F;
    printf("s = %hd\n", s);

    return 0;
}  

Он дал результат как:

sizeof(short) = 2
s = 160
s = 160
s = 160
s = 32767

В последней строке почему это 32767, а не 160? Какая разница между словами f = 65696.0F; s = f; и s = 65696.0F;?

Ответ 1

Потому что, если интегральная часть значения float не является представимой в новом типе, преобразование будет undefined.

В вашем случае SHRT_MAX, вероятно, 32767, и поэтому неотъемлемая часть 65696.0F тогда не представляется в объекте short.

Ответ 2

Это "undefined поведение", что означает, что компилятор свободен делать то, что он хочет. Но "undefined" не означает "необъяснимый".

Что делает компилятор в случае s = f, преобразование f сначала в значение int 65696, а затем назначение 65696 в s, которое переполняет и покидает 160. Компилятор делает это, потому что есть CPU для преобразования числа с плавающей запятой в 32-разрядное целое число, но не непосредственно в 16-разрядное целое число

Что делает компилятор с s = 65696.0F проще: он знает, что 65696.0 вне диапазона, поэтому он присваивает наивысшее значение, доступное для s, которое составляет 2 ^ 15-1 = 32767.

Это можно проверить, если вы читаете код сборки, который генерирует компилятор для s = f (например, используя ключ -S с gcc):

    movss   -4(%rbp), %xmm0        # Load float from memory into register xmm0
    cvttss2si       %xmm0, %eax    # Convert float in xmm0 into signed 32 bit, store in eax
    movw    %ax, -10(%rbp)         # Store lower 16 bits of eax into memory
    movswl  -10(%rbp), %eax        # Load those 16 bits into eax, with sign extend

Последняя команда сжимает высокие 16 бит% eax, устанавливая в этом случае все 0s.

То, что он генерирует для s = 65696.0F, проще:

    movw    $32767, -10(%rbp)      # Store the lower 16 bits of 32767 into memory
    movswl  -10(%rbp), %eax        # Load those 16 bits into eax, with sign extend