Проблемы с выводом объединения

#c #bit-manipulation #unions

#c #манипулирование битами #объединения

Вопрос:

Я пытаюсь разобраться в этом коде около часа, и все еще безуспешно.

 #include <stdio.h>
#include <stdlib.h>

int f(float f)
{
    union un {float f; int i;} u = {f};

    return (u.iamp;0x7F800000) >> 23;
}

int main()
{
    printf("%dn", f(1));

return 0;
}
  

Я не понимаю, как это работает, я пробовал f (1), f (2), f (3), f (4) и, конечно, получаю разные результаты. Я также много читал о объединениях и прочем. Что я заметил, когда я удаляю 0x7F800000 из return, результаты будут такими же. Я хочу знать, как генерируется u.i, очевидно, что это не какой-то случайный мусор, но также это не один (1) из аргумента функции. Что здесь происходит, как это работает?

Ответ №1:

Это действительно сводится к пониманию того, как числа с плавающей запятой представлены в памяти. (см. IEEE 754).

Короче говоря, 32-разрядное число с плавающей запятой будет иметь следующую структуру

  • бит 31 будет знаковым битом для общего числа
  • биты 30-23 будут показателем степени для числа со смещением 127
  • биты 22 — 0 будут представлять дробную часть числа. Это нормализовано таким образом, что цифра перед десятичной (фактически двоичной) точкой равна единице.

Что касается объединения, напомним, что объединение — это блок компьютерной памяти, который может одновременно содержать один из типов, поэтому объявление:

    union un
   {
        float f;
        int   i;
   };
  

создает 32-разрядный блок памяти, который может содержать либо число с плавающей запятой, либо целое число в любой момент времени. Теперь, когда мы вызываем функцию с параметром с плавающей запятой, битовый шаблон этого числа записывается в ячейку памяти un. Теперь, когда мы обращаемся к объединению с помощью i элемента, битовый шаблон обрабатывается как целое число.

Таким образом, общая схема 32-разрядного числа с плавающей запятой такова, seee eeee efff ffff ffff ffff ffff ffff что s представляет собой знаковый бит, e биты экспоненты и f биты дроби. Ладно, какая-то тарабарщина, надеюсь, пример может помочь.

Чтобы преобразовать 4 в IEEE с плавающей запятой, сначала преобразуйте 7 в двоичный код (я разделил 32-разрядное число на 4-разрядные фрагменты);

     4 = 0000 0000 0000 0000 0000 0000 0000 0111
  

Теперь нам нужно нормализовать это, то есть выразить это как число, возведенное в степень двойки;

     1.11 x 2^2
  

Здесь нам нужно помнить, что каждая степень двойки перемещает двоичную точку вправо на место (аналогично работе со степенями 10).

Исходя из этого, теперь мы можем сгенерировать битовый шаблон

  1. общий знак числа положительный, поэтому общий знаковый бит равен 0.

  2. показатель равен 2, но мы смещаем показатель на 127. Это означает, что показатель, равный -127, будет сохранен равным 0, в то время как показатель, равный 127, будет сохранен как 255. Таким образом, наше поле экспоненты было бы 129 или 1000 0001.

  3. Наконец, наше нормализованное число было бы 1100 0000 0000 0000 0000 000 000. Обратите внимание, что мы удалили начальную `1′, потому что она всегда предполагалась присутствующей.

  4. Собирая все это вместе, мы имеем в виде битового шаблона:

    4 = 0100 0000 1110 0000 0000 0000 0000 0000

Теперь, последний маленький фрагмент здесь — это побитовый и с 0x7F800000 которым, если мы запишем в двоичном формате, является 0111 1111 1000 0000 0000 0000 0000 0000 , если мы сравним это с общим расположением числа с плавающей запятой IEEE, мы увидим, что то, что мы выбираем с помощью маски, — это биты экспоненты, а затем мы сдвигаем влево 23 бита.

Итак, ваша программа просто выводит смещенный показатель числа с плавающей запятой. В качестве примера,

     #include <stdio.h>
    #include <stdlib.h>

    int f(float f)
    {
         union un {float f; int i;} u = {f};

         return (u.iamp;0x7F800000) >> 23;
    }

    int main()
    {
         printf("%dn", f(7));
         return 0;
    }
  

выдает результат 129 , как и следовало ожидать.