La forma más rápida de determinar si la raíz cuadrada de un número entero es un número entero

Estoy buscando la forma más rápida de determinar si un long el valor es un cuadrado perfecto (es decir, su raíz cuadrada es otro número entero):

  1. Lo he hecho de la manera más fácil, utilizando el Math.sqrt() función, pero me pregunto si hay una manera de hacerlo más rápido restringiéndose al dominio de solo enteros.
  2. Mantener una tabla de búsqueda no es práctico (ya que hay aproximadamente 231.5 enteros cuyo cuadrado es menor que 263).

Aquí está la forma muy simple y directa en que lo estoy haciendo ahora:

public final static boolean isPerfectSquare(long n)
{
  if (n < 0)
    return false;

  long tst = (long)(Math.sqrt(n) + 0.5);
  return tst*tst == n;
}

Nota: estoy usando esta función en muchos Proyecto Euler problemas. Por lo tanto, nadie más tendrá que mantener este código. Y este tipo de microoptimización podría marcar la diferencia, ya que parte del desafío es hacer todos los algoritmos en menos de un minuto, y esta función deberá llamarse millones de veces en algunos problemas.


Probé las diferentes soluciones al problema:

  • Después de pruebas exhaustivas, descubrí que agregar 0.5 al resultado de Math.sqrt () no es necesario, al menos no en mi máquina.
  • La raíz cuadrada inversa rápida fue más rápido, pero dio resultados incorrectos para n> = 410881. Sin embargo, como sugiere bobbyshaftoe, podemos usar el truco FISR para n <410881.
  • El método de Newton fue un poco más lento que Math.sqrt(). Probablemente esto se deba a que Math.sqrt() usa algo similar al método de Newton, pero implementado en el hardware, por lo que es mucho más rápido que en Java. Además, el método de Newton todavía requería el uso de dobles.
  • Un método de Newton modificado, que usó algunos trucos para que solo estuvieran involucradas las matemáticas de enteros, requirió algunos trucos para evitar el desbordamiento (quiero que esta función funcione con todos los enteros positivos de 64 bits con signo), y aún era más lento que Math.sqrt().
  • El corte binario fue aún más lento. Esto tiene sentido porque el corte binario requerirá en promedio 16 pasadas para encontrar la raíz cuadrada de un número de 64 bits.
  • Según las pruebas de John, usando or sentencias es más rápido en C ++ que usar un switch, pero en Java y C # no parece haber diferencia entre or y switch.
  • También intenté hacer una tabla de búsqueda (como una matriz estática privada de 64 valores booleanos). Entonces, en lugar de cambiar o or declaración, solo diría if(lookup[(int)(n&0x3F)]) { test } else return false;. Para mi sorpresa, esto fue (solo un poco) más lento. Esto es porque los límites de la matriz se verifican en Java.

preguntado el 17 de noviembre de 08 a las 11:11

Este es el código Java, donde int == 32 bits y long == 64 bits, y ambos están firmados. -

@Shreevasta: He realizado algunas pruebas con valores grandes (superiores a 2 ^ 53) y su método da algunos falsos positivos. El primero que se encuentra es para n = 9007199326062755, que no es un cuadrado perfecto pero se devuelve como uno. -

Por favor, no lo llames el "truco de John Carmack". No se le ocurrió. -

@mamama - Quizás, pero se le atribuye. Henry Ford no inventó el automóvil, los Wright Bros.no inventaron el avión, y Galleleo no fue el primero en descubrir que la Tierra giraba alrededor del sol ... el mundo está hecho de inventos robados (y amor). -

Puede obtener un pequeño aumento de velocidad en el 'error rápido' usando algo como ((1<<(n&15))|65004) != 0, en lugar de tener tres controles separados. -

0 Respuestas

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.