Подтвердить что ты не робот

Использование побитовых операторов для упаковки нескольких значений в один 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? Как выполняется упаковка и распаковка?

Источник

4b9b3361

Ответ 1

Как говорится в комментарии, мы собираем возраст, пол и высоту в 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

Итак:

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

Ответ 2

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

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

Обратите внимание, что ANDing что-либо в 1 является таким же, как "сохранение" этого бита, а ANDing с 0 совпадает с "игнорированием" этого бита.

Ответ 3

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

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

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

  • Сдвиньте 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)

С учетом этого мы можем кодировать данные возраста и пола с помощью маски aaaaaaaghhhhhhh, только здесь мы говорим о двоичных цифрах, а не о десятичных цифрах.

Итак,

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

В двоичном выражении оператор Shift-Left (< <) перемещает значение n позиций влево. Оператор "Or" ( "|" на многих языках) объединяет значения вместе. Поэтому:

    (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 в десятичной форме).

Ответ 4

Левый оператор сдвига означает "умножить на два, это много раз". В двоичном выражении умножение числа на два совпадает с добавлением нуля в правую сторону.

Оператор правого сдвига - это обратный оператор левого сдвига.

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

Итак, давайте извлеките операцию для packet_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);

Ответ 5

Более конденсированный ответ:

AAAAAAA G HHHHHHH

Упаковка:

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`;

Ответ 6

Вы можете видеть выражение 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

Ответ 7

То же требование, с которым я сталкивался много раз. Это очень легко с помощью побитового оператора И. Просто квалифицируйте свои ценности с увеличением полномочий двух (2). Чтобы сохранить несколько значений, добавьте их относительное число (мощность 2) и получите SUM. Этот СУММ будет консолидировать выбранные вами значения. КАК?

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

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

1) Значения (YES, NO, MAYBE)

2) Присвоение мощности двух (2)

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

3) Я выбираю YES и MAYBE, следовательно SUM:

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.

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