Je cherche le moyen le plus rapide pour déterminer si une long
valeur est un carré parfait (c'est-à-dire que sa racine carrée est un autre entier):
- Je l'ai fait de manière simple, en utilisant la
Math.sqrt()
fonction intégrée, mais je me demande s'il y a un moyen de le faire plus rapidement en vous limitant au domaine entier. - Il n'est pas pratique de gérer une table de correspondance (car il existe environ 2 31,5 entiers dont le carré est inférieur à 2 63 ).
Voici la façon très simple et directe dont je le fais maintenant:
public final static boolean isPerfectSquare(long n)
{
if (n < 0)
return false;
long tst = (long)(Math.sqrt(n) + 0.5);
return tst*tst == n;
}
Remarque: J'utilise cette fonction dans de nombreux problèmes liés à Project Euler . Donc, personne d'autre n'aura jamais à maintenir ce code. Et ce type de micro-optimisation pourrait réellement faire une différence, car une partie du défi consiste à faire chaque algorithme en moins d'une minute, et cette fonction devra être appelée des millions de fois dans certains problèmes.
J'ai essayé les différentes solutions au problème:
- Après des tests exhaustifs, j'ai trouvé que l'ajout
0.5
au résultat de Math.sqrt () n'est pas nécessaire, du moins pas sur ma machine. - La racine carrée inverse rapide était plus rapide, mais elle a donné des résultats incorrects pour n> = 410881. Cependant, comme suggéré par BobbyShaftoe , nous pouvons utiliser le hack FISR pour n <410881.
- La méthode de Newton était un peu plus lente que
Math.sqrt()
. C'est probablement parce qu'ilMath.sqrt()
utilise quelque chose de similaire à la méthode de Newton, mais implémenté dans le matériel, c'est donc beaucoup plus rapide qu'en Java. De plus, la méthode de Newton exigeait toujours l'utilisation de doubles. - Une méthode de Newton modifiée, qui utilisait quelques astuces pour que seules les mathématiques entières soient impliquées, nécessitait des hacks pour éviter le débordement (je veux que cette fonction fonctionne avec tous les entiers signés 64 bits positifs), et elle était toujours plus lente que
Math.sqrt()
. - La coupe binaire était encore plus lente. Cela a du sens, car le découpage binaire nécessitera en moyenne 16 passes pour trouver la racine carrée d'un nombre 64 bits.
- Selon les tests de John, l'utilisation d'
or
instructions est plus rapide en C ++ que l'utilisation de aswitch
, mais en Java et C #, il ne semble pas y avoir de différence entreor
etswitch
. - J'ai également essayé de créer une table de recherche (en tant que tableau statique privé de 64 valeurs booléennes). Ensuite, au lieu d'un commutateur ou d'une
or
déclaration, je dirais simplementif(lookup[(int)(n&0x3F)]) { test } else return false;
. À ma grande surprise, c'était (légèrement) plus lent. En effet , les limites des tableaux sont vérifiées en Java .
((1<<(n&15))|65004) != 0
, au lieu d'avoir trois contrôles distincts.Réponses:
J'ai trouvé une méthode qui fonctionne ~ 35% plus rapidement que votre code 6bits + Carmack + sqrt, au moins avec mon CPU (x86) et mon langage de programmation (C / C ++). Vos résultats peuvent varier, surtout parce que je ne sais pas comment le facteur Java se déroulera.
Mon approche est triple:
int64 x
.)z = r - x * x
et règle t pour être la plus grande puissance de 2 divisant z avec un petit truc. Cela me permet de sauter des valeurs t qui n'auraient pas affecté la valeur de r de toute façon. La valeur de départ précalculée dans mon cas sélectionne le "plus petit positif" racine modulo 8192.Même si ce code ne fonctionne pas plus rapidement pour vous, j'espère que vous apprécierez certaines des idées qu'il contient. Le code complet et testé suit, y compris les tables précalculées.
la source
9 < 0 => false
,9&2 => 0
,9&7 == 5 => false
,9&11 == 8 => false
.Je suis assez en retard à la fête, mais j'espère apporter une meilleure réponse; plus court et (en supposant que mon indice de référence est correct) aussi beaucoup plus rapide .
Le premier test capture rapidement la plupart des non-carrés. Il utilise une table de 64 éléments emballée dans un long, donc il n'y a pas de coût d'accès au tableau (vérifications d'indirection et de limites). Pour un aléatoire uniforme
long
, il y a une probabilité de 81,25% de se terminer ici.Le deuxième test capture tous les nombres ayant un nombre impair de deux dans leur factorisation. La méthode
Long.numberOfTrailingZeros
est très rapide car elle est convertie en JIT dans une seule instruction i86.Après avoir supprimé les zéros de fin, le troisième test gère les nombres se terminant par 011, 101 ou 111 en binaire, qui ne sont pas des carrés parfaits. Il se soucie également des nombres négatifs et gère également 0.
Le test final revient à l'
double
arithmétique. Comme ladouble
mantisse n'a que 53 bits, la conversion delong
àdouble
inclut l'arrondi pour les grandes valeurs. Néanmoins, le test est correct (sauf si la preuve est fausse).La tentative d'intégration de l'idée du mod255 n'a pas réussi.
la source
goodMask
test le fait, mais il le fait avant le bon décalage. Il faudrait donc le répéter, mais de cette façon, c'est plus simple et l'AFAIK un tout petit peu plus rapide et tout aussi bon.if ((x & (7 | Integer.MIN_VALUE)) != 1) return x == 0;
.Vous devrez faire des analyses comparatives. Le meilleur algorithme dépendra de la distribution de vos entrées.
Votre algorithme peut être presque optimal, mais vous voudrez peut-être faire une vérification rapide pour exclure certaines possibilités avant d'appeler votre routine racine carrée. Par exemple, regardez le dernier chiffre de votre numéro en hexadécimal en faisant un "et". Les carrés parfaits ne peuvent se terminer qu'en 0, 1, 4 ou 9 en base 16, donc pour 75% de vos entrées (en supposant qu'elles sont uniformément réparties), vous pouvez éviter un appel à la racine carrée en échange d'un peu de twiddling très rapide.
Kip a testé le code suivant implémentant l'astuce hex. Lors du test des numéros 1 à 100 000 000, ce code a fonctionné deux fois plus vite que l'original.
Lorsque j'ai testé le code analogue en C ++, il s'est en fait déroulé plus lentement que l'original. Cependant, lorsque j'ai supprimé l'instruction switch, l'astuce hexadécimale rend le code deux fois plus rapide.
L'élimination de l'instruction switch a eu peu d'effet sur le code C #.
la source
Je pensais aux moments horribles que j'ai passés dans le cours d'analyse numérique.
Et puis je me souviens, il y avait cette fonction qui tournait autour du net à partir du code source de Quake:
Ce qui calcule essentiellement une racine carrée, en utilisant la fonction d'approximation de Newton (je ne peux pas me souvenir du nom exact).
Il devrait être utilisable et pourrait même être plus rapide, il s'agit d'un des jeux phénoménaux du logiciel d'identification!
Il est écrit en C ++ mais il ne devrait pas être trop difficile de réutiliser la même technique en Java une fois que vous avez l'idée:
Je l'ai trouvé à l'origine sur: http://www.codemaestro.com/reviews/9
La méthode de Newton expliquée sur wikipedia: http://en.wikipedia.org/wiki/Newton%27s_method
Vous pouvez suivre le lien pour plus d'explications sur la façon dont cela fonctionne, mais si vous ne vous souciez pas beaucoup, c'est à peu près ce dont je me souviens en lisant le blog et en suivant le cours d'analyse numérique:
* (long*) &y
s'agit essentiellement d'une fonction de conversion rapide en fonction longue de sorte que des opérations entières peuvent être appliquées sur les octets bruts.0x5f3759df - (i >> 1);
ligne est une valeur de départ pré-calculée pour la fonction d'approximation.* (float*) &i
convertit la valeur en virgule flottante.y = y * ( threehalfs - ( x2 * y * y ) )
ligne réitère basiquement la valeur sur la fonction.La fonction d'approximation donne des valeurs plus précises plus vous itérez la fonction sur le résultat. Dans le cas de Quake, une itération est "assez bonne", mais si ce n'était pas pour vous ... alors vous pouvez ajouter autant d'itérations que vous le souhaitez.
Cela devrait être plus rapide car il réduit le nombre d'opérations de division effectuées en racine carrée naïve à une simple division par 2 (en fait une
* 0.5F
opération de multiplication) et le remplace par un nombre fixe d'opérations de multiplication à la place.la source
Je ne sais pas si ce serait plus rapide, voire précis, mais vous pouvez utiliser l' algorithme Magical Square Root de John Carmack pour résoudre la racine carrée plus rapidement. Vous pourriez probablement facilement tester cela pour tous les entiers 32 bits possibles et valider que vous avez réellement obtenu des résultats corrects, car ce n'est qu'une appoximation. Cependant, maintenant que j'y pense, l'utilisation de doubles est également approximative, donc je ne sais pas comment cela pourrait entrer en jeu.
la source
Si vous effectuez un découpage binaire pour essayer de trouver la "bonne" racine carrée, vous pouvez assez facilement détecter si la valeur que vous avez est suffisamment proche pour le dire:
Donc, après avoir calculé
n^2
, les options sont:n^2 = target
: terminé, retourne vrain^2 + 2n + 1 > target > n^2
: vous êtes proche, mais ce n'est pas parfait: retour fauxn^2 - 2n + 1 < target < n^2
: idemtarget < n^2 - 2n + 1
: coupure binaire sur un fondn
target > n^2 + 2n + 1
: coupure binaire sur un supérieurn
(Désolé, cela utilise
n
comme estimation actuelle ettarget
pour le paramètre. Veuillez vous excuser pour la confusion!)Je ne sais pas si ce sera plus rapide ou non, mais ça vaut le coup d'essayer.
EDIT: Le cliché binaire n'a pas non plus à prendre toute la gamme d'entiers,
(2^x)^2 = 2^(2x)
donc une fois que vous avez trouvé le bit le plus haut dans votre cible (ce qui peut être fait avec une astuce de bit-twiddling; j'oublie exactement comment) vous pouvez rapidement obtenir une gamme de réponses potentielles. Attention, une côtelette binaire naïve ne prendra toujours que 31 ou 32 itérations.la source
J'ai exécuté ma propre analyse de plusieurs des algorithmes de ce fil et j'ai trouvé de nouveaux résultats. Vous pouvez voir ces anciens résultats dans l'historique des modifications de cette réponse, mais ils ne sont pas précis, car j'ai fait une erreur et perdu du temps à analyser plusieurs algorithmes qui ne sont pas proches. Cependant, tirant des leçons de plusieurs réponses différentes, j'ai maintenant deux algorithmes qui écrasent le "gagnant" de ce fil. Voici l'essentiel que je fais différemment de tout le monde:
Cependant, cette ligne simple, qui ajoute la plupart du temps une ou deux instructions très rapides, simplifie considérablement l'
switch-case
instruction en une instruction if. Cependant, il peut s'ajouter à l'exécution si de nombreux nombres testés ont des facteurs de puissance de deux significatifs.Les algorithmes ci-dessous sont les suivants:
Voici un exemple d'exécution si les nombres sont générés à l'aide
Math.abs(java.util.Random.nextLong())
Et voici un exemple d'exécution s'il est exécuté uniquement sur le premier million de longs:
Comme vous pouvez le voir,
DurronTwo
fait mieux pour les grandes entrées, car il utilise très souvent le tour de magie, mais est encombré par rapport au premier algorithme etMath.sqrt
parce que les nombres sont tellement plus petits. Pendant ce temps, le plus simpleDurron
est un énorme gagnant car il n'a jamais à diviser par 4 plusieurs fois dans le premier million de nombres.Voici
Durron
:Et
DurronTwo
Et mon harnais de référence: (Nécessite un étrier Google 0.1-rc5)
MISE À JOUR: J'ai créé un nouvel algorithme qui est plus rapide dans certains scénarios, plus lent dans d'autres, j'ai obtenu différents repères basés sur différentes entrées. Si nous calculons le modulo
0xFFFFFF = 3 x 3 x 5 x 7 x 13 x 17 x 241
, nous pouvons éliminer 97,82% des nombres qui ne peuvent pas être des carrés. Cela peut être (en quelque sorte) effectué sur une seule ligne, avec 5 opérations au niveau du bit:L'indice résultant est soit 1) le résidu, 2) le résidu
+ 0xFFFFFF
, ou 3) le résidu+ 0x1FFFFFE
. Bien sûr, nous devons avoir une table de recherche pour les résidus modulo0xFFFFFF
, qui représente environ un fichier de 3 Mo (dans ce cas, stocké sous forme de nombres décimaux de texte ascii, pas optimal mais clairement améliorable avec aByteBuffer
et ainsi de suite. C'est si important. Vous pouvez trouver le fichier ici (ou le générer vous-même):Je le charge dans un
boolean
tableau comme celui-ci:Exemple d'exécution. Il a battu
Durron
(version un) dans chaque essai que j'ai couru.la source
sqrtps
débit SIMD ou mêmesqrtpd
(double précision) n'est pas trop mauvais sur Skylake, mais n'est pas beaucoup mieux que la latence sur les anciens processeurs. Quoi qu'il en soit, 7-cpu.com/cpu/Haswell.html a de bons numéros expérimentaux et des pages pour d'autres CPU. Le guide des microarchives d'Agner Fog pdf contient des numéros de latence de cache pour les uarches Intel et AMD: agner.org/optimizedouble
précision pour éviter d'arrondir un entier en dehors de la plage + -2 ^ 24 (donc un entier 32 bits peut être en dehors de cela), etsqrtpd
est plus lent quesqrtps
et ne traite que la moitié du nombre d'éléments par instruction (par vecteur SIMD) .Il devrait être beaucoup plus rapide d'utiliser la méthode de Newton pour calculer la racine carrée entière , puis de mettre ce nombre au carré et de vérifier, comme vous le faites dans votre solution actuelle. La méthode de Newton est la base de la solution de Carmack mentionnée dans certaines autres réponses. Vous devriez pouvoir obtenir une réponse plus rapide, car vous n'êtes intéressé que par la partie entière de la racine, ce qui vous permet d'arrêter l'algorithme d'approximation plus tôt.
Une autre optimisation que vous pouvez essayer: si la racine numérique d'un nombre ne se termine pas par 1, 4, 7 ou 9, le nombre n'est pas un carré parfait. Cela peut être utilisé comme un moyen rapide d'éliminer 60% de vos entrées avant d'appliquer l'algorithme de racine carrée plus lent.
la source
Math.sqrt()
fonctionne avec des doubles comme paramètres d'entrée, donc vous n'obtiendrez pas de résultats précis pour des entiers supérieurs à 2 ^ 53 .la source
Pour mémoire, une autre approche consiste à utiliser la décomposition principale. Si chaque facteur de la décomposition est pair, alors le nombre est un carré parfait. Donc, ce que vous voulez, c'est voir si un nombre peut être décomposé comme un produit de carrés de nombres premiers. Bien sûr, vous n'avez pas besoin d'obtenir une telle décomposition, juste pour voir si elle existe.
Construisez d'abord un tableau de carrés de nombres premiers inférieurs à 2 ^ 32. C'est beaucoup plus petit qu'une table de tous les nombres entiers jusqu'à cette limite.
Une solution serait alors comme ceci:
Je suppose que c'est un peu cryptique. Il vérifie à chaque étape que le carré d'un nombre premier divise le nombre entré. Si c'est le cas, il divise le nombre par le carré aussi longtemps que possible, pour retirer ce carré de la décomposition principale. Si par ce processus, nous arrivions à 1, alors le nombre d'entrée était une décomposition du carré des nombres premiers. Si le carré devient plus grand que le nombre lui-même, il n'y a aucun moyen que ce carré, ou des carrés plus grands, puisse le diviser, donc le nombre ne peut pas être une décomposition de carrés de nombres premiers.
Étant donné le sqrt de nos jours fait en matériel et la nécessité de calculer des nombres premiers ici, je suppose que cette solution est beaucoup plus lente. Mais cela devrait donner de meilleurs résultats que la solution avec sqrt qui ne fonctionnera pas sur 2 ^ 54, comme le dit mrzl dans sa réponse.
la source
sqrtsd
débit de Core2 est de un par 6-58c. C'estidiv
un par 12-36 cycles. (latences similaires aux débits: aucune des unités n'est en pipeline).Il a été souligné que les derniers
d
chiffres d'un carré parfait ne peuvent prendre que certaines valeurs. Les derniersd
chiffres (en baseb
) d'un nombren
sont les mêmes que le reste lorsqu'iln
est divisé parb
d
, c'est-à-dire. en notation Cn % pow(b, d)
.Cela peut être généralisé à n'importe quel module
m
, c'est-à-dire.n % m
peut être utilisé pour exclure un certain pourcentage des nombres d'être des carrés parfaits. Le module que vous utilisez actuellement est de 64, ce qui permet 12, c'est-à-dire. 19% des restes, comme des carrés possibles. Avec un peu de codage j'ai trouvé le module 110880, qui ne permet que 2016, c'est à dire. 1,8% des restes comme carrés possibles. Ainsi, en fonction du coût d'une opération de module (c.-à-d. Division) et d'une recherche de table par rapport à une racine carrée sur votre machine, l'utilisation de ce module peut être plus rapide.Soit dit en passant, si Java a un moyen de stocker un tableau compact de bits pour la table de recherche, ne l'utilisez pas. 110880 Les mots 32 bits ne consomment pas beaucoup de RAM de nos jours et la récupération d'un mot machine va être plus rapide que la récupération d'un seul bit.
la source
idiv
) a un coût égal ou pire à FP sqrt (sqrtsd
) sur le matériel x86 actuel. En outre, vous n'êtes pas du tout d'accord pour éviter les champs de bits. Le taux de réussite du cache sera bien meilleur avec un champ binaire, et tester un bit dans un champ binaire n'est qu'une ou deux instructions plus simples que tester un octet entier. (Pour les tables minuscules qui tiennent dans le cache même en tant que non-champs de bits, un tableau d'octets serait préférable, pas des entiers de 32 bits. X86 a un accès à un octet avec une vitesse égale à 32 bits dword.)Un problème entier mérite une solution entière. Donc
Effectuez une recherche binaire sur les entiers (non négatifs) pour trouver le plus grand entier t tel que
t**2 <= n
. Testez ensuite sir**2 = n
exactement. Cela prend du temps O (log n).Si vous ne savez pas comment rechercher binaire les entiers positifs parce que l'ensemble est illimité, c'est facile. Vous commencez par calculer votre fonction croissante f (ci-dessus
f(t) = t**2 - n
) sur des puissances de deux. Lorsque vous le voyez devenir positif, vous avez trouvé une limite supérieure. Ensuite, vous pouvez faire une recherche binaire standard.la source
O((log n)^2)
parce que la multiplication n'est pas à temps constant mais a en fait une limite inférieure deO(log n)
, ce qui devient apparent lorsque l'on travaille avec de grands nombres multi-précision. Mais la portée de ce wiki semble être de 64 bits, donc c'est peut-être nbd.La simplification suivante de la solution de maaartinus semble réduire de quelques points le temps d'exécution, mais je ne suis pas assez bon en analyse comparative pour produire une référence en laquelle je peux avoir confiance:
Il vaudrait la peine de vérifier comment omettre le premier test,
affecterait les performances.
la source
Pour les performances, vous devez très souvent faire quelques compromis. D'autres ont exprimé diverses méthodes, cependant, vous avez noté que le piratage de Carmack était plus rapide jusqu'à certaines valeurs de N. Ensuite, vous devriez vérifier le "n" et s'il est inférieur à ce nombre N, utilisez le piratage de Carmack, sinon utilisez une autre méthode décrite dans les réponses ici.
la source
C'est l'implémentation Java la plus rapide que j'ai pu trouver, en utilisant une combinaison de techniques suggérées par d'autres dans ce fil.
J'ai également expérimenté ces modifications mais elles n'ont pas amélioré les performances:
la source
Vous devez vous débarrasser de la partie à 2 puissances de N dès le début.
2nd Edit L'expression magique pour m ci-dessous devrait être
et pas comme écrit
Fin du 2e montage
1ère édition:
Amélioration mineure:
Fin du 1er montage
Continuez maintenant comme d'habitude. De cette façon, au moment où vous arrivez à la partie à virgule flottante, vous vous êtes déjà débarrassé de tous les nombres dont la partie à 2 puissances est impaire (environ la moitié), puis vous ne considérez que 1/8 de ce qui reste. C'est-à-dire que vous exécutez la partie à virgule flottante sur 6% des nombres.
la source
Le projet Euler est mentionné dans les balises et de nombreux problèmes nécessitent une vérification des numéros >>
2^64
. La plupart des optimisations mentionnées ci-dessus ne fonctionnent pas facilement lorsque vous travaillez avec un tampon de 80 octets.J'ai utilisé Java BigInteger et une version légèrement modifiée de la méthode de Newton, qui fonctionne mieux avec des entiers. Le problème était que les carrés exacts
n^2
convergeaient au(n-1)
lieu den
parce quen^2-1 = (n-1)(n+1)
et l'erreur finale était juste une étape en dessous du diviseur final et l'algorithme s'est terminé. Il a été facile à corriger en ajoutant un à l'argument d'origine avant de calculer l'erreur. (Ajoutez deux pour les racines cubiques, etc.)Un attribut intéressant de cet algorithme est que vous pouvez immédiatement dire si le nombre est un carré parfait - l'erreur finale (pas de correction) dans la méthode de Newton sera nulle. Une simple modification vous permet également de calculer rapidement
floor(sqrt(x))
au lieu de l'entier le plus proche. C'est pratique avec plusieurs problèmes d'Euler.la source
Il s'agit d'un remaniement de la décimale au binaire de l'ancien algorithme de la calculatrice Marchant (désolé, je n'ai pas de référence), en Ruby, adapté spécifiquement pour cette question:
Voici un résumé de quelque chose de similaire (s'il vous plaît ne me votez pas pour le style de codage / les odeurs ou les O / O maladroits - c'est l'algorithme qui compte, et C ++ n'est pas ma langue maternelle). Dans ce cas, nous recherchons des résidus == 0:
la source
L'appel sqrt n'est pas parfaitement précis, comme cela a été mentionné, mais il est intéressant et instructif qu'il n'épuise pas les autres réponses en termes de vitesse. Après tout, la séquence d'instructions du langage d'assemblage pour un sqrt est minuscule. Intel a une instruction matérielle, qui n'est pas utilisée par Java, je crois, car elle n'est pas conforme à IEEE.
Alors pourquoi est-ce lent? Parce que Java appelle en fait une routine C via JNI, et il est en fait plus lent de le faire que d'appeler un sous-programme Java, lui-même plus lent que de le faire en ligne. C'est très ennuyeux, et Java aurait dû trouver une meilleure solution, c'est-à-dire intégrer des appels de bibliothèque à virgule flottante si nécessaire. Tant pis.
En C ++, je soupçonne que toutes les alternatives complexes perdraient de la vitesse, mais je ne les ai pas toutes vérifiées. Ce que j'ai fait, et ce que les gens de Java trouveront utile, est un simple hack, une extension des tests de cas spéciaux suggérés par A. Rex. Utilisez une seule valeur longue comme tableau de bits, qui n'est pas vérifiée. De cette façon, vous avez une recherche booléenne 64 bits.
La routine isPerfectSquare5 s'exécute environ 1/3 du temps sur ma machine core2 duo. Je soupçonne que de nouveaux ajustements dans le même sens pourraient réduire davantage le temps en moyenne, mais chaque fois que vous vérifiez, vous échangez plus de tests pour plus d'élimination, vous ne pouvez donc pas aller trop loin sur cette route.
Certainement, plutôt que d'avoir un test séparé pour les négatifs, vous pouvez vérifier les 6 bits les plus élevés de la même manière.
Notez que tout ce que je fais est d'éliminer les carrés possibles, mais quand j'ai un cas potentiel, je dois appeler l'isPerfectSquare original et en ligne.
La routine init2 est appelée une fois pour initialiser les valeurs statiques de pp1 et pp2. Notez que dans mon implémentation en C ++, j'utilise non signé depuis longtemps, donc puisque vous êtes signé, vous devez utiliser l'opérateur >>>.
Il n'y a pas de besoin intrinsèque de vérifier le tableau, mais l'optimiseur de Java doit comprendre ce genre de choses assez rapidement, donc je ne leur en veux pas.
la source
pp2
? Je comprends quepp1
c'est utilisé pour tester les six bits les moins significatifs, mais je ne pense pas que tester les six bits suivants ait un sens.J'aime l'idée d'utiliser une méthode presque correcte sur certaines entrées. Voici une version avec un "offset" plus élevé. Le code semble fonctionner et passe mon cas de test simple.
Remplacez simplement votre:
code avec celui-ci:
la source
Compte tenu de la longueur générale des bits (bien que j'aie utilisé un type spécifique ici), j'ai essayé de concevoir un algo simpliste comme ci-dessous. Un contrôle simple et évident pour 0,1,2 ou <0 est requis au départ. Ce qui suit est simple en ce sens qu'il n'essaie pas d'utiliser les fonctions mathématiques existantes. La plupart des opérateurs peuvent être remplacés par des opérateurs bit à bit. Je n'ai cependant pas testé de données de référence. Je ne suis ni un expert en mathématiques ni en conception d'algorithmes informatiques en particulier, j'aimerais bien vous voir signaler un problème Je sais qu'il y a beaucoup de chances d'amélioration là-bas.
la source
J'ai vérifié tous les résultats possibles lorsque les n derniers bits d'un carré sont observés. En examinant successivement plus de bits, jusqu'à 5 / 6ème des entrées peuvent être éliminées. En fait, j'ai conçu cela pour implémenter l'algorithme de factorisation de Fermat, et c'est très rapide là-bas.
Le dernier bit de pseudocode peut être utilisé pour étendre les tests afin d'éliminer plus de valeurs. Les tests ci-dessus sont pour k = 0, 1, 2, 3
Il teste d'abord s'il a un résidu carré avec des modules de puissance de deux, puis il teste en fonction d'un module final, puis il utilise le Math.sqrt pour faire un test final. Je suis venu avec l'idée du poste supérieur, et j'ai essayé de la développer. J'apprécie tout commentaire ou suggestion.
Mise à jour: En utilisant le test par un module, (modSq) et une base de module de 44352, mon test s'exécute dans 96% du temps de celui de la mise à jour du PO pour des nombres allant jusqu'à 1 000 000 000.
la source
Voici une solution de division et de conquête.
Si la racine carrée d'un nombre naturel (
number
) est un nombre naturel (solution
), vous pouvez facilement déterminer une plage pour ensolution
fonction du nombre de chiffres denumber
:number
a 1 chiffre:solution
dans la plage = 1 - 4number
a 2 chiffres:solution
dans la plage = 3 - 10number
a 3 chiffres:solution
dans la plage = 10 - 40number
a 4 chiffres:solution
dans la plage = 30 - 100number
a 5 chiffres:solution
dans la plage = 100 - 400Remarquez la répétition?
Vous pouvez utiliser cette plage dans une approche de recherche binaire pour voir s'il existe un
solution
pour lequel:Voici le code
Voici ma classe SquareRootChecker
Et voici un exemple sur la façon de l'utiliser.
la source
toString
est une opération incroyablement coûteuse par rapport aux opérateurs au niveau du bit. Ainsi, pour satisfaire l'objectif de la question - les performances - vous devez utiliser des opérateurs au niveau du bit au lieu des chaînes de base 10. Encore une fois, j'aime vraiment votre concept. Néanmoins, votre implémentation (telle qu'elle est actuellement) est de loin la plus lente de toutes les solutions possibles affichées pour la question.Si la vitesse est un problème, pourquoi ne pas partitionner l'ensemble des entrées les plus couramment utilisées et leurs valeurs dans une table de recherche, puis faire l'algorithme magique optimisé que vous avez trouvé pour les cas exceptionnels?
la source
Il devrait être possible d'emballer le 'ne peut pas être un carré parfait si les X derniers chiffres sont N' beaucoup plus efficacement que cela! Je vais utiliser des entiers java 32 bits et produire suffisamment de données pour vérifier les 16 derniers bits du nombre - c'est 2048 valeurs int hexadécimales.
...
D'accord. Soit j'ai rencontré une théorie des nombres qui me dépasse un peu, soit il y a un bug dans mon code. En tout cas, voici le code:
et voici les résultats:
(ed: élidé pour de mauvaises performances dans prettify.js; voir l'historique des révisions pour voir.)
la source
Méthode de Newton avec arithmétique entière
Si vous souhaitez éviter les opérations non entières, vous pouvez utiliser la méthode ci-dessous. Il utilise essentiellement la méthode de Newton modifiée pour l'arithmétique entière.
Cette implémentation ne peut rivaliser avec les solutions qui utilisent
Math.sqrt
. Cependant, ses performances peuvent être améliorées en utilisant les mécanismes de filtrage décrits dans certains des autres articles.la source
Le calcul des racines carrées par la méthode de Newton est terriblement rapide ... à condition que la valeur de départ soit raisonnable. Cependant, il n'y a pas de valeur de départ raisonnable et, en pratique, nous terminons par un comportement de bissection et de log (2 ^ 64).
Pour être vraiment rapide, nous avons besoin d'un moyen rapide d'atteindre une valeur de départ raisonnable, et cela signifie que nous devons descendre dans le langage machine. Si un processeur fournit une instruction comme POPCNT dans le Pentium, cela compte les zéros de tête que nous pouvons utiliser pour avoir une valeur de départ avec la moitié des bits significatifs. Avec soin, nous pouvons trouver un nombre fixe d'étapes de Newton qui suffira toujours. (Ainsi, il est inutile de boucler et d'avoir une exécution très rapide.)
Une deuxième solution passe par la fonction de virgule flottante, qui peut avoir un calcul sqrt rapide (comme le coprocesseur i87.) Même une excursion via exp () et log () peut être plus rapide que Newton dégénéré en recherche binaire. Il y a un aspect délicat à cela, une analyse dépendante du processeur de quoi et si un raffinement par la suite est nécessaire.
Une troisième solution résout un problème légèrement différent, mais mérite d'être mentionnée car la situation est décrite dans la question. Si vous souhaitez calculer un grand nombre de racines carrées pour des nombres légèrement différents, vous pouvez utiliser l'itération de Newton, si vous ne réinitialisez jamais la valeur de départ, mais laissez-la simplement là où le calcul précédent s'était arrêté. Je l'ai utilisé avec succès dans au moins un problème Euler.
la source
Racine carrée d'un nombre, étant donné que le nombre est un carré parfait.
La complexité est log (n)
la source
Si vous voulez de la vitesse, étant donné que vos entiers sont de taille finie, je soupçonne que le moyen le plus rapide impliquerait (a) de partitionner les paramètres par taille (par exemple en catégories par le plus grand ensemble de bits), puis de vérifier la valeur par rapport à un tableau de carrés parfaits dans cette plage.
la source
En ce qui concerne la méthode Carmac, il semble qu'il serait assez facile de répéter une fois de plus, ce qui devrait doubler le nombre de chiffres de précision. C'est, après tout, une méthode itérative extrêmement tronquée - celle de Newton, avec une très bonne première supposition.
Concernant votre meilleur actuel, je vois deux micro-optimisations:
C'est à dire:
Encore mieux pourrait être un simple
Évidemment, il serait intéressant de savoir combien de numéros sont abattus à chaque point de contrôle - je doute que les contrôles soient vraiment indépendants, ce qui rend les choses délicates.
la source