Introducción: el problema que explota Stack Overflow
\n\nImagina: estás escribiendo código que procesa miles de números por segundo. Necesitas filtrar aquellos cuya raíz cuadrada es un número entero (1, 4, 9, 16…). Parece sencillo, ¿verdad? Tomas Math.sqrt(), verificas el residuo y listo. Pero no todo es tan simple.
La pregunta «Fastest way to determine if an integer's square root is an integer» en Stack Overflow ya ha acumulado más de 100 000 visitas. ¿Por qué? Porque Math.sqrt() es una operación de punto flotante, lo que la hace lenta y propensa a errores de redondeo. Para números grandes (por ejemplo, 2^50), el double puede simplemente «mentir».
En este artículo, analizaremos 5 enfoques en Java: desde el más ingenuo hasta algoritmos rápidos que utilizan operaciones de bits y aritmética de enteros. Descubrirás qué método es realmente más rápido y cuál es más seguro. ¡Allá vamos!
\n\nPlanteamiento del problema
\n\nNecesitamos escribir un método boolean isPerfectSquare(long n) que devuelva true si n es un cuadrado perfecto. Ejemplos:
- \n
isPerfectSquare(25)→true(5*5) \n isPerfectSquare(26)→false\n isPerfectSquare(0)→true(0*0) \n isPerfectSquare(1)→true\n
Probaremos con long, ya que int es un caso particular y long ofrece más margen para errores.
Método 1: Ingenuo — Math.sqrt + verificación del residuo
\n\nLa forma más simple que se le ocurre a un principiante:
\n\npublic static boolean isPerfectSquareSqrt(long n) {\n if (n < 0) return false;\n double sqrt = Math.sqrt(n);\n long floorSqrt = (long) sqrt;\n return floorSqrt * floorSqrt == n;\n}\n\n\nProblema n.º 1: Precisión. Para números mayores a ~2^53 (9 007 199 254 740 991), el double deja de representar enteros con exactitud. Ejemplo: n = 999999999999999999L — Math.sqrt devolverá un resultado incorrecto.
Problema n.º 2: Velocidad. Math.sqrt es una instrucción de CPU (FSQRT), pero sigue siendo más lenta que operaciones aritméticas simples.
Cuándo usarlo: Solo para int o long pequeños (< 10^12), donde la velocidad no sea crítica.
Método 2: Raíz cuadrada entera mediante Newton-Raphson
\n\nEl método de Newton (Newton-Raphson) es una forma clásica de calcular la raíz cuadrada sin números de punto flotante. Refinamos la aproximación de forma iterativa:
\n\npublic static boolean isPerfectSquareNewton(long n) {\n if (n < 0) return false;\n if (n < 2) return true;\n long x = n;\n long y = (x + 1) / 2;\n while (y < x) {\n x = y;\n y = (y + n / y) / 2;\n }\n return x * x == n;\n}\n\n\nEl algoritmo converge muy rápido — en O(log log n) iteraciones. Para números hasta Long.MAX_VALUE, solo se necesitan 6-8 iteraciones.
\n\nVentajas: Precisión absoluta para todos los long (hasta 2^63-1). No usa double.
\n\nDesventajas: La división es una operación costosa. Para números muy pequeños (n < 4), se puede agregar una verificación al inicio.
\n\nMétodo 3: Búsqueda binaria de la raíz
\n\nOtro algoritmo clásico: buscamos la raíz mediante búsqueda binaria en el intervalo [0, n]. Para long, el valor máximo de la raíz es 3 037 000 499 (sqrt(Long.MAX_VALUE)).
\n\npublic static boolean isPerfectSquareBinary(long n) {\n if (n < 0) return false;\n if (n < 2) return true;\n long low = 1;\n long high = 3037000499L; // sqrt(Long.MAX_VALUE)\n while (low <= high) {\n long mid = (low + high) >>> 1;\n long square = mid * mid;\n if (square == n) return true;\n else if (square < n) low = mid + 1;\n e