Estoy buscando la forma más rápida de determinar si un long
valor es un cuadrado perfecto (es decir, su raíz cuadrada es otro número entero):
- Lo hice de la manera más fácil, usando la
Math.sqrt()
función incorporada, pero me pregunto si hay una manera de hacerlo más rápido restringiéndote a un dominio solo de enteros. - Mantener una tabla de búsqueda no es práctico (ya que hay alrededor de 2 31.5 enteros cuyo cuadrado es menor que 2 63 ).
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 problemas del Proyecto Euler . Entonces nadie más tendrá que mantener este código. Y este tipo de micro-optimización en realidad 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.
He probado las diferentes soluciones al problema:
- Después de pruebas exhaustivas, descubrí que
0.5
no es necesario agregar al resultado de Math.sqrt (), al menos no en mi máquina. - La raíz cuadrada inversa rápida fue más rápida, pero dio resultados incorrectos para n> = 410881. Sin embargo, como lo sugiere BobbyShaftoe , podemos usar el hack de FISR para n <410881.
- El método de Newton fue un poco más lento que
Math.sqrt()
. Esto probablemente se deba a queMath.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 modificado de Newton, que usaba algunos trucos para que solo se involucrara la matemática entera, requería algunos hacks para evitar el desbordamiento (quiero que esta función funcione con todos los enteros con signo positivo de 64 bits), y aún así fue 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 pases para encontrar la raíz cuadrada de un número de 64 bits.
- Según las pruebas de John, el uso de
or
sentencias es más rápido en C ++ que el uso de aswitch
, pero en Java y C # parece que no hay diferencia entreor
yswitch
. - 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
declarar, solo diríaif(lookup[(int)(n&0x3F)]) { test } else return false;
. Para mi sorpresa, esto fue (solo un poco) más lento. Esto se debe a que los límites de la matriz se verifican en Java .
((1<<(n&15))|65004) != 0
, en lugar de tener tres controles separados.Respuestas:
Descubrí un método que funciona ~ 35% más rápido que sus 6 bits + Carmack + código sqrt, al menos con mi CPU (x86) y lenguaje de programación (C / C ++). Sus resultados pueden variar, especialmente porque no sé cómo se desarrollará el factor Java.
Mi enfoque es triple:
int64 x
).z = r - x * x
y configuro que t sea la mayor potencia de 2 dividiendo z con un poco de truco. Esto me permite omitir los valores t que no habrían afectado el valor de r de todos modos. El valor de inicio precalculado en mi caso selecciona el módulo de raíz cuadrada "más pequeño positivo" 8192.Incluso si este código no funciona más rápido para usted, espero que disfrute algunas de las ideas que contiene. El código completo y probado sigue, incluidas las tablas precalculadas.
fuente
9 < 0 => false
``9&2 => 0
`9&7 == 5 => false
`9&11 == 8 => false
.Llego bastante tarde a la fiesta, pero espero dar una mejor respuesta; más corto y (suponiendo que mi punto de referencia sea correcto) también mucho más rápido .
La primera prueba atrapa la mayoría de los no cuadrados rápidamente. Utiliza una tabla de 64 elementos empaquetada en un largo, por lo que no hay costo de acceso a la matriz (indirección y verificación de límites). Para un azar uniforme
long
, hay un 81.25% de probabilidad de terminar aquí.La segunda prueba captura todos los números que tienen un número impar de dos en su factorización. El método
Long.numberOfTrailingZeros
es muy rápido ya que obtiene JIT-ed en una sola instrucción i86.Después de eliminar los ceros finales, la tercera prueba maneja los números que terminan en 011, 101 o 111 en binario, que no son cuadrados perfectos. También se preocupa por los números negativos y también maneja el 0.
La prueba final recurre a la
double
aritmética. Comodouble
tiene solo 53 bits de mantisa, la conversión delong
adouble
incluye redondeo para valores grandes. No obstante, la prueba es correcta (a menos que la prueba sea incorrecta).Intentar incorporar la idea mod255 no tuvo éxito.
fuente
goodMask
prueba lo hace, pero lo hace antes del cambio a la derecha. Tendría que repetirlo, pero de esta manera es más simple y AFAIK un poco más rápido e igualmente bueno.if ((x & (7 | Integer.MIN_VALUE)) != 1) return x == 0;
.Tendrás que hacer algunos benchmarking. El mejor algoritmo dependerá de la distribución de sus entradas.
Su algoritmo puede ser casi óptimo, pero es posible que desee hacer una comprobación rápida para descartar algunas posibilidades antes de llamar a su rutina de raíz cuadrada. Por ejemplo, mire el último dígito de su número en hexadecimal haciendo un bit y "." Los cuadrados perfectos solo pueden terminar en 0, 1, 4 o 9 en la base 16, por lo que para el 75% de sus entradas (suponiendo que estén distribuidas uniformemente) puede evitar una llamada a la raíz cuadrada a cambio de un poco de giro de bits muy rápido.
Kip comparó el siguiente código que implementa el truco hexadecimal. Al probar los números 1 a 100,000,000, este código se ejecutó dos veces más rápido que el original.
Cuando probé el código análogo en C ++, en realidad fue más lento que el original. Sin embargo, cuando eliminé la declaración de cambio, el truco hexadecimal una vez más hizo que el código fuera dos veces más rápido.
La eliminación de la declaración de cambio tuvo poco efecto en el código C #.
fuente
Estaba pensando en los horribles momentos que pasé en el curso de Análisis Numérico.
Y luego recuerdo, había esta función dando vueltas alrededor de la red desde el código fuente de Quake:
Que básicamente calcula una raíz cuadrada, utilizando la función de aproximación de Newton (no puedo recordar el nombre exacto).
Debería ser utilizable e incluso podría ser más rápido, ¡es de uno de los fenomenales juegos de software de identificación!
Está escrito en C ++, pero no debería ser demasiado difícil reutilizar la misma técnica en Java una vez que tenga la idea:
Originalmente lo encontré en: http://www.codemaestro.com/reviews/9
El método de Newton explicado en wikipedia: http://en.wikipedia.org/wiki/Newton%27s_method
Puede seguir el enlace para obtener más explicaciones sobre cómo funciona, pero si no le importa mucho, entonces esto es más o menos lo que recuerdo al leer el blog y al tomar el curso de Análisis numérico:
* (long*) &y
es básicamente una función rápida-convertir-a largo para operaciones de números enteros se pueden aplicar en los bytes sin formato.0x5f3759df - (i >> 1);
línea es un valor semilla precalculado para la función de aproximación.* (float*) &i
convierte a la parte trasera valor de punto flotante.y = y * ( threehalfs - ( x2 * y * y ) )
línea básicamente itera el valor sobre la función nuevamente.La función de aproximación proporciona valores más precisos cuanto más itera la función sobre el resultado. En el caso de Quake, una iteración es "lo suficientemente buena", pero si no fuera por usted ... entonces podría agregar tanta iteración como necesite.
Esto debería ser más rápido porque reduce el número de operaciones de división realizadas en el enraizamiento cuadrado ingenuo a una simple división por 2 (en realidad, una
* 0.5F
operación de multiplicación) y lo reemplaza con un número fijo de operaciones de multiplicación.fuente
No estoy seguro de si sería más rápido, o incluso preciso, pero podría usar el algoritmo de la raíz cuadrada mágica de John Carmack para resolver la raíz cuadrada más rápido. Probablemente podría probar esto fácilmente para todos los posibles enteros de 32 bits y validar que realmente obtuvo los resultados correctos, ya que es solo una aproximación. Sin embargo, ahora que lo pienso, usar dobles también se está aproximando, así que no estoy seguro de cómo entraría en juego.
fuente
Si hace un corte binario para tratar de encontrar la raíz cuadrada "correcta", puede detectar con bastante facilidad si el valor que tiene es lo suficientemente cercano como para decir:
Entonces, habiendo calculado
n^2
, las opciones son:n^2 = target
: hecho, devuelve verdaderon^2 + 2n + 1 > target > n^2
: estás cerca, pero no es perfecto: devuelve falson^2 - 2n + 1 < target < n^2
: ídemtarget < n^2 - 2n + 1
: corte binario en una parte inferiorn
target > n^2 + 2n + 1
: corte binario en un nivel superiorn
(Lo sentimos, esto se usa
n
como su suposición actual ytarget
para el parámetro. ¡Disculpe la confusión!)No sé si será más rápido o no, pero vale la pena intentarlo.
EDITAR: El corte binario tampoco tiene que abarcar todo el rango de enteros, por
(2^x)^2 = 2^(2x)
lo que una vez que haya encontrado el bit establecido superior en su objetivo (que se puede hacer con un truco de giro de bits; me olvido exactamente cómo) puede obtener rápidamente una variedad de posibles respuestas. Eso sí, un ingenuo corte binario solo tomará hasta 31 o 32 iteraciones.fuente
Ejecuté mi propio análisis de varios de los algoritmos en este hilo y obtuve algunos resultados nuevos. Puede ver esos resultados anteriores en el historial de edición de esta respuesta, pero no son precisos, ya que cometí un error y perdí el tiempo analizando varios algoritmos que no están cerca. Sin embargo, sacando lecciones de varias respuestas diferentes, ahora tengo dos algoritmos que aplastan al "ganador" de este hilo. Aquí está lo más importante que hago de manera diferente a todos los demás:
Sin embargo, esta línea simple, que la mayoría de las veces agrega una o dos instrucciones muy rápidas, simplifica enormemente la
switch-case
declaración en una declaración if. Sin embargo, puede aumentar el tiempo de ejecución si muchos de los números probados tienen importantes factores de potencia de dos.Los siguientes algoritmos son los siguientes:
Aquí hay un ejemplo de tiempo de ejecución si los números se generan usando
Math.abs(java.util.Random.nextLong())
Y aquí hay un ejemplo de tiempo de ejecución si se ejecuta solo en el primer millón de largos:
Como puede ver,
DurronTwo
funciona mejor para entradas grandes, porque usa el truco de magia muy a menudo, pero se golpea en comparación con el primer algoritmo yMath.sqrt
porque los números son mucho más pequeños. Mientras tanto, el más simpleDurron
es un gran ganador porque nunca tiene que dividirse entre 4 muchas veces en el primer millón de números.Aquí está
Durron
:Y
DurronTwo
Y mi arnés de referencia: (Requiere Google caliper 0.1-rc5)
ACTUALIZACIÓN: He creado un nuevo algoritmo que es más rápido en algunos escenarios, más lento en otros, he obtenido diferentes puntos de referencia basados en diferentes entradas. Si calculamos el módulo
0xFFFFFF = 3 x 3 x 5 x 7 x 13 x 17 x 241
, podemos eliminar el 97.82% de los números que no pueden ser cuadrados. Esto puede hacerse (más o menos) en una línea, con 5 operaciones bit a bit:El índice resultante es 1) el residuo, 2) el residuo
+ 0xFFFFFF
o 3) el residuo+ 0x1FFFFFE
. Por supuesto, necesitamos tener una tabla de búsqueda para el módulo de residuos0xFFFFFF
, que se trata de un archivo de 3mb (en este caso almacenado como números decimales de texto ASCII, no óptimo pero claramente mejorable con ayByteBuffer
así sucesivamente. Pero como eso es precalculación, no funciona) Importa mucho. Puede encontrar el archivo aquí (o generarlo usted mismo):Lo cargo en una
boolean
matriz como esta:Ejemplo de tiempo de ejecución. Se superó
Durron
(versión uno) en cada prueba que ejecuté.fuente
sqrtps
rendimiento SIMD o inclusosqrtpd
(doble precisión) no es tan malo en Skylake, pero no es mucho mejor que la latencia en las CPU antiguas. De todos modos, 7-cpu.com/cpu/Haswell.html tiene algunos buenos números experimentales y páginas para otras CPU. El pdf de la guía de microarquitectura de Agner Fog tiene algunos números de latencia de caché para Intel y AMD uarches: agner.org/optimizedouble
precisión para evitar redondear algún número entero fuera del rango + -2 ^ 24 (por lo que un número entero de 32 bits puede estar fuera de eso), ysqrtpd
es más lento quesqrtps
, además de procesar la mitad de elementos por instrucción (por vector SIMD) .Debería ser mucho más rápido usar el método de Newton para calcular la raíz cuadrada entera , luego cuadrar este número y verificar, como lo hace en su solución actual. El método de Newton es la base de la solución Carmack mencionada en algunas otras respuestas. Debería poder obtener una respuesta más rápida ya que solo está interesado en la parte entera de la raíz, lo que le permite detener el algoritmo de aproximación antes.
Otra optimización que puede probar: si la raíz digital de un número no termina en 1, 4, 7 o 9, el número no es un cuadrado perfecto. Esto se puede usar como una forma rápida de eliminar el 60% de sus entradas antes de aplicar el algoritmo de raíz cuadrada más lento.
fuente
Math.sqrt()
funciona con dobles como parámetros de entrada, por lo que no obtendrá resultados precisos para enteros mayores de 2 ^ 53 .fuente
Solo para el registro, otro enfoque es usar la descomposición primaria. Si cada factor de la descomposición es par, entonces el número es un cuadrado perfecto. Entonces, lo que quiere es ver si un número puede descomponerse como un producto de cuadrados de números primos. Por supuesto, no necesita obtener dicha descomposición, solo para ver si existe.
Primero construya una tabla de cuadrados de números primos que sean menores que 2 ^ 32. Esto es mucho más pequeño que una tabla de todos los enteros hasta este límite.
Una solución sería así:
Supongo que es un poco críptico. Lo que hace es verificar en cada paso que el cuadrado de un número primo divida el número de entrada. Si lo hace, divide el número por el cuadrado tanto como sea posible, para eliminar este cuadrado de la descomposición primaria. Si por este proceso llegamos a 1, entonces el número de entrada fue una descomposición del cuadrado de los números primos. Si el cuadrado se vuelve más grande que el número en sí, entonces no hay forma de que este cuadrado, o cualquier cuadrado más grande, pueda dividirlo, por lo que el número no puede ser una descomposición de cuadrados de números primos.
Dado el sqrt de hoy en día hecho en hardware y la necesidad de calcular números primos aquí, supongo que esta solución es mucho más lenta. Pero debería dar mejores resultados que la solución con sqrt que no funcionará durante 2 ^ 54, como dice mrzl en su respuesta.
fuente
sqrtsd
rendimiento de Core2 es uno por 6-58c. Suidiv
es una por 12-36cycles. (latencias similares a los rendimientos: ninguna unidad está canalizada).Se ha señalado que los últimos
d
dígitos de un cuadrado perfecto solo pueden tomar ciertos valores. Los últimosd
dígitos (en la baseb
) de un númeron
son los mismos que el resto cuandon
se divide porb
d
, es decir. en C notaciónn % pow(b, d)
.Esto se puede generalizar a cualquier módulo
m
, es decir.n % m
se puede usar para descartar que algunos porcentajes de números sean cuadrados perfectos. El módulo que está utilizando actualmente es 64, que permite 12, es decir. 19% de los residuos, como posibles cuadrados. Con un poco de codificación encontré el módulo 110880, que solo permite 2016, es decir. 1.8% de los residuos como posibles cuadrados. Entonces, dependiendo del costo de una operación de módulo (es decir, división) y una búsqueda de tabla versus una raíz cuadrada en su máquina, el uso de este módulo podría ser más rápido.Por cierto, si Java tiene una manera de almacenar una matriz de bits empaquetada para la tabla de búsqueda, no la use. 110880 Las palabras de 32 bits no tienen mucha RAM en estos días y buscar una palabra de máquina será más rápido que recuperar un solo bit.
fuente
idiv
) es igual o peor en costo a FP sqrt (sqrtsd
) en el hardware x86 actual. Además, completamente en desacuerdo con evitar los campos de bits. La tasa de aciertos de caché será mucho mejor con un campo de bits, y probar un poco en un campo de bits es solo una o dos instrucciones más simples que probar un byte completo. (Para las tablas pequeñas que caben en la memoria caché, incluso como campos que no son de bits, sería mejor una matriz de bytes, no entradas de 32 bits. X86 tiene acceso de un solo byte con la misma velocidad de 32 bits dword.)Un problema entero merece una solución entera. Así
Haga una búsqueda binaria en los enteros (no negativos) para encontrar el mayor entero t tal que
t**2 <= n
. Luego prueba sir**2 = n
exactamente. Esto lleva tiempo O (log n).Si no sabe cómo buscar binariamente los enteros positivos porque el conjunto no tiene límites, es fácil. Empiezas calculando tu función creciente f (arriba
f(t) = t**2 - n
) en potencias de dos. Cuando vea que se vuelve positivo, ha encontrado un límite superior. Entonces puedes hacer una búsqueda binaria estándar.fuente
O((log n)^2)
porque la multiplicación no es un tiempo constante sino que, de hecho, tiene un límite inferiorO(log n)
, que se hace evidente cuando se trabaja con números grandes de precisión múltiple. Pero el alcance de esta wiki parece ser de 64 bits, por lo que tal vez sea nbd.La siguiente simplificación de la solución de maaartinus parece reducir algunos puntos porcentuales del tiempo de ejecución, pero no soy lo suficientemente bueno en la evaluación comparativa para producir una referencia en la que pueda confiar:
Valdría la pena comprobar cómo omitir la primera prueba,
afectaría el rendimiento
fuente
Para el rendimiento, a menudo tiene que hacer algunos compromisos. Otros han expresado varios métodos, sin embargo, notó que el hack de Carmack fue más rápido hasta ciertos valores de N. Luego, debe verificar la "n" y si es menor que ese número N, use el hack de Carmack, de lo contrario use algún otro método descrito en las respuestas aquí.
fuente
Esta es la implementación de Java más rápida que se me ocurrió, usando una combinación de técnicas sugeridas por otros en este hilo.
También experimenté con estas modificaciones pero no ayudaron al rendimiento:
fuente
Deberías deshacerte de la parte de 2 potencias de N desde el principio.
2da Edición La expresión mágica para m a continuación debe ser
y no como está escrito
Fin de la 2da edición
1ra Edición:
Mejora menor:
Fin de la primera edición
Ahora continúa como siempre. De esta manera, cuando llegas a la parte de coma flotante, ya te has deshecho de todos los números cuya parte de 2 potencias es impar (aproximadamente la mitad), y luego solo consideras 1/8 de lo que queda. Es decir, ejecuta la parte de coma flotante en el 6% de los números.
fuente
El proyecto Euler se menciona en las etiquetas y muchos de los problemas en él requieren verificar números >>
2^64
. La mayoría de las optimizaciones mencionadas anteriormente no funcionan fácilmente cuando trabaja con un búfer de 80 bytes.Utilicé Java BigInteger y una versión ligeramente modificada del método de Newton, una que funciona mejor con enteros. El problema era que los cuadrados exactos
n^2
convergían en(n-1)
lugar den
porquen^2-1 = (n-1)(n+1)
y el error final estaba solo un paso debajo del divisor final y el algoritmo terminaba. Fue fácil de solucionar agregando uno al argumento original antes de calcular el error. (Agregue dos para las raíces cúbicas, etc.)Un buen atributo de este algoritmo es que puede saber de inmediato si el número es un cuadrado perfecto: el error final (no la corrección) en el método de Newton será cero. Una modificación simple también le permite calcular rápidamente en
floor(sqrt(x))
lugar del entero más cercano. Esto es útil con varios problemas de Euler.fuente
Esta es una reelaboración de decimal a binario del antiguo algoritmo de calculadora Marchant (lo siento, no tengo una referencia), en Ruby, adaptado específicamente para esta pregunta:
Aquí hay una solución de algo similar (por favor, no me rechace por codificar estilos / olores u O / O torpe: es el algoritmo lo que cuenta, y C ++ no es mi idioma de origen). En este caso, estamos buscando residuos == 0:
fuente
La llamada sqrt no es perfectamente precisa, como se ha mencionado, pero es interesante e instructivo que no elimina las otras respuestas en términos de velocidad. Después de todo, la secuencia de instrucciones en lenguaje ensamblador para un sqrt es pequeña. Intel tiene una instrucción de hardware, que Java no utiliza, creo, porque no cumple con IEEE.
Entonces, ¿por qué es lento? Debido a que Java en realidad está llamando a una rutina C a través de JNI, y en realidad es más lento hacerlo que llamar a una subrutina Java, que en sí es más lenta que hacerlo en línea. Esto es muy molesto, y Java debería haber encontrado una solución mejor, es decir, incorporar llamadas de biblioteca de punto flotante si fuera necesario. Oh bien.
En C ++, sospecho que todas las alternativas complejas perderían velocidad, pero no las he verificado todas. Lo que hice, y lo que la gente de Java encontrará útil, es un simple truco, una extensión de las pruebas de casos especiales sugeridas por A. Rex. Use un solo valor largo como una matriz de bits, que no esté marcada en los límites. De esa manera, tiene una búsqueda booleana de 64 bits.
La rutina isPerfectSquare5 se ejecuta en aproximadamente 1/3 del tiempo en mi máquina core2 duo. Sospecho que más ajustes a lo largo de la misma línea podrían reducir el tiempo más en promedio, pero cada vez que verifica, está intercambiando más pruebas por más eliminación, por lo que no puede avanzar mucho más en ese camino.
Ciertamente, en lugar de tener una prueba de negativo por separado, puede verificar los 6 bits altos de la misma manera.
Tenga en cuenta que todo lo que estoy haciendo es eliminar posibles cuadrados, pero cuando tengo un caso potencial tengo que llamar al original, en línea, isPerfectSquare.
La rutina init2 se llama una vez para inicializar los valores estáticos de pp1 y pp2. Tenga en cuenta que en mi implementación en C ++, estoy usando unsigned long long, por lo que, dado que está firmado, tendría que usar el operador >>>.
No hay necesidad intrínseca de verificar los límites de la matriz, pero el optimizador de Java tiene que resolver esto bastante rápido, así que no los culpo por eso.
fuente
pp2
? Entiendo quepp1
se usa para probar los seis bits menos significativos, pero no creo que probar los próximos seis bits tenga sentido.Me gusta la idea de usar un método casi correcto en algunas de las entradas. Aquí hay una versión con un "desplazamiento" más alto. El código parece funcionar y pasa mi caso de prueba simple.
Simplemente reemplace su:
código con este:
fuente
Teniendo en cuenta la longitud de bits general (aunque he usado un tipo específico aquí), traté de diseñar algo simplista como se muestra a continuación. Inicialmente se requiere una verificación simple y obvia para 0,1,2 o <0. Lo siguiente es simple en el sentido de que no intenta usar ninguna función matemática existente. La mayor parte del operador puede ser reemplazado por operadores de bits. Sin embargo, no he probado con ningún dato de referencia. No soy experto en matemáticas ni en diseño de algoritmos informáticos en particular, me encantaría verte señalando el problema. Sé que hay muchas posibilidades de mejora allí.
fuente
Verifiqué todos los resultados posibles cuando se observan los últimos n bits de un cuadrado. Al examinar sucesivamente más bits, se pueden eliminar hasta 5/6 de las entradas. De hecho, diseñé esto para implementar el algoritmo de factorización de Fermat, y es muy rápido allí.
El último bit de pseudocódigo se puede usar para extender las pruebas para eliminar más valores. Las pruebas anteriores son para k = 0, 1, 2, 3
Primero prueba si tiene un residual cuadrado con módulos de potencia de dos, luego prueba en función de un módulo final, luego usa Math.sqrt para hacer una prueba final. Se me ocurrió la idea desde la publicación superior e intenté extenderla. Agradezco cualquier comentario o sugerencia.
Actualización: Utilizando la prueba por un módulo, (modSq) y una base de módulo de 44352, mi prueba se ejecuta en el 96% del tiempo de la actualización de OP para números de hasta 1,000,000,000.
fuente
Aquí hay una solución de divide y vencerás.
Si la raíz cuadrada de un número natural (
number
) es un número natural (solution
), puede determinar fácilmente un rangosolution
basado en el número de dígitos denumber
:number
tiene 1 dígito:solution
en rango = 1 - 4number
tiene 2 dígitos:solution
en rango = 3 - 10number
tiene 3 dígitos:solution
en rango = 10-40number
tiene 4 dígitos:solution
en rango = 30-100number
tiene 5 dígitos:solution
en rango = 100 - 400¿Te das cuenta de la repetición?
Puede usar este rango en un enfoque de búsqueda binaria para ver si hay una
solution
para la cual:Aqui esta el codigo
Aquí está mi clase SquareRootChecker
Y aquí hay un ejemplo sobre cómo usarlo.
fuente
toString
es una operación increíblemente costosa en comparación con los operadores bit a bit. Por lo tanto, para satisfacer el objetivo de la pregunta (rendimiento), debe utilizar operadores bit a bit en lugar de cadenas de base 10. Nuevamente, me gusta mucho tu concepto. No obstante, su implementación (tal como está ahora) es, con mucho, la más lenta de todas las soluciones posibles publicadas para la pregunta.Si la velocidad es una preocupación, ¿por qué no dividir el conjunto de entradas más comúnmente utilizado y sus valores en una tabla de búsqueda y luego hacer cualquier algoritmo mágico optimizado que se le haya ocurrido para los casos excepcionales?
fuente
¡Debería ser posible empacar el 'no puede ser un cuadrado perfecto si los últimos X dígitos son N' mucho más eficientemente que eso! Usaré ints de Java de 32 bits y produciré suficientes datos para verificar los últimos 16 bits del número, es decir, 2048 valores int hexadecimales.
...
Okay. O me he topado con alguna teoría de números que está un poco más allá de mí, o hay un error en mi código. En cualquier caso, aquí está el código:
Y aquí están los resultados:
(ed: elidido por bajo rendimiento en prettify.js; ver el historial de revisiones para ver).
fuente
Método de Newton con aritmética de enteros
Si desea evitar operaciones no enteras, puede utilizar el siguiente método. Básicamente utiliza el método de Newton modificado para la aritmética de enteros.
Esta implementación no puede competir con las soluciones que utilizan
Math.sqrt
. Sin embargo, su rendimiento puede mejorarse utilizando los mecanismos de filtrado descritos en algunas de las otras publicaciones.fuente
Calcular raíces cuadradas por el método de Newton es terriblemente rápido ... siempre que el valor inicial sea razonable. Sin embargo, no hay un valor inicial razonable, y en la práctica terminamos con la bisección y el comportamiento log (2 ^ 64).
Para ser realmente rápidos, necesitamos una forma rápida de obtener un valor inicial razonable, y eso significa que debemos descender al lenguaje máquina. Si un procesador proporciona una instrucción como POPCNT en el Pentium, cuenta los ceros iniciales que podemos usar para tener un valor inicial con la mitad de los bits significativos. Con cuidado podemos encontrar un número fijo de pasos de Newton que siempre será suficiente. (Por lo tanto, antes de la necesidad de bucle y tener una ejecución muy rápida).
Una segunda solución es a través de la instalación de punto flotante, que puede tener un cálculo rápido de sqrt (como el coprocesador i87). Incluso una excursión a través de exp () y log () puede ser más rápida que Newton degenerada en una búsqueda binaria. Hay un aspecto complicado en esto, un análisis dependiente del procesador de qué y si es necesario un refinamiento posterior.
Una tercera solución resuelve un problema ligeramente diferente, pero vale la pena mencionarlo porque la situación se describe en la pregunta. Si desea calcular una gran cantidad de raíces cuadradas para números que difieren ligeramente, puede usar la iteración de Newton, si nunca reinicializa el valor inicial, pero simplemente déjelo donde quedó el cálculo anterior. He usado esto con éxito en al menos un problema de Euler.
fuente
Raíz cuadrada de un número, dado que el número es un cuadrado perfecto.
La complejidad es log (n)
fuente
Si desea velocidad, dado que sus enteros son de tamaño finito, sospecho que la forma más rápida implicaría (a) dividir los parámetros por tamaño (por ejemplo, en categorías por conjunto de bits más grande), luego verificar el valor contra una matriz de cuadrados perfectos dentro de ese rango.
fuente
Con respecto al método Carmac, parece que sería bastante fácil repetir una vez más, lo que debería duplicar el número de dígitos de precisión. Después de todo, es un método iterativo extremadamente truncado, el de Newton, con una muy buena primera suposición.
En cuanto a su mejor momento actual, veo dos micro optimizaciones:
Es decir:
Aún mejor podría ser un simple
Obviamente, sería interesante saber cuántos números se seleccionan en cada punto de control; dudo mucho que los controles sean realmente independientes, lo que dificulta las cosas.
fuente