#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).
Исходя из этого, теперь мы можем сгенерировать битовый шаблон
-
общий знак числа положительный, поэтому общий знаковый бит равен 0.
-
показатель равен 2, но мы смещаем показатель на 127. Это означает, что показатель, равный -127, будет сохранен равным 0, в то время как показатель, равный 127, будет сохранен как 255. Таким образом, наше поле экспоненты было бы 129 или 1000 0001.
-
Наконец, наше нормализованное число было бы 1100 0000 0000 0000 0000 000 000. Обратите внимание, что мы удалили начальную `1′, потому что она всегда предполагалась присутствующей.
-
Собирая все это вместе, мы имеем в виде битового шаблона:
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
, как и следовало ожидать.