uso de los operadores bitwise para empaquetar múltiples valores en un int


La manipulación de bits de bajo nivel nunca ha sido mi punto fuerte. Apreciaré algo de ayuda en la comprensión del siguiente caso de uso de operadores bitwise.Considerar...

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

No estoy seguro de lo que este código está logrando y cómo? ¿Por qué usar el número mágico 0x7F ? ¿Cómo se realiza el embalaje y desembalaje?

Fuente

Author: Maksim Yegorov, 2011-07-02

7 answers

Como dice el comentario, vamos a empaquetar la edad, el género y la altura en 15 bits, del formato:

AAAAAAAGHHHHHHH

Comencemos con esta parte:{[19]]}

(age << 8)

Para empezar, age tiene este formato:

age           = 00000000AAAAAAA

Donde cada A puede ser 0 o 1.

<< 8 mueve los bits 8 lugares a la izquierda, y llena los huecos con ceros. Así que obtienes:

(age << 8)    = AAAAAAA00000000

Del mismo modo:

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

Ahora queremos combinar estos en una variable. El operador | trabaja por mirando cada bit, y devolviendo 1 si el bit es 1 en cualquiera de las entradas. Así que:

0011 | 0101 = 0111

Si un bit es 0 en una entrada, entonces obtiene el bit de la otra entrada. Mirando (age << 8), (gender << 7) y height, verás que, si un bit es 1 para uno de estos, es 0 para los otros. Así que:

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

Ahora queremos desempaquetar los bits. Empecemos con la altura. Queremos obtener los últimos 7 bits e ignorar los primeros 8. Para hacer esto, usamos el operador &, que devuelve 1 solo si ambos de los bits de entrada son 1. Así que:

0011 & 0101 = 0001

Así que:

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

Para obtener la edad, podemos simplemente empujar todo 8 lugares a la derecha, y nos quedamos con 0000000AAAAAAAA. So age = (packed_info >> 8).

Finalmente, para obtener el género, empujamos todo 7 lugares a la derecha para deshacerse de la altura. Entonces solo nos importa lo último:

packed_info            = AAAAAAAGHHHHHHH
(packed_info >> 7)     = 0000000AAAAAAAG
1                      = 000000000000001
(packed_info >> 7) & 1 = 00000000000000G
 58
Author: thomson_matt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-02 12:37:41

Esto podría ser una lección bastante larga en la manipulación de bits, pero primero permítanme señalar también el artículo enmascaramiento de bits en Wikipedia.

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

Tome la edad y mueva su valor sobre 8 bits luego tome el género y muévalo sobre 7 bits y la altura ocupará los últimos bits.

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

Desempaquetado hace lo contrario, pero utiliza máscaras como 0x7F (que es 0b 01111111) para recortar los otros valores en el campo.

gender = (packed_info >> 7) & 1;

Funcionaría como...

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

Tenga en cuenta que ANDing nada a 1 es lo mismo que" mantener "ese bit y ANDing con 0 es lo mismo que" ignorar " ese bit.

 9
Author: Andrew White,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-02 12:33:57

Si fueras a almacenar una fecha como un número, tal vez lo lograrías multiplicando el año por 10000, el mes por 100 y sumando el día. Una fecha como el 2 de julio de 2011 sería codificada como el número 20110702:

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

Podemos decir que codificamos la fecha en una máscara aaaammdd. Podríamos describir esta operación como

  • Cambie las posiciones del año 4 a la izquierda,
  • cambie las posiciones del mes 2 a la izquierda y
  • deja el día como está.
  • Luego combine los tres valores juntos.

Esto es lo mismo que está sucediendo con la codificación de edad, género y altura, solo que el autor está pensando en binario.

Vea los rangos que esos valores pueden tener:

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

Si traducimos esos valores a binario, tendríamos esto:

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

Con esto en mente, podemos codificar los datos de edad-género-altura con la máscara aaaaaaaghhhhhh, solo que aquí estamos hablando de dígitos binarios, no decimales dígitos.

Así que,

  • Cambia la edad 8 bits a la izquierda,
  • cambio de género 7 bits a la izquierda y
  • deje la altura como está.
  • Luego combine los tres valores juntos.

En binario, el operador Shift-Left (n posiciones hacia la izquierda. El operador " O " ("|"en muchos idiomas) combina valores. Por lo tanto:

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

Ahora, ¿cómo "decodificar" esos valores?

Es más fácil en binario que en decimal:

  • "Enmascaras" la altura,
  • cambiar el género 7 bits a la derecha y la máscara de distancia también, y finalmente
  • cambie la edad de 8 bits a la derecha.

El operador Shift-Right (>>) mueve un valor n posiciones a la derecha (cualquier dígito desplazado "fuera" de la posición más a la derecha se pierde). El operador binario " Y " ("&"en muchos idiomas) máscaras bits. Para ello necesita una máscara, indicando qué bits conservar y qué bits destruir (se conservan 1 bits). Por lo tanto:

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

Dado que 1111111b en hex es 0x7f en la mayoría de los idiomas, esa es la razón de ese número mágico. Usted tendría el mismo efecto usando 127 (que es 1111111b en decimal).

 4
Author: Branco Medeiros,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-02 14:01:19

Una respuesta más condensada:

AAAAAAA G HHHHHHH

Embalaje:

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

Alternativamente, puede sumar componentes si ie cuando se usa en MySQL SUM aggregate function

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

Desembalaje:

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


Otro ejemplo (más largo):

Digamos que tiene una dirección IP que desea empacar, sin embargo, es una dirección IP ficticia, por ejemplo 132.513.151.319. Tenga en cuenta que algunos componentes mayores que 256 que requiere más de 8 bits a diferencia de las direcciones IP reales.

Primero tenemos que averiguar qué desplazamiento necesitamos usar para poder almacenar el número máximo. Digamos que con nuestras IPS ficticias ningún componente puede ser más grande que 999, lo que significa que necesitamos 10 bits de almacenamiento por componente (permite números de hasta 1014).

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

Que da dec 342682502276 o bin 100111111001001011110000000010010000100

Ahora vamos a descomprimir el valor

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

Donde (1 << 10) - 1 es una máscara binaria que usamos para ocultar bits a la izquierda más allá de los 10 bits a la derecha estamos interesados en.

El mismo ejemplo usando MySQL query

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`;
 3
Author: Alexei Tenitski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-17 23:44:53

El operador de desplazamiento izquierdo significa "multiplicar por dos, esto muchas veces". En binario, multiplicar un número por dos es lo mismo que agregar un cero al lado derecho.

El operador de desplazamiento derecho es el reverso del operador de desplazamiento izquierdo.

El operador de tubería es "o", lo que significa superponer dos números binarios uno encima del otro, y donde hay un 1 en cualquiera de los números, el resultado en esa columna es un 1.

Entonces, vamos a extraer la operación para packed_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;

Y el desempaquetar es lo contrario.

// 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);
 2
Author: Anarchofascist,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-02 12:40:26

Puede ver la expresión x & mask como una operación que elimina de x los bits que no están presentes (es decir, tienen valor 0) en mask. Eso significa, packed_info & 0x7F elimina de packed_info todos los bits que están por encima del séptimo bit.

Ejemplo: si packed_info es 1110010100101010 en binario, entonces packed_info & 0x7f se

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

Así, en height obtenemos los 7 bits inferiores de packed_info.

A continuación, estamos cambiando todo packed_info por 7, de esta manera eliminamos la información que ya hemos leído. Así que tenemos (para el valor del ejemplo anterior) 111001010 El género se almacena en el siguiente bit, por lo que con el mismo truco: & 1 estamos extrayendo solo ese bit de la información. El resto de la información figura en la compensación 8.

Embalaje de la espalda no es complicado, demasiado: tomar age, turno es de 8 bits (para obtener 1110010100000000 de 11100101), cambio de gender 7 (así se obtiene 00000000), y tomar la altura (suponiendo que encajaría de 7 bits inferiores). Entonces, estás componiendo todos ellos juntos:

1110010100000000
0000000000000000
0000000000101010
----------------
1110010100101010
 1
Author: Vlad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-02 12:34:34

El mismo requisito que he enfrentado muchas veces. Es muy fácil con la ayuda de operador. Simplemente califique sus valores con potencias crecientes de dos (2). Para almacenar varios valores, AGREGUE su número relativo (potencia de 2) y obtenga la SUMA. Esta SUMA consolidará los valores seleccionados. ¿CÓMO?

Simplemente haga Bitwise Y con cada valor y dará cero (0) para los valores que no fueron seleccionados y no cero para los que están seleccionados.

Aquí está el explicación:

1) Valores (SÍ, NO, TAL VEZ)

2) Asignación a la potencia de dos(2)

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

3) Elijo SÍ y TAL vez por lo tanto SUM:

SUM    =    1    +    4    =    5

SUM    =    00000001    +    00000100    =    00000101 

Este valor almacenará tanto SÍ como TAL VEZ. ¿CÓMO?

1    &    5    =    1    ( non zero )

2    &    5    =    0    ( zero )

4    &    5    =    4    ( non zero )

Por lo tanto, LA SUMA consiste en

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

Para una explicación más detallada y la implementación visitar mi blog

 0
Author: user5212481,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-19 16:08:04