использование побитовых операторов для упаковки нескольких значений в один int

Низкоуровневые манипуляции с битами никогда не были моей сильной стороной. Буду признателен за помощь в понимании следующего варианта использования побитовых операторов. Рассмотрим...

int age, gender, height, packed_info;

. . .   // Assign values 

// Pack as AAAAAAA G HHHHHHH using shifts and "or"
packed_info = (age << 8) | (gender << 7) | height;

// Unpack with shifts and masking using "and"
height = packed_info & 0x7F;   // This constant is binary ...01111111
gender = (packed_info >> 7) & 1;
age    = (packed_info >> 8);

Я не уверен, что этот код выполняет и как? Зачем использовать магическое число 0x7F? Как осуществляется упаковка и распаковка?

Источник


person maxpayne    schedule 02.07.2011    source источник
comment
Я думаю, что стоит прочитать о представлении двоичных чисел и побитовых операторах, прежде чем задавать этот вопрос.   -  person Serge Dundich    schedule 02.07.2011
comment
Картинка в комментарии говорит сама за себя: ААААААА Г ХХХХХХХ   -  person Jim Balter    schedule 02.07.2011


Ответы (7)


Как говорится в комментарии, мы собираемся упаковать возраст, пол и рост в 15 бит формата:

AAAAAAAGHHHHHHH

Начнем с этой части:

(age << 8)

Начнем с того, что возраст имеет следующий формат:

age           = 00000000AAAAAAA

где каждый A может быть 0 или 1.

<< 8 перемещает биты на 8 позиций влево и заполняет пробелы нулями. Итак, вы получаете:

(age << 8)    = AAAAAAA00000000

Сходным образом:

gender        = 00000000000000G
(gender << 7) = 0000000G0000000
height        = 00000000HHHHHHH

Теперь мы хотим объединить их в одну переменную. Оператор | работает, просматривая каждый бит и возвращая 1, если бит равен 1 в любом из входных данных. Так:

0011 | 0101 = 0111

Если бит равен 0 на одном входе, то вы получаете бит на другом входе. Глядя на (age << 8), (gender << 7) и height, вы увидите, что если бит равен 1 для одного из них, то для остальных он равен 0. Так:

packed_info = (age << 8) | (gender << 7) | height = AAAAAAAGHHHHHHH

Теперь мы хотим распаковать биты. Начнем с высоты. Мы хотим получить последние 7 бит и проигнорировать первые 8. Для этого мы используем оператор &, который возвращает 1, только если оба входных бита равны 1. Итак:

0011 & 0101 = 0001

So:

packed_info          = AAAAAAAGHHHHHHH
0x7F                 = 000000001111111
(packed_info & 0x7F) = 00000000HHHHHHH = height

Чтобы получить возраст, мы можем просто сдвинуть все на 8 позиций вправо, и у нас останется 0000000AAAAAAAA. Итак, age = (packed_info >> 8).

Наконец, чтобы получить пол, мы сдвигаем все на 7 позиций вправо, чтобы избавиться от высоты. Затем нас интересует только последний бит:

packed_info            = AAAAAAAGHHHHHHH
(packed_info >> 7)     = 0000000AAAAAAAG
1                      = 000000000000001
(packed_info >> 7) & 1 = 00000000000000G
person thomson_matt    schedule 02.07.2011
comment
Это супер хорошая запись. Из всего, что я читал, это первое, что дало понять, что происходит. - person DrHall; 07.01.2014

Это может быть довольно длинный урок по манипулированию битами, но сначала позвольте мне также указать вам на статью о битовой маскировке в Википедии.

packed_info = (age << 8) | (gender << 7) | height;

Возьмите возраст и переместите его значение на 8 бит, затем возьмите пол и переместите его на 7 бит, а рост займет последние биты.

age    = 0b101
gender = 0b1
height = 0b1100
packed_info = 0b10100000000
            | 0b00010000000
            | 0b00000001100
/* which is */
packed_info = 0b10110001100

При распаковке происходит обратное, но используются маски, такие как 0x7F (то есть 0b 01111111), чтобы обрезать другие значения в поле.

gender = (packed_info >> 7) & 1;

Работал бы как...

gender = 0b1011 /* shifted 7 here but still has age on the other side */
       & 0b0001
/* which is */
gender = 0b1

Обратите внимание, что операция И с 1 аналогична «сохранению» этого бита, а операция И с 0 — это то же самое, что «игнорирование» этого бита.

person Andrew White    schedule 02.07.2011

Если бы вы собирались хранить дату в виде числа, возможно, вы бы сделали это, умножив год на 10000, месяц на 100 и прибавив день. Такая дата, как 2 июля 2011 года, будет закодирована как число 20110702:

    year * 10000 + month * 100 + day -> yyyymmdd
    2011 * 10000 + 7 * 100 + 2 -> 20110702

Можно сказать, что мы закодировали дату в маске ггггммдд. Мы могли бы описать эту операцию как

  • Сдвиньте год на 4 позиции влево,
  • сдвиньте месяц на 2 позиции влево и
  • оставить день как есть.
  • Затем объедините три значения вместе.

Это то же самое, что происходит с кодированием возраста, пола и роста, только автор мыслит бинарно.

См. диапазоны, которые могут иметь эти значения:

    age: 0 to 127 years
    gender: M or F
    height: 0 to 127 inches

Если мы переведем эти значения в двоичный формат, мы получим следующее:

    age: 0 to 1111111b (7 binary digits, or bits)
    gender: 0 or 1 (1 bit)
    height: 0 to 1111111b (7 bits also)

Имея это в виду, мы можем закодировать данные возраста, пола и роста маской aaaaaaaaghhhhhhh, только здесь мы говорим о двоичных цифрах, а не десятичных< /em> цифры.

So,

  • Сдвиньте возраст на 8 битов влево,
  • сдвиньте пол на 7 битов влево и
  • оставить высоту как есть.
  • Затем объедините все три значения вместе.

В двоичном формате оператор Shift-Left (‹‹) перемещает значение на n позиций влево. Оператор «ИЛИ» («|» во многих языках) объединяет значения вместе. Следовательно:

    (age << 8) | (gender << 7) | height

Теперь, как «расшифровать» эти значения?

В двоичном виде проще, чем в десятичном:

  • Ты «маскируешь» высоту,
  • сдвиньте пол на 7 бит вправо и также замаскируйте это, и, наконец,
  • сдвиньте возраст 8 бит вправо.

Оператор Shift-Right (>>) перемещает значение на n позиций вправо (любые цифры, сдвинутые «вне» из крайнего правого положения, теряются). Бинарный оператор «И» («&» во многих языках) маскирует биты. Для этого ему нужна маска, указывающая, какие биты сохранить, а какие уничтожить (1 бит сохраняется). Следовательно:

    height = value & 1111111b (preserve the 7 rightmost bits)
    gender = (value >> 1) & 1 (preserve just one bit)
    age = (value >> 8)

Поскольку 1111111b в шестнадцатеричном формате на большинстве языков равно 0x7f, это и есть причина этого магического числа. Вы получите тот же эффект, используя 127 (что равно 1111111b в десятичном формате).

person Branco Medeiros    schedule 02.07.2011
comment
спасибо за подробную информацию..было очень полезно. - person maxpayne; 04.07.2011

Более сжатый ответ:

ААААААА Г ЧХХХХХХ

Упаковка:

packed = age << 8 | gender << 7 | height

В качестве альтернативы вы можете просто суммировать компоненты, если, например, при использовании в агрегатной функции MySQL SUM.

packed = age << 8 + gender << 7 + height

Распаковка:

age = packed >> 8 // no mask required
gender = packed >> 7 & ((1 << 1) - 1) // applying mask (for gender it is just 1)
height = packed & ((1 << 7) - 1) // applying mask


Еще один (длинный) пример:

Допустим, у вас есть IP-адрес, который вы хотите упаковать, однако это вымышленный IP-адрес, например, 132.513.151.319. Обратите внимание, что некоторые компоненты больше 256, что требует более 8 бит, в отличие от реальных IP-адресов.

Сначала нам нужно выяснить, какое смещение нам нужно использовать, чтобы иметь возможность хранить максимальное число. Допустим, с нашими вымышленными IP-адресами ни один компонент не может быть больше 999, что означает, что нам нужно 10 бит памяти на компонент (допускается число до 1014).

packed = (comp1 << 0 * 10) | (comp1 << 1 * 10) | (comp1 << 2 * 10) | (comp1 << 3 * 10)

Что дает dec 342682502276 или bin 100111111001001011110000000010010000100

Теперь давайте распакуем значение

comp1 = (packed >> 0 * 10) & ((1 << 10) - 1) // 132
comp2 = (packed >> 1 * 10) & ((1 << 10) - 1) // 513
comp3 = (packed >> 2 * 10) & ((1 << 10) - 1) // 151
comp4 = (packed >> 3 * 10) & ((1 << 10) - 1) // 319

Где (1 << 10) - 1 — это двоичная маска, которую мы используем, чтобы скрыть биты слева за пределами 10 правых битов, которые нас интересуют.

Тот же пример с использованием запроса MySQL

SELECT

(@offset := 10) AS `No of bits required for each component`,
(@packed := (132 << 0 * @offset) | 
            (513 << 1 * @offset) | 
            (151 << 2 * @offset) | 
            (319 << 3 * @offset)) AS `Packed value (132.513.151.319)`,

BIN(@packed) AS `Packed value (bin)`,

(@packed >> 0 * @offset) & ((1 << @offset) - 1) `Component 1`,
(@packed >> 1 * @offset) & ((1 << @offset) - 1) `Component 2`,
(@packed >> 2 * @offset) & ((1 << @offset) - 1) `Component 3`,
(@packed >> 3 * @offset) & ((1 << @offset) - 1) `Component 4`;
person Alexei Tenitski    schedule 17.07.2014

Оператор сдвига влево означает «умножить на два столько раз». В двоичном формате умножение числа на два равносильно добавлению нуля к правой части.

Оператор сдвига вправо является обратным оператору сдвига влево.

Оператор канала — «или», что означает наложение двух двоичных чисел друг на друга, и если в любом числе есть 1, результатом в этом столбце будет 1.

Итак, давайте извлечем операцию для pack_info:

// Create age, shifted left 8 times:
//     AAAAAAA00000000
age_shifted = age << 8;

// Create gender, shifted left 7 times:
//     0000000G0000000
gender_shifted = gender << 7;

// "Or" them all together:
//     AAAAAAA00000000
//     0000000G0000000
//     00000000HHHHHHH
//     ---------------
//     AAAAAAAGHHHHHHH
packed_info = age_shifted | gender_shifted | height;

А распаковка обратная.

// Grab the lowest 7 bits:
//     AAAAAAAGHHHHHHH &
//     000000001111111 =
//     00000000HHHHHHH
height = packed_info & 0x7F;

// right shift the 'height' bits into the bit bucket, and grab the lowest 1 bit:
//     AAAAAAAGHHHHHHH 
//   >> 7 
//     0000000AAAAAAAG &
//     000000000000001 =
//     00000000000000G
gender = (packed_info >> 7) & 1;

// right shift the 'height' and 'gender' bits into the bit bucket, and grab the result:
//     AAAAAAAGHHHHHHH 
//   >> 8
//     00000000AAAAAAA
age    = (packed_info >> 8);
person Anarchofascist    schedule 02.07.2011

С таким же требованием я сталкивался много раз. Это очень легко сделать с помощью побитового оператора AND. Просто уточните свои значения с возрастающими степенями двойки (2). Чтобы сохранить несколько значений, ДОБАВЬТЕ их относительное число ( степень 2 ) и получите СУММУ. Эта СУММА объединит выбранные вами значения. КАК ?

Просто выполните побитовое И с каждым значением, и оно даст ноль (0) для значений, которые не были выбраны, и ненулевых для тех значений, которые выбраны.

Вот объяснение:

1) Ценности (ДА, НЕТ, МОЖЕТ БЫТЬ)

2) Присвоение степени двойки(2)

YES   =    2^0    =    1    =    00000001
NO    =    2^1    =    2    = 00000010
MAYBE =    2^2    =    4    = 00000100

3) Я выбираю ДА и МОЖЕТ БЫТЬ, следовательно, СУММА:

SUM    =    1    +    4    =    5

SUM    =    00000001    +    00000100    =    00000101 

Это значение будет хранить как YES, так и MAYBE. КАК?

1    &    5    =    1    ( non zero )

2    &    5    =    0    ( zero )

4    &    5    =    4    ( non zero )

Следовательно, SUM состоит из

1    =    2^0    =    YES
4    =    2^2    =    MAYBE.

Для более подробного объяснения и реализации посетите мой блог

person user5212481    schedule 19.01.2018

Вы можете рассматривать выражение x & mask как операцию, удаляющую из x биты, которые отсутствуют (т. е. имеют значение 0) в mask. Это означает, что packed_info & 0x7F удаляет из packed_info все биты старше седьмого бита.

Пример: если packed_info равно 1110010100101010 в двоичном формате, то packed_info & 0x7f будет

1110010100101010
0000000001111111
----------------
0000000000101010

Итак, в height мы получаем младшие 7 бит packed_info.

Далее сдвигаем все packed_info на 7, таким образом убираем информацию, которую уже считывали. Таким образом, мы получаем (для значения из предыдущего примера) 111001010 Пол сохраняется в следующем бите, поэтому с тем же приемом: & 1 мы извлекаем из информации только этот бит. Остальная информация содержится по смещению 8.

Обратная упаковка также не сложна: вы берете age, сдвигаете его на 8 бит (так что вы получаете 1110010100000000 из 11100101), сдвигаете gender на 7 (так что вы получаете 00000000) и берете высоту (при условии, что она будет соответствовать младшим 7 битам) . Затем вы составляете их все вместе:

1110010100000000
0000000000000000
0000000000101010
----------------
1110010100101010
person Vlad    schedule 02.07.2011