O que é manipulação de bits (em inglês: "bit twiddling")?
Da Wikipedia:
Dados numéricos podem ser representados através de várias bases:
Uma base numérica é apenas uma forma de interpretar os dados, mas isso não altera o valor armazenado
Algumas bases são mais adequadas para programação em baixo nível
Bom para humanos, ruim para programadores
Experimente converter de decimal... para qualquer outra base!
Bom para computadores, ruim para todo o resto
✔ Nos permite ver claramente como um computador armazena de fato um determinado valor
☹ Mas você trabalharia com valores do tipo 11110000101011100001010011101100?
Um pouco de terminologia:
LSB: least significant bit, i.e. o bit que contribui menos para o valor (menos significativo, bit 0)
MSB: most significant bit, i.e. o bit que contribui mais para o valor (mais significativo, bit 31)
Dec | Bin | Hex | Dec | Bin | Hex |
---|---|---|---|---|---|
0 | 0000 | 0 | 8 | 1000 | 8 |
1 | 0001 | 1 | 9 | 1001 | 9 |
2 | 0010 | 2 | 10 | 1010 | A |
3 | 0011 | 3 | 11 | 1011 | B |
4 | 0100 | 4 | 12 | 1100 | C |
5 | 0101 | 5 | 13 | 1101 | D |
6 | 0110 | 6 | 14 | 1110 | E |
7 | 0111 | 7 | 15 | 1111 | F |
Base 16 é uma potência de 2 (24), portanto a conversão entre binário e hex é simples
A partir do bit mais significativo, lê-se 4 bits de cada vez:
binário: 1111 0000 1010 1110 0001 0100 1110 1100
hex: F 0 A E 1 4 E C
O mesmo número em hex é: F0AE14EC
Para praticar conversões de base, podemos usar Python
Python é uma excelente linguagem para prototipar aplicações com rapidez... ou para fazer cálculos simples
Para executar o interpretador Python no Linux, digite o seguinte comando no shell:
python
Para sair do interpretador, pressione CTRL+D
Para exibir o valor decimal a partir de uma entrada binária, use o prefixo "0b":
>>> 0b1111
15
Para exibir o valor decimal a partir de uma entrada em hex, use o prefixo "0x":
>>> 0xFF
255
Tente converter os seguintes números para decimal:
>>> 0b10101010
>>> 0x00FF
>>> 0xF000
>>> 0b11110000
Para ver o número hex a partir de qualquer entrada, use a função hex():
>>> hex(0b1111)
'0xf'
Outra forma é usar o comando print com o modificador de formato X:
>>> print "{0:X}".format(0b1111)
F
Se você deseja obter um valor com dois dígitos, use 02X:
>>> print "{0:02X}".format(0b1111)
0F
Um recurso útil é a função bin(), que converte qualquer coisa para binário:
>>> bin(255)
'0b11111111'
Outra forma é usar o comando print com o modificador b:
>>> print "{0:b}".format(15)
1111
Se você quiser, por exemplo, obter um byte completo, use 08b:
>>> print "{0:08b}".format(15)
00001111
Em um programa C, você pode usar exatamente a mesma notação:
unsigned int var;
var = 254; // decimal
var = 0xFE; // mesmo em hex
var = 0b11111110 // mesmo em binário
Obs: suporte para binário só está disponível no GCC 4.7+ e alguns outros compilador (não é padrão)
Para exibir a saída em hex, use o modificador %X na função printf:
printf("%02X\n", var);
Obs: não existe modificador para exibir números em binário!
Podemos usar o operador sizeof para obter o tamanho em bytes de qualquer variável ou valor
Por exemplo, supondo o seguinte programa:
...
int main() {
printf("sizeof(int) = %d\n", sizeof (int));
printf("sizeof(char) = %d\n", sizeof(char));
printf("sizeof(float) = %d\n", sizeof(float));
printf("sizeof(double) = %d\n", sizeof(double));
return 0;
}
Você pode explicar a saída?
sizeof é essencial para manipulação de bits, de forma que se possa saber qual é o bit mais significativo
Operadores bitwise são aqueles que manipulam individualmente cada bit dos operandos
Podem ser usados com valores inteiros: int, char, short int, long int, de preferência unsigned (e.g, unsigned int var)
Dois tipos:
Operadores lógicos
Operadores de deslocamento
Aplicam operações lógicas usuais, mas bit a bit
AND
OR
NOT
XOR
Em C/Python: o operador &
Exemplo:
Descrição | Valor |
---|---|
entrada 1 | 01001010 |
entrada 2 | 10010010 |
resultado | 00000010 |
Tente no interpretador Python:
x = 0b01001010
y = 0b10010010
print "{0:08b}".format(x & y)
Em C/Python: o operador |
Exemplo:
Descrição | Valor |
---|---|
entrada 1 | 01001010 |
entrada 2 | 10010010 |
resultado | 11011010 |
Tente no interpretador Python:
x = 0b01001010
y = 0b10010010
print "{0:08b}".format(x | y)
Em C/Python: o operador ^ (mesmo que OR, mas gera 0 se ambos forem 1)
Exemplo:
Descrição | Valor |
---|---|
entrada 1 | 01001010 |
entrada 2 | 10010010 |
resultado | 11011000 |
Tente no interpretador Python:
x = 0b01001010
y = 0b10010010
print "{0:08b}".format(x ^ y)
Em C/Python: o operador ~
Exemplo:
Descrição | Valor |
---|---|
entrada | 01001010 |
resultado | 10110101 |
Tente no interpretador Python:
x = 0b01001010
print "{0:08b}".format(~x & 0xFF)
O AND bitwise no final é apenas para garantir que teremos 8 bits no resultado
Recebem este nome porque deslocam os bits para a direita ou para a esquerda
Espaços "vazios" são preenchidos com zeros
São particularmente úteis quando combinados com operadores lógicos (a seguir)
Em C/Python: o operador <<
Argumentos: valor a ser deslocado e quantidade de bits para deslocar para a esquerda (o valor original fica inalterado)
Exemplo: considere x armazenando 23 (0x17):
unsigned char x = 0x17; // 23 em decimal
unsigned char y = x << 1; // left shift por 1 bit
// agora y armazenará 46 (0x2E)
Deslocando vários bits de cada vez:
y = x << 2; // y armazenará 92 (0x5C)
y = x << 3; // y armazenará 184 (0xB8)
Em C/Python: o operador >>
Argumentos: valor a ser deslocado e quantidade de bits para deslocar para a direita (o valor original fica inalterado)
Exemplo: considere x armazenando 23 (0x17):
unsigned char x = 0x17; // 23 em decimal
unsigned char y = x >> 1; // shift right por 1 bit
// agora y armazenará 11 (0x0B)
Como antes, pode-se deslocar vários bits de cada vez:
y = x >> 2; // y armazenará 5 (0x5)
y = x >> 3; // y armazenará 2 (0x2)
Using operadores lógicos e de deslocamento, tente imaginar como implementar as seguintes operações
Cria uma função para cada uma (a função deve retornar o valor alterado):
unsigned int clear(unsigned int val)
unsigned int setbit (unsigned int x, int bit)
unsigned int clearbit (unsigned int x, int bit)
unsigned int invertBit (unsigned int x, int bit)
int testBit (unsigned int x, int bit)
(retorna 0 ou 1)Denomina-se campo de bit ou palavra parcial (bit field, partial word) quando extraímos apenas um grupo de bits de uma palavra
Por exemplo, suponha que desejamos armazenar uma cor através dos seus componentes RGB (red, green, blue)
Cada componente pode armazenar um valor de 0 a 1023 (1024 valores diferentes)
A solução mais simples é criar uma struct:
typedef struct {
int red;
int green;
int blue;
} RGB;
Qual é o problema?
Quanto espaço precisamos para essa struct?
typedef struct {
int red;
int green;
int blue;
} RGB;
Mas isso é apenas UM ponto - e se tiveremos uma matriz de... 1024 x 768 pontos?
Dependendo da aplicação e do hardware, pode ser demais!
Agora tentaremos usar campos de bit:
1024 = 210, então precisamos pelo menos 10 bits por componente de cor
3 componentes (R, G, B) x 10 bits = 30 bits
Tudo caberia em um único int (e ainda teríamos 2 bits sem uso!)
Mas como poderíamos...
Armazenar um valor específico no campo de bits do componente verde?
Extrair o valor dos campos de bits dos componentes vermelho ou azul?
...?
Exemplo: supondo que desejamos armazenar o valor 500 no campo de bits da componente verde
500 é 0x1F4 (hex)
unsigned int colour = ...; // algum valor inicial
unsigned int newgreen = 0x1F4; // novo valor para armazenar no verde
Primeiro, precisamos limpar os 10 bits "verdes"
Um AND bitwise pode ser usado
Precisamos de uma máscara, onde os bits desejados devem ser zeros
Primeiro criamos uma máscara com 10 bits setados:
Agora deslocamos para a esquerda por 10 bits:
00000000000011111111110000000000
unsigned int mask = 0x3FF << 10;
Finalmente, precisamos inverter a máscara, pois os bits do meio precisam ser ressetados
Para isso, usamos o operador NOT bitwise:
unsigned int mask = ~mask;
Agora precisamos apenas deslocar o valor desejado à posição correta
Isto significa que devemos deslocar newgreen à esquerda em 10 bits:
00000000000000000000000111110100
newgreen = newgreen << 10;
00000000000001111101000000000000
E finalmente, para combinar com o valor atual precisamos:
Zerar os bits "verdes" com a máscara:
colour = colour & mask;
Modificar os bits "verdes" com o newgreen deslocado:
colour = colour | newgreen;
Obs: se você souber exatamente o que está fazendo, é possível fazer tudo de uma vez só!
colour = colour & ~(0x3FF << 10) | (newgreen << 10);
Crie um programa completo que defina uma varíavel colour como um unsigned int
Em uma repetição:
Mostre o valor atual na tela, em binário e hex
Permite ao usuário escolher o componente de cor desejado (red, green ou blue)
Pergunte o valor novo e armazene no campo de bits correto