Operações Bit a Bit - JavaScript
Representação Binária dos Números
No nível do computador, todos os dados são representados como um conjunto de bits. Cada bit pode ter dois valores: 1 (há sinal) e 0 (não há sinal). E todos os dados são de fato um conjunto de zeros e uns. 8 bits compõem 1 byte. Esse sistema é chamado de sistema binário.
Por exemplo, o número 13 no sistema binário seria 1101₂. Veja como chegamos a isso:
// conversão do número decimal 13 para o sistema binário 13 / 2 = 6 // resto 1 (13 - 6 * 2 = 1) 6 / 2 = 3 // resto 0 (6 - 3 * 2 = 0) 3 / 2 = 1 // resto 1 (3 - 1 * 2 = 1) 1 / 2 = 0 // resto 1 (1 - 0 * 2 = 1)
O algoritmo geral consiste em dividir sequencialmente o número e os resultados da divisão por 2 e obter os restos, até chegar a 0. Em seguida, alinhamos os restos em linha na ordem inversa para assim formar a representação binária do número. Especificamente neste caso, passo a passo:
Dividimos o número 13 por 2. O resultado da divisão é 6, o resto da divisão é 1 (pois 13 - 6 * 2 = 1)
Em seguida, dividimos o resultado da divisão anterior - o número 6 por 2. O resultado da divisão é 3, o resto da divisão é 0
Dividimos o resultado da divisão anterior - o número 3 por 2. O resultado da divisão é 1, o resto da divisão é 1
Dividimos o resultado da divisão anterior - o número 1 por 2. O resultado da divisão é 0, o resto da divisão é 1
O último resultado da divisão é 0, então concluímos o processo e alinhamos os restos das divisões, começando pelo último - 1101
Para converter de volta do sistema binário para o decimal, multiplicamos o valor de cada bit (1 ou 0) pelo número 2 elevado ao expoente igual ao número do bit (a numeração dos bits começa do zero):
// conversão do número binário 1101 para o sistema decimal 1(3º bit) 1(2º bit) 0(1º bit) 1(0º bit) 1 * 2³ + 1 * 2² + 0 * 2¹ + 1 * 2⁰ 1 * 8 + 1 * 4 + 0 * 2 + 1 * 1 8 + 4 + 0 + 1
No JavaScript, para definir números no formato binário, um prefixo 0b é usado antes do número:
const num = 0b1101; // 13 no sistema decimal
console.log(num); // 13
Representação de Números Negativos
Para representar números com sinal em JavaScript, usa-se o código complementar a dois (two's complement), no qual o dígito mais significativo é o sinal. Se o seu valor é 0, então o número é positivo, e sua representação binária é a mesma que a de um número sem sinal. Por exemplo, 0000 0001 em decimal é 1.
Se o dígito mais significativo é 1, então estamos lidando com um número negativo. Por exemplo, 1111 1111 em decimal representa -1. Da mesma forma, 1111 0011 representa -13.
Para converter um número positivo em negativo, é necessário inverter seus bits e adicionar um:

Por exemplo, vamos pegar o número -3. Para isso, primeiro pegamos a representação binária do número 3:
3₁₀ = 0000 0011₂
Invertemos os bits:
~0000 0011 = 1111 1100
E adicionamos 1:
1111 1100 + 1 = 1111 1101
Assim, o número 1111 1101 é a representação binária do número -3.
Vamos considerar como será a soma de um número com sinal e sem sinal. Por exemplo, somemos 12 e -8:
12₁₀ = 00001100₂ + -8₁₀ = 11111000₂ (8 = 00001000, após inversão = 11110111, após +1 = 11111000) = 4₁₀ = 00000100₂
No sistema binário o resultado é 00000100₂ ou 4₁₀ no sistema decimal.
Podemos ver isso na prática:
let num = 0b1100; // 12 em decimal
num = ~num; // inversão dos bits
num = num + 1;
console.log(num); // -12
Operações de Deslocamento (shift)
Cada número inteiro na memória é representado por uma quantidade específica de bits. As operações de deslocamento permitem mover a representação binária do número vários bits para a direita ou para a esquerda. Essas operações são aplicadas apenas a operandos inteiros. Existem duas operações:
<< (deslocamento à esquerda)
Desloca a representação binária do número, representado pelo primeiro operando, para a esquerda por um número específico de bits, que é determinado pelo segundo operando.
const res = 2 << 2; // 10 deslocado dois bits para a esquerda = 1000 (8 no sistema decimal) console.log(res); // 8
O número 2 em representação binária é 0010₂. Se deslocarmos o número 0010 dois bits para a esquerda, resultará em 1000, que no sistema decimal é igual a 8.
>> (deslocamento aritmético à direita)
Desloca a representação binária do número para a direita por um número específico de bits.
const res = 16 >> 3; // 10000 deslocado três bits para a direita = 10 (2 no sistema decimal) console.log(res); // 2
O número 16 em representação binária é 10000₂. Se deslocarmos o número 10000 três bits para a direita (os três últimos bits são descartados), resultará em 00010, que no sistema decimal representa o número 2.
É importante notar que isso é chamado de deslocamento aritmético, no qual os bits deslocados na parte esquerda são preenchidos com o bit de sinal - 0 para números positivos e 1 para negativos. Assim, ao deslocar números negativos, não há risco de eles se tornarem positivos. Por exemplo:
Assim, ao deslocar -16 três bits para a direita, obtemos -2, o que é bastante natural.const res = -16 >> 3; // 11111110000 deslocado três bits para a direita = 1111111111111110 console.log(res); // -2
>>> (deslocamento lógico à direita)
Desloca a representação binária do número para a direita por um número específico de bits, mas ao contrário da operação >>, preenche os bits deslocados na parte esquerda com zeros, resultando em um deslocamento sem sinal. Assim, mesmo que desloquemos um número negativo, o resultado será sempre positivo:
const res = -16 >>> 3; console.log(res); // 536870910
Pode-se notar que um deslocamento de um bit para a esquerda é efetivamente equivalente à multiplicação por 2, enquanto que um deslocamento para a direita por um bit é equivalente à divisão por dois. Podemos generalizar: deslocar à esquerda por n é equivalente a multiplicar o número por 2^n, e deslocar à direita por n bits é equivalente a dividir por 2^n, o que pode ser usado em lugar de multiplicar/dividir por potências de dois:
const res1 = 8 << 2; // equivalente a 8 * 4 console.log(res1); // 32 const res2 = 64 >> 4; // equivalente a 64 / 16 console.log(res2); // 4
Operações Bit-a-Bit (Bitwise Operations)
As operações bit a bit também são realizadas apenas sobre os bits correspondentes dos números:
&: conjunção bit a bit (operação E ou multiplicação bit a bit). Retorna 1 se ambos os bits correspondentes de ambos os números forem 1
|:disjunção bit a bit (operação OU ou adição bit a bit). Retorna 1 se pelo menos um dos bits correspondentes de ambos os números for 1
^:OU exclusivo bit a bit. Retorna 1 se apenas um dos bits correspondentes de ambos os números for 1
~:negação bit a bit ou inversão. Inverte todos os bits do operando. Se um bit é 1, ele se torna 0, e se é 0, ele se torna 1.
Aplicação das operações:
const a = 5 | 2; // 101 | 010 = 111 - 7
const b = 6 & 2; // 110 & 010 = 010 - 2
const c = 5 ^ 2; // 101 ^ 010 = 111 - 7
const d = ~9; // -10
Por exemplo, a expressão 5 | 2 é igual a 7. O número 5 em notação binária é 101, e o número 2 é 10 ou 010. Somamos os bits correspondentes de ambos os números. Na soma, se pelo menos um bit é 1, então o resultado dos bits correspondentes é 1. Portanto, obtemos:
1 0 1 0 1 0 1 1 1
O resultado é 111, que em notação decimal representa o número 7.
Vejamos outro exemplo com a expressão 6 & 2. O número 6 em notação binária é 110, e o número 2 é 10 ou 010. Multiplicamos os bits correspondentes de ambos os números. O produto dos bits correspondentes é 1 se ambos os bits são 1. Caso contrário, o produto é 0. Portanto, obtemos:
1 1 0 0 1 0 0 1 0
O resultado é 010, que em notação decimal é igual a 2.