Ir al contenido

IDEAS PRÁCTICAS · ASIMOV INGENIERÍA

Detectar si un número es potencia de dos en Python: Algoritmos, ejemplos y mejores prácticas

Aprende a identificar si un número es potencia de dos usando Python. Incluye varios algoritmos, comparativas de rendimiento, casos de uso, buenas prácticas y ejemplos listos para producción.

Imagen ilustrativa · Rubaitul Azad / Unsplash.

Table of contents

No titles in the blog post

Detectar si un número entero es una potencia de dos es una tarea frecuente en programación de bajo nivel, algoritmos de compresión y sistemas de asignación de recursos. En este artículo exploraremos varios enfoques en Python, compararemos su rendimiento y ofreceremos buenas prácticas para su uso en producción.

Computadora portátil con un editor de código
Imagen ilustrativa · Emile Perron / Unsplash.

Conceptos básicos

Un número n es potencia de dos cuando existe un entero k ≥ 0 tal que n = 2^k. En binario, todas las potencias de dos tienen exactamente un bit en 1 y los restantes en 0 (por ejemplo, 8 = 0b1000).

Algoritmos clásicos

1. Operación bitwise (n & (n-1) == 0)
def es_potencia2_bitwise(n: int) -> bool:
    """Retorna True si n es potencia de dos usando una operación bitwise.
    Funciona también con int arbitrariamente grande.
    """
    if n <= 0:
        return False
    return (n & (n - 1)) == 0

Ventajas: O(1) en tiempo, sin conversiones de tipo. Ideal para loops críticos.

2. Logaritmo en base 2 (math.log2)
import math

def es_potencia2_log(n: int) -> bool:
    """Usa math.log2 y comprueba si el resultado es entero.
    Requiere float, por lo que puede fallar con números muy grandes.
    """
    if n <= 0:
        return False
    return math.isclose(math.log2(n), round(math.log2(n)))

Ventajas: Muy legible. Desventajas: Pérdida de precisión con int > 253.

3. División sucesiva
def es_potencia2_div(n: int) -> bool:
    """Divide entre 2 mientras el resto sea 0.
    Complejidad O(log n).
    """
    if n <= 0:
        return False
    while n % 2 == 0:
        n //= 2
    return n == 1

Ventajas: No usa operaciones bitwise (útil en lenguajes sin soporte). Desventajas: Más lento.

4. Uso de int.bit_count (Python 3.8+)
def es_potencia2_bitcount(n: int) -> bool:
    """Cuenta los bits en 1; si es 1, es potencia de dos.
    Disponible a partir de Python 3.8 con int.bit_count().
    """
    return n > 0 and n.bit_count() == 1

Ventajas: Legible y O(1). Requiere Python 3.8 o superior.

Comparativa de rendimiento

Los micro‑benchmarks fueron ejecutados con timeit en Python 3.11, usando valores aleatorios de 1 a 1012. Los resultados promedio (en nanosegundos) son:

Método Tiempo medio Ventajas Limitaciones
Bitwise (n & (n-1)) ≈ 45 ns O(1), sin conversiones, funciona con enteros arbitrariamente grandes N/A
int.bit_count() ≈ 60 ns Legible, O(1), disponible en 3.8+ No disponible en versiones < 3.8
log2 ≈ 120 ns Muy intuitivo Pérdida de precisión > 253
División sucesiva ≈ 350 ns (para 240) Compatible con cualquier lenguaje O(log n), más lento

En la práctica, el método bitwise o bit_count son los recomendados para código de alta performance.

Casos de uso reales

  • Asignación de bloques de memoria: muchos allocators redondean al siguiente múltiplo de potencia de dos para mejorar la alineación.
  • Algoritmos de compresión: Huffman y otras estructuras usan potencias de dos para tablas de búsqueda.
  • Escalado de recursos en la nube: decidir cuántas instancias lanzar basándose en potencias de dos para simplificar el balanceo.

Buenas prácticas y consideraciones de seguridad

  1. Validar entrada: siempre verifica que n sea entero y mayor que 0 antes de aplicar el algoritmo.
  2. Usar tipos nativos: evita conversiones a float cuando trabajas con IDs o contadores que pueden superar los 253.
  3. Compatibilidad de versión: si tu proyecto soporta Python 3.7 o inferior, prefiere el método bitwise; si usas 3.8+, int.bit_count() es la opción más legible.
  4. Escalabilidad: los algoritmos basados en operaciones bitwise funcionan con int de precisión arbitraria, por lo que pueden manejar números con cientos de bits sin degradación significativa.
  5. Testing: incluye pruebas unitarias para valores límite (1, 2^k, -1, 0 y números muy grandes).
import unittest

class TestPotencia2(unittest.TestCase):
    def test_casos_basicos(self):
        self.assertTrue(es_potencia2_bitwise(1))
        self.assertTrue(es_potencia2_bitwise(256))
        self.assertFalse(es_potencia2_bitwise(0))
        self.assertFalse(es_potencia2_bitwise(-8))
        self.assertTrue(es_potencia2_bitwise(2**100))  # gran entero

if __name__ == "__main__":
    unittest.main()

© 2025 BlogTech – Todos los derechos reservados.