Operações bit a bit: guia de AND, OR, XOR e máscaras
Você abre uma migração antiga do PostgreSQL e se depara com permissions & 0b100. Um colega sobe um sistema de feature flags que empacota 32 booleanos em um único inteiro. Um cálculo de sub-rede no Kubernetes devolve 192.168.1.0/24 e você precisa extrair o endereço de rede no código. Três situações, uma habilidade comum por baixo: operações bit a bit.
A maioria dos devs de camada de aplicação nunca precisa mexer em & ou ^ numa webapp, até que precisa. Este guia percorre os seis operadores bit a bit, o complemento de dois, os nove padrões que vale a pena memorizar e as pegadinhas específicas de cada linguagem (em particular JavaScript). O código vem em JS, Python, Go e C, e todo exemplo é executável.
Abra nosso Conversor de base em outra aba: várias seções convidam você a digitar um número e ver o padrão binário mudar.
Por que operações bit a bit ainda importam em 2026
Linguagens de alto nível não tornaram operações bit a bit obsoletas. Só esconderam onde elas acontecem. Alguns lugares em que você depende delas hoje, sabendo ou não:
- Row-Level Security do PostgreSQL guarda privilégios ACL (
SELECT,INSERT,UPDATE,DELETE…) num bitmap inteiro. - Capabilities do Linux substituem o velho modelo root-ou-nada por mais de 40 bits de permissão combinados com
|. - Cabeçalhos de algoritmo JWT codificam o hash num campo pequeno, onde comparação a nível de bit é comum na camada de biblioteca.
- Snowflake, ULID e UUIDv7 empacotam timestamp, ID de máquina e sequência num inteiro de 64 ou 128 bits via deslocamento à esquerda.
BITCOUNTeBITOPdo Redis expõem primitivas bit a bit direto ao código de aplicação, para estimativa de cardinalidade e bucketing A/B.- Processamento de imagem lê pixels RGBA de 32 bits e extrai canais com
&e>>.
No nível de instrução de CPU, essas operações continuam custando O(1). Empacotar 32 booleanos num inteiro economiza 31 bytes de memória e, talvez mais importante, permite responder “algum desses 32 flags está ligado?” com um único teste != 0.
Fundamentos binários que você precisa primeiro
Este guia assume que você já entende binário. Se precisar de revisão, leia antes nosso Guia de conversão entre bases numéricas e volte.
Um glossário rápido:
- Um bit é 0 ou 1.
- Um nibble são 4 bits (um dígito hexadecimal).
- Um byte são 8 bits.
- Uma palavra costuma ter 32 ou 64 bits, dependendo da CPU.
Inteiros na maioria das linguagens têm largura fixa: 8, 16, 32, 64. A largura importa muito em bit a bit, porque deslocamentos podem empurrar bits para fora da borda, e o bit de sinal fica na posição mais à esquerda em inteiros sinalizados.
Experimente agora: abra o Conversor de base, digite 170 em decimal e veja a saída binária. Ela deve ser 10101010, um padrão alternado ao qual voltaremos algumas vezes.
Os seis operadores bit a bit
Toda linguagem mainstream oferece os mesmos seis operadores, com pequenas diferenças sintáticas. &, |, ^, ~, << e >> funcionam igual em JavaScript, Python, Go, Rust, C, C++, Java e C#. JavaScript acrescenta um: >>>, o deslocamento à direita sem sinal.
AND (&): filtro de bits
O bit resultante vale 1 só se ambos os bits de entrada valem 1.
| A | B | A & B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
Pense em AND como uma porta: só os bits ligados em ambos os operandos sobrevivem. O uso mais comum é mascarar, ou seja, manter alguns bits e zerar outros.
// Extrair os 4 bits baixos (nibble à direita)
const value = 0b11010110; // 214
const low4 = value & 0x0F; // 0b00000110 = 6
// Checar paridade
const isOdd = (n) => (n & 1) === 1;
isOdd(7); // true
isOdd(42); // false
# Igual em Python
value = 0b11010110
low4 = value & 0x0F # 6
def is_odd(n):
return (n & 1) == 1
OR (|): ativador de bits
O bit resultante vale 1 se pelo menos um dos bits de entrada vale 1.
| A | B | A | B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
OR combina flags. Com READ = 1, WRITE = 2 e EXECUTE = 4, READ | WRITE dá 3: as duas permissões ligadas.
const READ = 0b001;
const WRITE = 0b010;
const EXEC = 0b100;
const rw = READ | WRITE; // 0b011 = 3
READ, WRITE, EXEC = 0b001, 0b010, 0b100
rw = READ | WRITE # 3
XOR (^): chave de bit
O bit resultante vale 1 quando os bits de entrada diferem.
| A | B | A ^ B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
XOR tem três propriedades algébricas que sustentam alguns dos truques mais engenhosos da ciência da computação:
a ^ a = 0: qualquer valor XOR com ele mesmo se cancela.a ^ 0 = a: XOR com zero é a identidade.a ^ b ^ a = b: XOR é o próprio inverso.
A última propriedade explica por que XOR aparece em toda parte, de checagens de paridade a cifras de fluxo, passando pela clássica pergunta de entrevista “ache o número único num array onde todos os outros aparecem duas vezes”.
// Achar o único num array onde os outros aparecem duas vezes
const findUnique = (arr) => arr.reduce((a, b) => a ^ b, 0);
findUnique([4, 1, 2, 1, 2]); // 4
from functools import reduce
from operator import xor
find_unique = lambda arr: reduce(xor, arr, 0)
find_unique([4, 1, 2, 1, 2]) # 4
NOT (~): inversor de bits
O ~ unário inverte cada bit: 0 vira 1, 1 vira 0.
~0b00001111 // -16 (JavaScript força 32 bits com sinal)
~5 // -6
~5 # -6
// Go usa ^ como NOT unário; atenção ao símbolo
var x int8 = 5
fmt.Println(^x) // -6
~5 = -6 surpreende iniciantes em quase toda linguagem. O motivo é o complemento de dois, que vem na próxima seção. Por enquanto, fica a regra: em qualquer linguagem que use complemento de dois para negativos (ou seja, todas), ~x equivale a -(x + 1).
Deslocamento à esquerda (<<): multiplicador por potência de 2
x << n desloca todos os bits de x n posições para a esquerda, preenchendo zeros à direita. Matematicamente, multiplica por 2ⁿ.
1 << 0 // 1 (2^0)
1 << 1 // 2 (2^1)
1 << 3 // 8 (2^3)
1 << 10 // 1024 (2^10 = 1 KiB)
// Montar flags
const FLAG_ADMIN = 1 << 0;
const FLAG_EDITOR = 1 << 1;
const FLAG_REVIEWER = 1 << 2;
A graça de 1 << n é produzir um número com um único bit ligado na posição n. Esse bit vira um flag.
Cuidado com overflow. Em JavaScript, 1 << 31 dá -2147483648 (e não 2147483648), porque as operações bit a bit em JS trabalham com inteiros de 32 bits com sinal.
Deslocamento à direita (>> vs >>>): com sinal ou preenchido?
Deslocamento à direita move bits para a direita. A pergunta é: o que preenche as posições que ficam livres à esquerda?
>>(deslocamento aritmético) preserva o bit de sinal. Negativos continuam negativos.>>>(deslocamento lógico, ou sem sinal) preenche com zeros. Só JavaScript tem esse operador dedicado.
-8 >> 1 // -4 (sinal preservado)
-8 >>> 1 // 2147483644 (bit de sinal tratado como dado)
8 >> 1 // 4
8 >> 2 // 2
Em C, se >> sobre tipos com sinal é aritmético ou lógico depende da implementação. A maioria dos compiladores faz aritmético, mas não confie sem verificar. Go exige que a quantidade de deslocamento seja inteiro sem sinal e distingue claramente tipos com e sem sinal. Python não tem >>> porque não tem largura fixa.
Complemento de dois: como computadores guardam negativos
Se bits são só 0 e 1, como codificar -5? A resposta em que o mundo convergiu nos anos 60 é o complemento de dois, e toda CPU moderna usa.
A abordagem ingênua, reservar um bit para o sinal, tem dois problemas. Primeiro, você fica com +0 e -0, o que é chato. Segundo, circuitos de soma e subtração precisariam checar o bit de sinal, complicando o hardware. Complemento de dois resolve os dois.
A regra é curta:
- Escreva a representação binária positiva.
- Inverta cada bit (esse é o “complemento de um”).
- Some 1.
Exemplo detalhado, codificando -5 em complemento de dois de 8 bits:
5 em binário: 0000 0101
inverter bits: 1111 1010 (isso é -6 em complemento de dois!)
somar 1: 1111 1011 ← aqui está -5
Verifique com nosso conversor: insira 251 (decimal) no Conversor de base com base 10, e a saída binária será 11111011. Em contexto de 8 bits com sinal, 11111011 é -5. Em 8 bits sem sinal, o mesmo padrão é 251. Os bits são idênticos; só a interpretação muda.
Isso também explica a surpresa de ~5 = -6. O NOT bit a bit produz o complemento de um, e o complemento de dois é o complemento de um mais 1. Daí a equivalência:
~x = -(x + 1) // vale em qualquer linguagem com complemento de dois
~5 = -6
~(-3) = 2
Para inteiros com sinal de n bits, o intervalo representável vai de -2ⁿ⁻¹ até 2ⁿ⁻¹ − 1. 8 bits com sinal cobrem -128 a 127. 32 bits com sinal cobrem algo em torno de -2,1 bilhões a +2,1 bilhões.
Nove padrões úteis de manipulação de bits
Esses nove padrões cobrem uns 95% da manipulação de bits que você vai escrever no dia a dia. Depois de memorizá-los, você passa a reconhecê-los em código de sistema.
Ligar um bit: x | (1 << n)
Acende o bit n e mantém o resto.
let flags = 0b0100;
flags = flags | (1 << 0); // 0b0101
Apagar um bit: x & ~(1 << n)
Desliga o bit n e mantém o resto. ~(1 << n) é uma máscara com todos os bits ligados exceto n.
let flags = 0b0111;
flags = flags & ~(1 << 1); // 0b0101
Alternar um bit: x ^ (1 << n)
Inverte o bit n, qualquer que seja o estado atual.
let flags = 0b0100;
flags = flags ^ (1 << 2); // 0b0000
flags = flags ^ (1 << 2); // 0b0100 de novo
Checar um bit: (x >> n) & 1
Retorna 1 se o bit n está ligado, 0 caso contrário. Forma equivalente: (x & (1 << n)) !== 0.
const flags = 0b0101;
const isBit2Set = (flags >> 2) & 1; // 1
Isolar o bit ligado mais baixo: x & -x
Produz um valor que mantém só o 1 mais à direita de x. Funciona porque, em complemento de dois, -x equivale a ~x + 1, que inverte todos os bits até o mais baixo ligado, inclusive.
const x = 0b10110100;
const lowest = x & -x; // 0b00000100 = 4
É o truque central dentro das árvores de Fenwick (BIT) para somas de prefixo em O(log n).
Contar bits ligados (popcount)
Conta quantos 1 há num inteiro. A maioria das linguagens tem uma função nativa:
// JavaScript (versão manual)
const popcount = (n) => {
let count = 0;
while (n) { count += n & 1; n >>>= 1; }
return count;
};
popcount(0b10110100); // 4
# Python 3.10+
(0b10110100).bit_count() # 4
// Go
import "math/bits"
bits.OnesCount(0b10110100) // 4
Troca XOR sem variável temporária
Truque clássico: trocar dois inteiros sem variável auxiliar. Nunca use em produção (é mais lento que uma variável temporária e quebra se a e b apontam para a mesma memória), mas vale entender.
let a = 5, b = 9;
a = a ^ b; // a = 5 ^ 9
b = a ^ b; // b = (5 ^ 9) ^ 9 = 5
a = a ^ b; // a = (5 ^ 9) ^ 5 = 9
// a = 9, b = 5
Detectar potência de 2: (x & (x - 1)) === 0
Potência de 2 tem exatamente um bit ligado. Subtrair 1 apaga esse bit e acende todos os abaixo dele. O AND dá zero só para potências de 2 (e para 0, daí a guarda x > 0).
const isPow2 = (x) => x > 0 && (x & (x - 1)) === 0;
isPow2(16); // true
isPow2(17); // false
Checagem rápida de ímpar: x & 1
Mais rápida que x % 2 em algumas linguagens e equivalente, após otimização, em outras. Vale em laços quentes ou quando legibilidade não é prioridade.
const isOdd = (x) => (x & 1) === 1;
Flags com máscara de bits em código real
Aqui operações bit a bit deixam o terreno do truque e viram prática.
32 booleanos como feature flag
Em vez de uma struct com 32 campos booleanos, empacote tudo num inteiro:
const FLAGS = {
DARK_MODE: 1 << 0,
NEW_NAV: 1 << 1,
AI_SUGGESTIONS: 1 << 2,
BETA_EDITOR: 1 << 3,
// ... até 1 << 31
};
let userFlags = 0;
userFlags |= FLAGS.DARK_MODE | FLAGS.AI_SUGGESTIONS; // ativar
if (userFlags & FLAGS.AI_SUGGESTIONS) {
showSuggestions();
}
userFlags &= ~FLAGS.DARK_MODE; // desativar
Armazena 32 booleanos em 4 bytes e permite consultar qualquer subconjunto num único AND. Bancos adoram esse padrão: uma coluna no lugar de 32.
Permissões de arquivos Unix
chmod 755 é bit a bit. Os três dígitos octais correspondem a três ternos de bits:
7 = 111 (dono: rwx)
5 = 101 (grupo: r-x)
5 = 101 (outros: r-x)
Experimente: no Conversor de base, fixe a origem em octal, digite 755 e veja a saída binária 111101101. É literalmente como o sistema de arquivos guarda o campo de permissões.
Para acrescentar só “escrita de grupo”:
const perms = 0o755;
const withGroupWrite = perms | 0o020; // 0o775
Máscara de sub-rede IP
Dado 192.168.1.10/24, o endereço de rede sai de um AND com a máscara:
const ip = 0xC0A8010A; // 192.168.1.10
const mask = 0xFFFFFF00; // 255.255.255.0 (/24)
const network = ip & mask; // 0xC0A80100 = 192.168.1.0
IDs empacotados: Snowflake
O Snowflake do Twitter empacota timestamp, ID de máquina e sequência dentro de um inteiro de 64 bits:
┌─ 1 bit ─┬─── 41 bits ───┬─ 10 bits ─┬─ 12 bits ─┐
│ sinal │ timestamp │ máquina │ seq │
└─────────┴───────────────┴───────────┴───────────┘
Codificar um ID se resume a dois deslocamentos e dois OR:
const id = (BigInt(timestamp) << 22n) |
(BigInt(machineId) << 12n) |
BigInt(sequence);
Decodificar faz o inverso: deslocar à direita e mascarar. Para escolher entre Snowflake, ULID e UUIDv7, veja nosso comparativo de IDs distribuídos.
Pegadinhas específicas de linguagem
JavaScript: a armadilha da coerção de 32 bits
JavaScript converte operandos para inteiros de 32 bits com sinal antes de cada operação bit a bit, e o resultado volta para Number. Qualquer valor acima de 2³¹ − 1 = 2147483647 estoura:
2147483647 | 0 // 2147483647 (ainda ok)
2147483648 | 0 // -2147483648 (estourou!)
4294967295 | 0 // -1 (todos os bits 1, interpretado como sinalizado)
Para trabalho em 64 bits, use BigInt: tem operadores bit a bit independentes, sem limite de largura.
(2n ** 40n) | 1n // 1099511627777n
Bugs de precedência de operadores
Um dos bugs bit a bit mais frequentes na vida real:
// Bug: lido como (x & (1 == 0)) porque == liga mais forte que &
if (x & 1 == 0) { /* ... */ }
// Correto: parênteses
if ((x & 1) == 0) { /* ... */ }
Em C, JavaScript, Python, Go e seus descendentes, operadores de comparação ligam mais forte que AND/OR/XOR bit a bit. Na dúvida, ponha parênteses.
Tabela comparativa
| Linguagem | Coerção de largura | >> negativo | Suporte a BigInt |
|---|---|---|---|
| JavaScript | Força 32 bits com sinal; >>> sem sinal | aritmético | BigInt tem operadores próprios |
| Python | Precisão arbitrária, sem largura fixa | aritmético | Nativo |
| Go | Estrito; deslocamento precisa ser sem sinal | aritmético para tipos com sinal | math/big |
| C/C++ | Segue o tipo; int, unsigned, etc. | definido pela implementação em tipos com sinal | Nada embutido |
| Rust | Estrito; panic em overflow no debug | aritmético para tipos com sinal | u128 / crates externos |
A reviravolta de largura infinita do Python
Inteiros em Python não têm largura fixa, então a lógica do complemento de dois se estende “infinitamente” à esquerda. Por isso ~5 dá -6, e não 250 ou 65530: Python trata o resultado como inteiro negativo, não como padrão de bits de largura fixa. Se quiser semântica de wrap-around, mascare explicitamente:
# Simular NOT de 8 bits
(~5) & 0xFF # 250
Realidade de performance em 2026
A lenda comum diz que operações bit a bit são “sempre mais rápidas”. Em 2026, isso é meia verdade.
Compiladores já fazem as reescritas óbvias. Otimizadores modernos transformam x * 2 em x << 1 automaticamente. Escrever x << 1 em código de aplicação “por velocidade” é cargo cult: não ajuda e prejudica legibilidade.
Onde código bit a bit realmente ganha:
- Laços quentes numéricos: popcount, contagem de zeros inicial e final, engines de xadrez com bitboards.
- Estruturas compactas: filtros de Bloom, roaring bitmaps, árvores de Fenwick.
- Registradores de hardware e I/O mapeado em memória: embarcado, kernel, firmware.
- Primitivas criptográficas: AES, ChaCha20, SHA, todas construídas com XOR, rotações e deslocamentos.
- Compressão e descompressão: Huffman, RLE, inteiros empacotados.
- Engines de banco de dados: índices bitmap e formatos colunares como Parquet com dicionário.
Onde não ajuda: trocar x % 2 por x & 1 numa função de regra de negócio rodada duas vezes por requisição. O ganho é indetectável; o custo em legibilidade é real.
O único caso em que manipulação de bits sempre ganha é pegada de memória. Empacotar 32 flags num int economiza 31 bytes em relação a 32 booleanos. Em escala (milhões de registros, bilhões de eventos), é a diferença entre um layout cache-friendly e uma tempestade de cache miss.
Referência rápida
| Operação | Operador | Exemplo | Resultado | Uso típico |
|---|---|---|---|---|
| AND | & | 0b1100 & 0b1010 | 0b1000 | Máscara/extrair |
| OR | | | 0b1100 | 0b1010 | 0b1110 | Combinar flags |
| XOR | ^ | 0b1100 ^ 0b1010 | 0b0110 | Alternar/diff |
| NOT | ~ | ~0b1100 | ...11110011 | Máscara inversa |
| Deslocamento esq. | << | 1 << 3 | 8 | Multiplicar por 2ⁿ |
| Deslocamento dir. | >> | 16 >> 2 | 4 | Dividir por 2ⁿ (com sinal) |
| Desloc. dir. sem sinal (JS) | >>> | -1 >>> 0 | 4294967295 | Tratar como sem sinal |
Ligar bit n | | | x | (1 << n) | Ligar | |
Apagar bit n | & ~ | x & ~(1 << n) | Desligar | |
Alternar bit n | ^ | x ^ (1 << n) | Inverter | |
Testar bit n | & | (x >> n) & 1 | 0 ou 1 | Checar |
| Bit baixo isolado | & - | x & -x | Isolar | |
| Potência de 2 | & | x > 0 && (x & (x-1)) == 0 | booleano | Detectar |
FAQ
Qual a diferença entre AND lógico (&&) e bit a bit (&)?
O AND lógico opera sobre valores booleanos inteiros e faz curto-circuito: false && expr nunca avalia expr. O AND bit a bit opera sobre bits individuais de inteiros e sempre avalia os dois lados. Use && em condições e & em manipulação de bits.
Por que ~1 é -2 na maioria das linguagens?
O NOT bit a bit inverte cada bit, produzindo o complemento de um. Em representação de complemento de dois, inverter todos os bits de x dá -(x + 1). Daí ~1 = -2, ~0 = -1 e ~(-1) = 0.
x << 1 é mesmo mais rápido que x * 2?
Na prática, não. Todo compilador moderno reconhece x * 2 e emite a mesma instrução de deslocamento. Use x * 2 por clareza e reserve << para quando estiver de fato pensando em bits, como ao montar uma máscara.
JavaScript suporta operações bit a bit de 64 bits?
Com os operadores padrão &, |, ^, << e >>, não: eles forçam operandos para 32 bits com sinal. Use literais BigInt (por exemplo, 1n << 40n) para operações bit a bit de precisão arbitrária.
Como contar bits ligados com eficiência?
Use o built-in da sua linguagem: bits.OnesCount em Go, Integer.bitCount em Java, .bit_count() em Python 3.10+ e intrinsics popcount em C/C++. Todos mapeiam para uma única instrução POPCNT de CPU em x86 e ARM modernos.
Quando usar flags com máscara em vez de struct de booleanos?
Use máscara quando precisar guardar muitos flags de forma compacta (bancos, protocolos de rede, formatos de arquivo) ou testar combinações rápido com flags & REQUIRED_MASK. Use struct quando os campos têm tipos diferentes ou quando legibilidade vale mais que memória.
O que acontece se eu deslocar além da largura?
Em C/C++, é comportamento indefinido. Em JavaScript, a quantidade de deslocamento é tomada módulo 32, então 1 << 32 dá 1, não 0. Em Python não há largura, e 1 << 100 é apenas um inteiro maior. Nunca confie no comportamento de over-shift; mascare a quantidade você mesmo se precisar.
Por que ~5 em Python dá -6 e não 2?
Inteiros em Python não têm largura fixa, então o complemento de dois se estende conceitualmente ao infinito. ~5 vale -(5 + 1) = -6, como em qualquer linguagem de complemento de dois. Se você quer o valor “invertido” de 8 bits 250, mascare: (~5) & 0xFF.
Cifragem XOR é segura?
Um one-time pad com chave verdadeiramente aleatória do tamanho da mensagem é inquebrável por teoria da informação. Já reutilizar a mesma chave entre mensagens é catastroficamente inseguro: a “cifra XOR” com chave curta repetida cai trivialmente. Cifras reais como AES e ChaCha20 usam XOR internamente, mas só como um passo entre muitos.
Como representar um negativo em complemento de dois à mão?
Escreva o valor positivo em binário na largura alvo, inverta cada bit e some 1. Exemplo: -5 em 8 bits = 00000101 → inverter 11111010 → somar 1 → 11111011. Verifique no nosso Conversor de base convertendo 251 (a interpretação sem sinal de 11111011) e conferindo que a saída binária é 11111011.
Ferramentas relacionadas e leituras adicionais
- Conversor de base: digite qualquer número e veja os bits.
- Guia de conversão entre bases numéricas: leitura prévia sobre binário, octal e hexadecimal.
- UUID v4 vs v7 vs ULID vs Snowflake: empacotamento de bits em IDs distribuídos.
- Boas práticas de segurança: bitmaps de permissão e suas armadilhas.