Manipulação de Bits
Todos os dados em um programa de computador são internamente armazenados como números binários, ou seja, uma sequência de 0's ou 1's. Em C++ um número do tipo int é uma variável de 32-bits, ou seja, todo número int consiste de uma sequência de 32 0's ou 1's. Por exemplo, a representação do número int 43 é:
Normalmente, é usada a representação de bits com sinal de um número, o que significa que números negativos e positivos podem ser representados. Por exemplo, o int de C++ é um tipo com sinal, logo uma variável desse tipo pode armazenar valores inteiros entre \(-2^{31}\) e \(2^{31} - 1\). O primeiro bit de uma representação com sinal indica o sinal do número (0 para números não-negativos e 1 para números negativos). O complemento de dois é usado, o que significa que o oposto de um número é calculado primeiro invertendo todos os bits do número e depois aumentando o número em um. Por exemplo, a representação do número int -43 é:
Atenção
Se um número for maior que o limite superior da representação de bits, ocorrerá um overflow. Considerando uma variável do tipo int, o próximo número depois de \(2^{31} - 1\) é \(-2^{31}\).
- \(01111111111111111111111111111111_2\).
- Deveria ser \(2147483648_{10}\), mas esse valor não pode ser representado em bits usando uma variável de 32-bits.
- \(10000000000000000000000000000000_2\) (lembre-se que é utilizado complemento de dois).
Operadores sobre Bits
Operador AND (& e &=)
Os bits são definidos como 1 no resultado, se os bits correspondentes em ambos os operadores forem 1. Exemplos:
a = 5 // 00000101
b = 9 // 00001001
a & b -> 1 // 00000001
c = 10 // 00001010
d = 12 // 00001100
c & d -> 8 // 00001000
Dica
Com o operador &, podemos verificar se um número x é par usando x & 1. De forma geral, x é divisível por \(2^k\) se \(x \& (2^k − 1) = 0\).
Operador OR inclusivo (| e |=)
Os bits são definidos como 1 no resultado, se pelo menos um dos bits correspondentes em ambos os operandos for 1. Exemplos:
a = 5 // 00000101
b = 9 // 00001001
a | b -> 13 // 00001101
c = 10 // 00001010
d = 12 // 00001100
c | d -> 14 // 00001110
Operador OR exclusivo (^ e ^=)
Os bits são definidos como 1 no resultado, se exatamente um dos bits correspondentes em ambos os operandos for 1. Exemplos:
a = 5 // 00000101
b = 9 // 00001001
a ^ b -> 12 // 00001100
c = 10 // 00001010
d = 12 // 00001100
c ^ d -> 6 // 00000110
Operador NOT (~ e ~=)
Produz um número onde todos os bits são invertidos, ou seja, todos os bits 0 são definidos como 1 e vice-versa. O resultado do operador NOT depende do tamanho da representação do bit, pois a operação inverte todos os bits. Por exemplo, considerando um número do tipo int (32-bits), o resultado será:
Dica
A fórmula ~x = -x - 1 é válida. Por exemplo, ~5 = 6.
Operador de deslocamento à esquerda (<< e <<=)
Desloca os bits do primeiro operando à esquerda pelo número de bits especificado pelo segundo operando (deve ser um valor positivo): preenche a partir da direita com zero (0). Exemplos:
a = 1 // 00000001 -> 1
a = a << 1 // 00000010 -> 2
a = a << 1 // 00000100 -> 4
a = a << 1 // 00001000 -> 8
a = a << 1 // 00010000 -> 16
b = 7 // 00000111
b = b << 1 // 00001110 -> 14
c = 7 // 00000111
c <<= 3 // 00111000 -> 56
Operador de deslocamento à direita (>> e >>=)
Desloca os bits do primeiro operando à direita pelo número de bits especificado pelo segundo operando (deve ser um valor positivo): preenche a partir da esquerda com zero (0). Exemplos:
a = 16 // 00010000 -> 16
a = a >> 1 // 00001000 -> 8
a = a >> 1 // 00000100 -> 4
a = a >> 1 // 00000010 -> 2
a = a >> 1 // 00000001 -> 1
a = a >> 1 // 00000000 -> 0
b = 56 // 00111000
b = b >> 3 // 00000111 -> 7
c = 56 // 00111000
c >>= 3 // 00000111 -> 7
Funções adicionais
O compilador g++ fornece as seguintes funções para contar bits:
__builtin_clz(x): o número de zeros no início do númeroint;__builtin_ctz(x): o número de zeros no final do númeroint;__builtin_popcount(x): o número de uns no númeroint;;__builtin_parity(x): a paridade (par ou ímpar) de uns de um númeroint.;
Veja um exemplo de utilização dessas funções:
Embora as funções acima sejam apenas para números int, também existem versões das funções que suportam long e long long bastando adicionar o sufixo l ou ll no nome das funções.
-
__builtin_clzl(x)ou__builtin_clzll(x) -
__builtin_ctzl(x)ou__builtin_ctzll(x) -
__builtin_popcountl(x)ou__builtin_popcountll(x) -
__builtin_parityl(x)ou__builtin_parityll(x)
Alguns truques e aplicações
Um número da forma 1 << k tem um bit na posição k e todos os outros bits são zero, então esse número pode ser usado para acessar bits únicos de números. Em particular, o \(k\)-ésimo bit de um número é 1 exatamente quando x & (1 << k) não é zero. Por exemplo, para imprimir a representação de bits de um número int pode ser usado o seguinte código:
Outras aplicações:
x = x | (1 << k): define o \(k\)-ésimo bit dexpara 1;x = x & ~(1 << k): define o \(k\)-ésimo bit dexpara 0;x = x ^ (1 << k): inverte o \(k\)-ésimo bit dex;x = x & (x − 1): define o último bit 1 dexcomo zero;x = x & −x: define todos os bits 1 como 0, exceto o último bit 1;x = x | (x − 1): inverte todos os bits após o último bit 1;x & (x − 1): sexfor um número positivo, verifica sexé uma potência de dois.
std::bitset
Bitset é um contêiner da Standard Template Library do C++ que representa uma sequência de tamanho fixo de \(N\) bits. Bitsets podem ser manipulados por operadores lógicos padrão e convertidos para inteiros ou strings. Exemplos:
Explore mais o contêiner através da documentação.
Material complementar
- Bitwise Operations tutorial #1 | XOR, Shift, Subsets
- C++ Bitsets in Competitive Programming
- C Bitwise Operators: AND, OR, XOR, Complement and Shift Operations
- Bitwise Operators in C/C++ - GeeksforGeeks
- Bits manipulation (Important tactics) - GeeksforGeeks
- Bitwise Hacks for Competitive Programming - GeeksforGeeks
- Bit Tricks for Competitive Programming - GeeksforGeeks
- Bitwise Algorithms - GeeksforGeeks
- Builtin functions of GCC compiler - GeeksforGeeks
- Bit Manipulation | HackerEarth
- Manipulação de Bits | Neps Academy
- C++ bitset and its application