Programación · Algoritmos
Manipulación de bits explicada: operadores, trucos y ejemplos
Respuesta corta
La manipulación de bits consiste en trabajar directamente con los dígitos binarios (bits) de los enteros mediante operadores bit a bit: AND (&), OR (|), XOR (^), NOT (~) y los desplazamientos << y >>. Con ellos puedes comprobar, activar, borrar e invertir bits individuales, guardar un conjunto de números pequeños en un solo entero (una máscara de bits) y resolver problemas como contar los bits activos o encontrar el único número sin pareja usando espacio extra constante. Cada operación es una única instrucción rápida de la CPU para enteros de tamaño fijo.
Qué problema resuelve la manipulación de bits
Un entero es una fila de bits, y muchos problemas tratan en realidad de esos bits: permisos de archivos como rwx, banderas de funcionalidades, máscaras de red, colores de píxeles, conjuntos compactos de opciones, sumas de verificación y hashes. Los operadores bit a bit permiten leer y cambiar muchos bits a la vez en una sola operación, lo que es compacto en memoria y muy rápido.
Las preguntas de entrevista también usan trucos con bits porque dan soluciones elegantes con espacio O(1), por ejemplo encontrar el único número que aparece una vez cuando todos los demás aparecen dos veces.
Los operadores
Los ejemplos usan a = 12 (binario 1100) y b = 10 (binario 1010).
| Operador | Nombre | Regla por bit | Ejemplo | Resultado |
|---|---|---|---|---|
a & b | AND | 1 si ambos bits son 1 | 1100 & 1010 | 1000 = 8 |
a | b | OR | 1 si alguno de los bits es 1 | 1100 | 1010 | 1110 = 14 |
a ^ b | XOR | 1 si los bits son distintos | 1100 ^ 1010 | 0110 = 6 |
~a | NOT | invierte cada bit | ~12 | -13 (complemento a dos) |
a << n | desplazamiento a la izquierda | mueve los bits a la izquierda y rellena con 0 | 12 << 1 | 24 (multiplicar por 2ⁿ) |
a >> n | desplazamiento a la derecha | mueve los bits a la derecha | 12 >> 2 | 3 (división entera entre 2ⁿ) |
a, b = 12, 10
print(bin(a), bin(b)) # 0b1100 0b1010
print(a & b, a | b, a ^ b) # 8 14 6
print(~a, a << 1, a >> 2) # -13 24 3
print(f"{a ^ b:04b}") # 0110
print(int("1010", 2)) # 10Operaciones con un solo bit
Las posiciones de los bits se cuentan desde 0 a la derecha (el bit menos significativo). La máscara 1 << i tiene activado solo el bit i.
| Tarea | Expresión |
|---|---|
| Comprobar el bit i | (x >> i) & 1 o x & (1 << i) != 0 |
| Activar el bit i | x | (1 << i) |
| Borrar el bit i | x & ~(1 << i) |
| Invertir el bit i | x ^ (1 << i) |
| Solo el bit activo más bajo | x & -x |
| Borrar el bit activo más bajo | x & (x - 1) |
| ¿Es x una potencia de dos? (x > 0) | x & (x - 1) == 0 |
Ejemplo resuelto: contar bits activos
El método de Brian Kernighan borra el bit activo más bajo hasta que no queda ninguno, por lo que itera una vez por cada bit en 1, no una vez por cada posición de bit. Traza para x = 44 (binario 101100):
| Paso | x (binario) | x - 1 (binario) | x & (x - 1) | Contador |
|---|---|---|---|---|
| 1 | 101100 | 101011 | 101000 | 1 |
| 2 | 101000 | 100111 | 100000 | 2 |
| 3 | 100000 | 011111 | 000000 | 3 |
def count_bits(x):
count = 0
while x:
x &= x - 1 # drop the lowest set bit
count += 1
return count
print(count_bits(44)) # 3
print((44).bit_count()) # 3
print((44).bit_length()) # 6int.bit_count() está disponible desde Python 3.10. int.bit_length() devuelve el número de bits necesarios para escribir el número en binario, sin el signo.
Trucos clásicos en Python
from functools import reduce
from operator import xor
# Every number appears twice except one: pairs cancel out with XOR.
print(reduce(xor, [4, 1, 2, 1, 2])) # 4
# Swap two integers without a temporary variable (for illustration only).
x, y = 5, 9
x ^= y; y ^= x; x ^= y
print(x, y) # 9 5
# Bitmask as a set: enumerate all subsets of 3 items.
items = ["a", "b", "c"]
for mask in range(1 << len(items)):
print(mask, [items[i] for i in range(len(items)) if mask >> i & 1])
# 0 []
# 1 ['a']
# 2 ['b']
# 3 ['a', 'b']
# 4 ['c']
# 5 ['a', 'c']
# 6 ['b', 'c']
# 7 ['a', 'b', 'c']La máscara 0 es el conjunto vacío, 5 (binario 101) es ['a', 'c'] y 7 son los tres elementos. La programación dinámica con máscaras de bits usa la misma idea, con una máscara como estado, para problemas de hasta unos 20 elementos, como el problema del viajante en entradas pequeñas.
Dos clásicos más: x & 1 vale 1 para los números impares, y hacer XOR de todos los números de 0 a n con todos los valores de una lista encuentra el único número que falta.
Complejidad temporal y espacial
| Operación | Tiempo | Notas |
|---|---|---|
&, |, ^, ~, shifts on machine-size integers | O(1) | Una instrucción de CPU en C, Java, Go o Rust. |
Las mismas operaciones sobre int de Python | O(número de bits) | Los enteros de Python tienen tamaño ilimitado; los valores pequeños siguen siendo muy rápidos. |
| Conteo de bits de Kernighan | O(número de bits activos) | int.bit_count() es más rápido en Python 3.10+. |
| Enumerar todos los subconjuntos con máscaras | O(n × 2ⁿ) | 2ⁿ máscaras, n comprobaciones de bit cada una. |
Una máscara de bits usa un bit por cada posible elemento, de modo que un conjunto de 64 banderas cabe en un único entero de 64 bits, frente a 64 variables booleanas o elementos de lista independientes.
Errores comunes
- Números negativos: los enteros de Python se comportan como si tuvieran infinitos bits de signo, así que
~5es-6ybin(-5)es-0b101, no una cadena en complemento a dos. Para ver patrones de 32 bits, aplica una máscara al valor:-5 & 0xFFFFFFFF. - El desplazamiento a la derecha de números negativos en Python es aritmético:
-8 >> 1es-4, y-1 >> 10sigue siendo-1. - Desbordamiento en lenguajes de ancho fijo: en Java o C,
1 << 31en un int de 32 bits se vuelve negativo o indefinido; usa un tipo de 64 bits y1L << 40o1ULL << 40. - Confundir operadores lógicos y bit a bit:
andyor(&&,||en lenguajes de tipo C) devuelven valores de verdad, mientras que&y|operan bit a bit. - Legibilidad: sustituir
x * 2porx << 1no aporta ninguna mejora de velocidad en los compiladores modernos ni en Python, y hace el código más difícil de leer.
Usos en el mundo real
- Permisos de archivos en Unix:
chmod 754establece tres grupos de 3 bits (rwx = 4 + 2 + 1). - Redes: máscaras de subred IPv4, donde
ip & maskda la dirección de red de un bloque CIDR. - Banderas y opciones:
re.IGNORECASE | re.MULTILINEde Python yenum.Flagcombinan opciones con OR. - Gráficos y colores: un valor RGB de 24 bits empaqueta rojo, verde y azul como
(r << 16) | (g << 8) | b. - Estructuras de datos y algoritmos: filtros de Bloom, bitsets, árboles de Fenwick (que usan
i & -i), hashing y sumas de verificación. - Problemas de entrevista: single number, counting bits, potencia de dos, invertir bits, número que falta, subconjuntos mediante máscara de bits, suma de dos enteros sin
+.
Preguntas frecuentes
¿Qué hace x & (x - 1)?
Borra el bit activo más bajo de x. Se usa para contar bits activos y para comprobar si un x positivo es una potencia de dos, lo cual es cierto cuando el resultado es 0.
¿Por qué ~5 es igual a -6 en Python?
Los enteros usan complemento a dos, donde ~x es igual a -x - 1. Python aplica esto a sus enteros de tamaño ilimitado, así que invertir todos los bits de 5 da -6.
¿Cómo cuento los bits en 1 de un número en Python?
Usa x.bit_count() en Python 3.10 o posterior, o bin(x).count("1") en versiones anteriores. Para números negativos, bit_count() cuenta los bits del valor absoluto.
¿Es la manipulación de bits más rápida que la aritmética?
Con enteros de tamaño fijo, las operaciones bit a bit son instrucciones únicas, pero también lo son la suma y la multiplicación por constantes tras la optimización del compilador. El beneficio real es el almacenamiento compacto y los algoritmos de espacio constante, no sustituir la aritmética normal.