Nos artigos anteriores, exploramos
a aritmética dos computadores. Aprendemos como eles somam, subtraem usando
Complemento de Dois e como lidam com os limites físicos de memória (Overflow).
No artigo anterior, usamos brevemente o operador & para revelar a
representação interna dos números negativos em Python. Se você ficou curioso
sobre o que aquele & realmente fez — e o que mais é possível fazer com ele —,
chegou a hora de descobrir.
Nesses casos aritméticos, os bits eram tratados em conjunto para representar um valor numérico (peso posicional). Mas, às vezes, não queremos calcular o valor de um número. Queremos manipular a estrutura dele. Queremos acender uma “chave” específica, desligar um sensor, criptografar uma mensagem ou verificar permissões de um usuário.
É hora de deixar a aritmética de um pouco de lado e entrar no mundo da Lógica Bitwise (Bit a Bit). Aqui, o processador não enxerga o número “5” ou “10”, ele enxerga uma sequência de interruptores individuais que podem ser ligados (1) ou desligados (0).
A Conexão com a Lógica Proposicional #
Se você acompanha o blog, já deve ter lido nosso artigo sobre Lógica para Programadores. Lá, vimos como criar tabelas-verdade para decidir se uma proposição complexa é Verdadeira (V) ou Falsa (F).
A lógica bitwise é a aplicação direta desse conceito, mas em escala industrial:
- Verdadeiro (V) vira 1.
- Falso (F) vira 0.
Em vez de comparar apenas duas proposições (“A porta está aberta” E “A luz está acesa”), o operador bitwise compara 8, 16, 32 ou 64 pares de proposições de uma só vez, em paralelo.
O Grande Alerta: and não é &
#
Antes de vermos os operadores, precisamos resolver uma confusão clássica em
Python, linguagem mais adotada aqui no blog. Diferente de linguagens como C ou
Java, Python é muito verbal, usando palavras como and, or e not. Porém,
ele também possui os símbolos &, | e ~.
Eles não são a mesma coisa.
No artigo sobre Curto-Circuito em
Python,
explicamos que os operadores lógicos (and, or) são “preguiçosos”. Eles param
de avaliar assim que descobrem a resposta.
Já os operadores Bitwise (&, |) são “trabalhadores incansáveis”. Eles
avaliam todos os bits, sem exceção, e geram um novo número como resultado.
Tabela de Diferenças #
| Característica | Operadores Lógicos | Operadores Bitwise |
|---|---|---|
| Alvo | Expressões Booleanas (True/False) ou objetos | Números Inteiros (Bits individuais) |
| Avaliação | Curto-Circuito (Lazy evaluation) | Completa (Eager evaluation) |
| Resultado | Retorna um dos operandos (ex: True) |
Retorna um novo número inteiro |
| Exemplo | if idade > 18 and tem_cnh: |
masked = flags & 0xFF |
Jamais use & no lugar de and dentro de um if, a menos que você saiba exatamente o que está fazendo.
5 and 6retorna6(pois 5 é verdadeiro, ele segue e retorna o último).5 & 6retorna4(pois faz a interseção dos bits de 101 e 110).
São comportamentos completamente diferentes que podem gerar bugs silenciosos.
Agora que separamos a lógica de fluxo da lógica de bits, vamos conhecer as quatro ferramentas fundamentais: AND, OR, XOR e NOT.
A Matemática dos Bits: AND e OR #
Para entender bitwise, precisamos parar de olhar para o número decimal (ex: 12) e começar a olhar para a sua representação binária. As operações acontecem verticalmente, alinhando os bits correspondentes de cada número.
AND (&) - A Interseção
#
O operador E Bit a Bit (Bitwise AND) segue uma regra estrita: o bit de resultado será 1 se, e somente se, os bits de ambos os operandos forem 1.
Pense no AND como um Filtro ou uma Interseção.
1 & 1 = 11 & 0 = 00 & 1 = 00 & 0 = 0
Observe que o Zero é dominante: qualquer coisa combinada com zero vira zero. Essa propriedade torna o AND a ferramenta perfeita para desligar bits ou verificar valores (técnica conhecida como Masking).
Exemplo Prático: 12 & 10
Vamos operar 12 & 10.
- 12 em binário é
1100. - 10 em binário é
1010.
Alinhamos os bits e aplicamos a lógica coluna por coluna:
$$ \begin{array}{r c c c c l} & 1 & 1 & 0 & 0 & (12) \\ \& & 1 & 0 & 1 & 0 & (10) \\ \hline & 1 & 0 & 0 & 0 & (8) \end{array} $$Análise:
- Bit 3 (esquerda):
1 & 1resulta em 1. - Bit 2:
1 & 0resulta em 0. - Bit 1:
0 & 1resulta em 0. - Bit 0:
0 & 0resulta em 0.
Resultado: 1000 (que é 8 em decimal).
No Python:
>>> 12 & 10
8
>>> bin(12 & 10)
'0b1000'OR (|) - A União
#
O operador OU Bit a Bit (Bitwise OR) é mais permissivo: o bit de resultado será 1 se pelo menos um dos bits dos operandos for 1.
Pense no OR como uma Soma Lógica ou União.
1 | 1 = 11 | 0 = 10 | 1 = 10 | 0 = 0
Aqui, o Um é dominante: qualquer coisa combinada com 1 vira 1. Essa propriedade torna o OR a ferramenta ideal para ligar bits (forçar um valor a 1) sem alterar os outros.
Exemplo Prático: 12 | 10
Vamos operar 12 | 10.
- 12 em binário é
1100. - 10 em binário é
1010.
Alinhamos os bits e aplicamos a lógica coluna por coluna:
$$ \begin{array}{r c c c c l} & 1 & 1 & 0 & 0 & (12) \\ \| & 1 & 0 & 1 & 0 & (10) \\ \hline & 1 & 1 & 1 & 0 & (14) \end{array} $$Análise:
- Onde havia pelo menos um bit
1nas entradas, a saída foi1. - Apenas a última coluna (
0 | 0) resultou em zero.
Resultado: 1110 (que é 14 em decimal).
No Python:
>>> 12 | 10
14
>>> bin(12 | 10)
'0b1110'Resumo da Lógica #
Para não esquecer:
- AND (
&): Usado para Cortar/Limpar. Se você quer garantir que certos bits sejam zero, use AND com 0. - OR (
|): Usado para Adicionar/Ligar. Se você quer garantir que certos bits sejam um, use OR com 1.
Na próxima seção, veremos outros operadores: o XOR (o localizador de diferenças) e o NOT (a inversão total).
Os Operadores XOR e NOT #
Enquanto AND e OR são intuitivos (parecem interseção e união de conjuntos), os próximos dois operadores possuem comportamentos únicos que são a base da criptografia e da aritmética de inteiros.
XOR (^) - O Detetive de Diferenças
#
O OU Exclusivo (Exclusive OR, ou XOR) é um operador comum em
criptografia e algoritmos de hash. A regra é: o bit de resultado será 1 se os
bits dos operandos forem diferentes.
1 ^ 1 = 0(Iguais \(\to\) 0)0 ^ 0 = 0(Iguais \(\to\) 0)1 ^ 0 = 1(Diferentes \(\to\) 1)0 ^ 1 = 1(Diferentes \(\to\) 1)
Pense no XOR como um Alternador (Toggle).
- Se você faz XOR com
0, o bit original se mantém (1^0=1,0^0=0). - Se você faz XOR com
1, o bit original se inverte (1^1=0,0^1=1).
Sua utilidade em
criptografia
de textos vem do fato de que ele é “balanceado”, ou seja, há uma igual
quantidade de 0s e 1s no resultado quando os bits de entrada são aleatórios.
Isto dificulta a análise estatística de padrões.
Exemplo Prático: 12 ^ 10
Vamos operar 12 ^ 10.
Análise:
- Bit 3:
1e1são iguais \(\to\) 0. - Bit 2:
1e0são diferentes \(\to\) 1. - Bit 1:
0e1são diferentes \(\to\) 1. - Bit 0:
0e0são iguais \(\to\) 0.
Resultado: 0110 (que é 6 em decimal).
Curiosidade Criptográfica: O XOR é reversível. Se você calcular (A ^ B) ^ B, o resultado volta a ser A.
>>> chave = 123
>>> msg = 456
>>> cripto = msg ^ chave
>>> print(cripto)
435
>>> print(cripto ^ chave) # Desfazendo a operação
456NOT (~) - A Inversão Total
#
O operador NÃO Bit a Bit (Bitwise NOT) é o único que opera sobre um único
número (operador unário). Ele simplesmente inverte todos os bits: o que é 0 vira 1, e
o que é 1 vira 0.
Visualmente, parece simples. Mas se você testar no Python, o resultado pode te confundir:
>>> ~0
-1
>>> ~10
-11Por que o resultado é negativo?
Aqui entra o conhecimento do nosso artigo anterior sobre Complemento de Dois.
- O Python trata inteiros como se tivessem “infinitos bits” à esquerda. O número
10positivo é...00001010. - Quando aplicamos o NOT (
~), invertemos todos os bits, inclusive os infinitos zeros à esquerda. O número vira...11110101. - Uma sequência infinita de
1s à esquerda indica um número negativo.
Matematicamente, em sistemas de Complemento de Dois, o operador ~x é equivalente ao Complemento de 1.
A relação com o valor decimal segue a fórmula:
Entendendo a Fórmula (~10)
Queremos calcular ~10.
- Pela fórmula matemática:
-(10) - 1 = -11. - Pela visão de bits (em 8 bits para simplificar):
-
10é0000 1010. -
Invertendo tudo (
~):1111 0101. -
O que é
1111 0101? Como o primeiro bit é 1, é negativo. -
Para descobrir o valor, aplicamos a regra inversa (Inverte e soma 1):
-
Inverte:
0000 1010(10) -
Soma 1:
0000 1011(11) -
Logo, o valor original era -11.
No próximo tópico, veremos como mover esses bits de um lado para o outro com os operadores de Shift (Deslocamento).
Shift: A Aritmética do Deslocamento #
Imagine que você tem o número decimal 23. Se você adicionar um zero à direita
(230), você efetivamente multiplicou o número por 10 (a base do sistema). Se
remover o último dígito (o 3), você dividiu por 10 (divisão inteira, restando 2).
No sistema binário, a lógica é idêntica, mas a base é 2. Mover os bits para a esquerda ou direita é a forma mais eficiente computacionalmente de multiplicar ou dividir números.
Left Shift (<<) - O Multiplicador
#
O operador Deslocamento à Esquerda (x << n) move todos os bits de x para
a esquerda por n posições.
- Os bits que “caem” pela esquerda são descartados (em tipos de tamanho fixo) ou o número cresce (em Python).
- Zeros são inseridos nas posições vagas à direita.
Regra Matemática:
Deslocar n bits para a esquerda equivale a multiplicar o número por \(2^n\).
Exemplo: 3 « 2
O número 3 em binário (8 bits) é 0000 0011.
Vamos deslocar 2 casas para a esquerda.
- Empurre tudo para a esquerda.
- Preencha os buracos da direita com
0. $$ \begin{array}{r c l} & 0000\,0011 & (3) \\ \downarrow & \text{Shift Left 2} & \\ & 0000\,11\mathbf{00} & (12) \end{array} $$ Verificação: \(3 \times 2^2 = 3 \times 4 = 12\).
Right Shift (>>) - O Divisor
#
O operador Deslocamento à Direita (x >> n) move os bits para a direita.
- Os bits da direita “caem” no abismo e são perdidos.
- O que entra pela esquerda? Aqui está o grande detalhe técnico.
Em linguagens de baixo nível, existem dois tipos de shift à direita:
- Logical Shift: Preenche sempre com 0. Usado para números sem sinal (Unsigned).
- Arithmetic Shift: Preenche com o valor do Bit de Sinal (o bit mais à esquerda). Se o número era negativo (começava com 1), ele preenche com 1s para manter o número negativo.
Como o Python faz?
Como Python lida com números com sinal e precisão arbitrária, o operador >> se comporta como um Arithmetic Shift. Ele preserva o sinal matemático.
Regra Matemática:
Deslocar n bits para a direita equivale a uma Divisão Inteira (Floor Division) por \(2^n\).
Exemplo Positivo vs. Negativo
Caso 1: Positivo (12 » 2)
Binário: 0000 1100
Desloca direita, entra 0: 0000 0011
Resultado: 3. (Matemática: \(12 / 4 = 3\)).
Caso 2: Negativo (-12 » 2)
Binário (Complemento de 2): ...1111 0100
Desloca direita. Como o bit de sinal é 1, entram 1s pela esquerda.
Resultado: ...1111 1101
O resultado é -3. (Matemática: \(-12 / 4 = -3\)).
Isso garante que a divisão por potências de 2 funcione corretamente tanto para números positivos quanto negativos.
Na próxima seção, vamos juntar tudo o que aprendemos (AND, OR, NOT, SHIFT) para criar ferramentas práticas chamadas Máscaras de Bits (Bitmasks).
Máscaras de Bits (Bitmasks) #
Agora que conhecemos os operadores, podemos combiná-los para realizar alterações precisas em números. A técnica de usar um número auxiliar para “selecionar” ou “modificar” bits específicos de outro número é chamada de Mascaramento (Masking).
Para os exemplos abaixo, imagine que queremos manipular o 3º bit (da direita para esquerda, índice 2). Para isso, criamos uma máscara onde apenas esse bit é 1:
MASK = 1 << 2 # Resultado: 0b00000100 (Decimal 4)Consultar um Bit (Check) #
Queremos saber: “O 3º bit está ligado?”.
Usamos o AND (&). Como o AND com 0 zera tudo, se o resultado for diferente de zero, significa que o nosso bit alvo estava ligado.
flags = 0b10110111
is_on = (flags & MASK) > 0
# O resultado será True se o 3º bit estiver ligado (que é o caso), False caso contrário.Ligar um Bit (Set) #
Queremos forçar o 3º bit a ser 1, sem mexer nos outros.
Usamos o OR (|). Como o OR com 0 preserva o valor original, e o OR com 1 força virar 1, nossa máscara protege o resto e acende o alvo.
flags = flags | MASK
# Agora o 3º bit é 1 com certeza. Os outros não mudaram.Desligar um Bit (Clear) #
Queremos forçar o 3º bit a ser 0.
Esta é a operação mais complexa logicamente. Precisamos de um AND (para zerar), mas precisamos de uma máscara que tenha 0 no alvo e 1 em todo o resto (para preservar os vizinhos).
Como geramos isso? Invertendo nossa máscara original com NOT (~).
A Lógica do Clear (Limpar Bit)
- Máscara Original (
MASK):...0000 0100 - Máscara Invertida (
~MASK):...1111 1011 - Operação (
flags & ~MASK):...10110011
O bit alvo foi zerado, os outros foram preservados pelos 1s da máscara.
flags = flags & (~MASK)Alternar um Bit (Toggle) #
Queremos inverter o estado atual: se estava ligado, desliga; se estava desligado, liga.
Usamos o XOR (^), pois XOR com 1 sempre inverte o bit.
flags = flags ^ MASKExemplo Real: Sistema de Permissões #
Vamos ver onde isso é usado na prática. Sistemas operacionais (como Linux) usam bits para controlar permissões de arquivos. Vamos simular isso em Python.
Imagine que temos 3 permissões possíveis, representadas por bits:
- Leitura (READ): bit 2 (valor 4)
- Escrita (WRITE): bit 1 (valor 2)
- Execução (EXEC): bit 0 (valor 1)
# Definindo as constantes (MÁSCARAS)
READ = 0b100 # 4
WRITE = 0b010 # 2
EXEC = 0b001 # 1
# Cenário 1: Criando um usuário com permissão de Leitura e Execução
# Usamos OR (|) para combinar as permissões
permissao_usuario = READ | EXEC
print(bin(permissao_usuario)) # Saída: 0b101 (Decimal 5)
# Cenário 2: O usuário pode escrever?
# Usamos AND (&) para verificar
if (permissao_usuario & WRITE):
print("Acesso de escrita liberado")
else:
print("Acesso de escrita negado") # Vai cair aqui
# Cenário 3: Adicionando permissão de escrita dinamicamente
permissao_usuario = permissao_usuario | WRITE
print(bin(permissao_usuario))
# Agora o valor é 0b111 (7) - Permissão total (rwx)
# Cenário 4: Revogando permissão de execução
# Usamos AND com NOT
permissao_usuario = permissao_usuario & (~EXEC)
print(bin(permissao_usuario))
# Agora o valor é 0b110 (6) - Apenas Leitura e EscritaPerceba como conseguimos armazenar 3 informações booleanas complexas em uma única variável inteira minúscula (ocupando apenas 3 bits de memória). É por isso que operações bitwise são vitais para performance e economia de recursos.
Conclusão #
Entrar no mundo Bitwise é como olhar para a Matrix: você para de ver apenas os números superficiais e começa a enxergar a estrutura binária que sustenta tudo.
Embora Python seja uma linguagem de alto nível, entender &, |, ^ e << te
dá superpoderes para lidar com protocolos de rede, processamento de imagens,
criptografia e otimização de algoritmos.
E caso não use Python, esses conceitos são universais e se aplicam a praticamente todas as linguagens de programação.
Agora você sabe que:
- Bitwise não é Lógico:
&não éand. - Máscaras são a chave: Use
ANDpara ler/limpar eORpara escrever. - Bits são versáteis: Um único inteiro pode guardar dezenas de “interruptores” independentes.
Pronto para praticar? Tente criar um código que criptografa uma string usando XOR e depois descriptografa usando a mesma chave!
Com isso, encerramos o capítulo dos números inteiros nesta série. Ao longo
dos últimos artigos, vimos como representar, converter, operar e manipular
inteiros em diferentes bases — inclusive na estrutura de bits bruta. Agora é
hora de dar o próximo salto: como o computador lida com números
fracionários? Por que 0.1 + 0.2 não é exatamente 0.3 em Python? A
resposta começa com algo que você já domina — conversão de bases —, mas aplicada
agora à parte depois da vírgula.
Até a próxima!