Ich suche nach dem schnellsten Weg, um festzustellen, ob ein long
Wert ein perfektes Quadrat ist (dh seine Quadratwurzel ist eine andere ganze Zahl):
- Ich habe es auf einfache Weise mit der integrierten
Math.sqrt()
Funktion gemacht, aber ich frage mich, ob es eine Möglichkeit gibt, es schneller zu machen, indem Sie sich auf eine Nur-Ganzzahl-Domäne beschränken. - Das Verwalten einer Nachschlagetabelle ist unpraktisch (da es ungefähr 2 31,5 Ganzzahlen gibt, deren Quadrat kleiner als 2 63 ist ).
Hier ist die sehr einfache und unkomplizierte Art, wie ich es jetzt mache:
public final static boolean isPerfectSquare(long n)
{
if (n < 0)
return false;
long tst = (long)(Math.sqrt(n) + 0.5);
return tst*tst == n;
}
Hinweis: Ich verwende diese Funktion bei vielen Project Euler- Problemen. Niemand sonst wird diesen Code jemals pflegen müssen. Und diese Art der Mikrooptimierung könnte tatsächlich einen Unterschied machen, da ein Teil der Herausforderung darin besteht, jeden Algorithmus in weniger als einer Minute auszuführen, und diese Funktion muss bei einigen Problemen millionenfach aufgerufen werden.
Ich habe die verschiedenen Lösungen für das Problem ausprobiert:
- Nach ausführlichen Tests stellte ich fest, dass das Hinzufügen
0.5
zum Ergebnis von Math.sqrt () nicht erforderlich ist, zumindest nicht auf meinem Computer. - Die schnelle inverse Quadratwurzel war schneller, ergab jedoch falsche Ergebnisse für n> = 410881. Wie von BobbyShaftoe vorgeschlagen , können wir den FISR-Hack für n <410881 verwenden.
- Newtons Methode war ein bisschen langsamer als
Math.sqrt()
. Das liegt wahrscheinlich daranMath.sqrt()
etwas Ähnliches wie Newtons Methode verwendet wird, jedoch in der Hardware implementiert ist, sodass es viel schneller als in Java ist. Außerdem erforderte die Newtonsche Methode immer noch die Verwendung von Doppel. - Eine modifizierte Newton-Methode, bei der einige Tricks verwendet wurden, sodass nur ganzzahlige Mathematik beteiligt war, erforderte einige Hacks, um einen Überlauf zu vermeiden (ich möchte, dass diese Funktion mit allen positiven 64-Bit-Ganzzahlen mit Vorzeichen funktioniert), und sie war immer noch langsamer als
Math.sqrt()
. - Binary Chop war noch langsamer. Dies ist sinnvoll, da der binäre Chop durchschnittlich 16 Durchgänge benötigt, um die Quadratwurzel einer 64-Bit-Zahl zu finden.
- Laut Johns Tests ist die Verwendung von
or
Anweisungen in C ++ schneller als die Verwendung von aswitch
, aber in Java und C # scheint es keinen Unterschied zwischenor
und zu gebenswitch
. - Ich habe auch versucht, eine Nachschlagetabelle zu erstellen (als privates statisches Array mit 64 booleschen Werten). Dann
or
würde ich statt Schalter oder Anweisung einfach sagenif(lookup[(int)(n&0x3F)]) { test } else return false;
. Zu meiner Überraschung war dies (nur geringfügig) langsamer. Dies liegt daran, dass Array-Grenzen in Java überprüft werden .
((1<<(n&15))|65004) != 0
, anstatt drei separate Prüfungen durchzuführen .Antworten:
Ich habe eine Methode gefunden, die ~ 35% schneller funktioniert als Ihr 6-Bit + Carmack + SQL-Code, zumindest mit meiner CPU (x86) und Programmiersprache (C / C ++). Ihre Ergebnisse können variieren, insbesondere weil ich nicht weiß, wie sich der Java-Faktor auswirken wird.
Mein Ansatz ist dreifach:
int64 x
.)z = r - x * x
und setze t mit einem kleinen Trick als die größte Potenz von 2, die z teilt. Dadurch kann ich t-Werte überspringen, die den Wert von r ohnehin nicht beeinflusst hätten. Der vorberechnete Startwert in meinem Fall wählt das "kleinste positive" Quadratwurzelmodulo 8192 aus.Auch wenn dieser Code für Sie nicht schneller funktioniert, hoffe ich, dass Ihnen einige der darin enthaltenen Ideen gefallen. Es folgt ein vollständiger, getesteter Code, einschließlich der vorberechneten Tabellen.
quelle
9 < 0 => false
,9&2 => 0
,9&7 == 5 => false
,9&11 == 8 => false
.Ich bin ziemlich spät zur Party, aber ich hoffe, eine bessere Antwort zu geben; kürzer und (vorausgesetzt mein Benchmark ist korrekt) auch viel schneller .
Der erste Test fängt die meisten Nichtquadrate schnell ab. Es wird eine Tabelle mit 64 Elementen verwendet, die in einer langen Tabelle gepackt ist, sodass keine Kosten für den Arrayzugriff anfallen (Indirektion und Grenzüberprüfung). Für einen einheitlich zufälligen Zufall
long
besteht eine Wahrscheinlichkeit von 81,25%, hier zu enden.Der zweite Test erfasst alle Zahlen mit einer ungeraden Anzahl von Zweien in ihrer Faktorisierung. Die Methode
Long.numberOfTrailingZeros
ist sehr schnell, da sie JIT-ed in einen einzelnen i86-Befehl umwandelt.Nach dem Löschen der nachfolgenden Nullen behandelt der dritte Test Zahlen, die mit 011, 101 oder 111 in Binärform enden und keine perfekten Quadrate sind. Es kümmert sich auch um negative Zahlen und behandelt auch 0.
Der letzte Test fällt auf die
double
Arithmetik zurück. Dadouble
nur 53-Bit-Mantisse vorhanden ist, umfasst die Konvertierung vonlong
nachdouble
eine Rundung für große Werte. Trotzdem ist der Test korrekt (es sei denn, der Beweis ist falsch).Der Versuch, die Idee von mod255 zu integrieren, war nicht erfolgreich.
quelle
goodMask
Test macht es, aber er macht es vor der richtigen Schicht. Sie müssten es also wiederholen, aber auf diese Weise ist es einfacher und AFAIK ein kleines bisschen schneller und gleich gut.if ((x & (7 | Integer.MIN_VALUE)) != 1) return x == 0;
.Sie müssen ein Benchmarking durchführen. Der beste Algorithmus hängt von der Verteilung Ihrer Eingaben ab.
Ihr Algorithmus ist möglicherweise nahezu optimal, Sie sollten jedoch eine schnelle Überprüfung durchführen, um einige Möglichkeiten auszuschließen, bevor Sie Ihre Quadratwurzel-Routine aufrufen. Schauen Sie sich zum Beispiel die letzte Ziffer Ihrer Zahl in hexadezimaler Form an, indem Sie ein bisschen "und" eingeben. Perfekte Quadrate können nur mit 0, 1, 4 oder 9 in Basis 16 enden. Für 75% Ihrer Eingaben (vorausgesetzt, sie sind gleichmäßig verteilt) können Sie einen Aufruf der Quadratwurzel im Austausch für ein sehr schnelles Bit-Twiddling vermeiden.
Kip hat den folgenden Code mit dem Hex-Trick verglichen. Beim Testen der Nummern 1 bis 100.000.000 lief dieser Code doppelt so schnell wie das Original.
Als ich den analogen Code in C ++ getestet habe, lief er tatsächlich langsamer als das Original. Als ich jedoch die switch-Anweisung eliminierte, machte der Hex-Trick den Code erneut doppelt so schnell.
Das Eliminieren der switch-Anweisung hatte nur geringe Auswirkungen auf den C # -Code.
quelle
Ich dachte an die schrecklichen Zeiten, die ich im Kurs Numerische Analyse verbracht habe.
Und dann erinnere ich mich, dass diese Funktion aus dem Quake-Quellcode im Netz kreiste:
Was im Grunde genommen eine Quadratwurzel unter Verwendung der Newtonschen Approximationsfunktion berechnet (ich kann mich nicht an den genauen Namen erinnern).
Es sollte brauchbar sein und könnte sogar schneller sein, es stammt aus einem der phänomenalen ID-Software-Spiele!
Es ist in C ++ geschrieben, aber es sollte nicht zu schwierig sein, dieselbe Technik in Java wiederzuverwenden, sobald Sie die Idee haben:
Ich fand es ursprünglich unter: http://www.codemaestro.com/reviews/9
Newtons Methode wird auf Wikipedia erklärt: http://en.wikipedia.org/wiki/Newton%27s_method
Sie können dem Link folgen, um weitere Erklärungen zur Funktionsweise zu erhalten. Wenn Sie sich jedoch nicht viel darum kümmern, ist dies ungefähr das, woran ich mich beim Lesen des Blogs und beim Besuch des Kurses für numerische Analyse erinnere:
* (long*) &y
ist im Prinzip eine schnelle convert-to-Funktion so lange Ganzzahl - Operationen an den unformatierten Bytes angewendet werden kann.0x5f3759df - (i >> 1);
Linie ist ein vorberechneter Startwert für die Approximationsfunktion.* (float*) &i
konvertiert den Wert zurück in Gleitkomma.y = y * ( threehalfs - ( x2 * y * y ) )
Zeile iteriert den Wert erneut grundlegend über die Funktion.Die Approximationsfunktion liefert genauere Werte, je mehr Sie die Funktion über das Ergebnis iterieren. In Quakes Fall ist eine Iteration "gut genug", aber wenn Sie nicht wären ... dann könnten Sie so viel Iteration hinzufügen, wie Sie benötigen.
Dies sollte schneller sein, da dadurch die Anzahl der Divisionsoperationen, die beim naiven Quadratwurzeln ausgeführt werden, auf eine einfache Division durch 2 reduziert wird (eigentlich eine
* 0.5F
Multiplikationsoperation) und stattdessen durch einige feste Multiplikationsoperationen ersetzt wird.quelle
Ich bin mir nicht sicher, ob es schneller oder sogar genauer wäre, aber Sie könnten John Carmacks Magical Square Root- Algorithmus verwenden, um die Quadratwurzel schneller zu lösen. Sie könnten dies wahrscheinlich leicht für alle möglichen 32-Bit-Ganzzahlen testen und überprüfen, ob Sie tatsächlich korrekte Ergebnisse erhalten haben, da dies nur eine Annäherung ist. Jetzt, wo ich darüber nachdenke, ist die Verwendung von Doppel auch ungefähr, daher bin ich mir nicht sicher, wie das ins Spiel kommen würde.
quelle
Wenn Sie einen binären Schnitt ausführen, um die "richtige" Quadratwurzel zu finden, können Sie ziemlich leicht feststellen, ob der Wert, den Sie haben, nahe genug ist, um Folgendes zu erkennen:
Nach der Berechnung
n^2
stehen folgende Optionen zur Verfügung:n^2 = target
: erledigt, return truen^2 + 2n + 1 > target > n^2
: Sie sind nah dran, aber es ist nicht perfekt: Geben Sie false zurückn^2 - 2n + 1 < target < n^2
: ditotarget < n^2 - 2n + 1
: binärer Chop auf einem niedrigerenn
target > n^2 + 2n + 1
: binärer Chop auf einem höherenn
(Entschuldigung, dies wird
n
als Ihre aktuelle Vermutung undtarget
für den Parameter verwendet. Entschuldigen Sie die Verwirrung!)Ich weiß nicht, ob das schneller geht oder nicht, aber es ist einen Versuch wert.
BEARBEITEN: Der binäre Chop muss auch nicht den gesamten Bereich von ganzen Zahlen
(2^x)^2 = 2^(2x)
berücksichtigen. Wenn Sie also das oberste gesetzte Bit in Ihrem Ziel gefunden haben (was mit einem Trick gemacht werden kann; ich vergesse genau, wie) Sie können schnell eine Reihe möglicher Antworten erhalten. Wohlgemerkt, ein naiver binärer Chop dauert immer noch nur bis zu 31 oder 32 Iterationen.quelle
Ich habe meine eigene Analyse mehrerer Algorithmen in diesem Thread durchgeführt und einige neue Ergebnisse erzielt. Sie können diese alten Ergebnisse im Bearbeitungsverlauf dieser Antwort sehen, aber sie sind nicht korrekt, da ich einen Fehler gemacht habe und Zeit damit verschwendet habe, mehrere Algorithmen zu analysieren, die nicht in der Nähe sind. Da ich jedoch Lehren aus verschiedenen Antworten gezogen habe, habe ich jetzt zwei Algorithmen, die den "Gewinner" dieses Threads vernichten. Hier ist das Kernstück, das ich anders mache als alle anderen:
Diese einfache Zeile, die meistens ein oder zwei sehr schnelle Anweisungen hinzufügt, vereinfacht das jedoch erheblich
switch-case
Anweisung jedoch zu einer if-Anweisung. Es kann jedoch zur Laufzeit beitragen, wenn viele der getesteten Zahlen signifikante Zweierpotenzfaktoren aufweisen.Die folgenden Algorithmen sind wie folgt:
Hier ist eine Beispiellaufzeit, wenn die Zahlen mit generiert werden
Math.abs(java.util.Random.nextLong())
Und hier ist eine Beispiellaufzeit, wenn sie nur auf den ersten Millionen Longs ausgeführt wird:
Wie Sie sehen können,
DurronTwo
eignet es sich besser für große Eingaben, da der Zaubertrick sehr oft verwendet wird, aber im Vergleich zum ersten Algorithmus überlastet wird undMath.sqrt
die Zahlen so viel kleiner sind. Inzwischen ist das einfacherDurron
ein großer Gewinner, da er in den ersten Millionen Zahlen nie viele Male durch 4 dividieren muss.Hier ist
Durron
:Und
DurronTwo
Und mein Benchmark-Gurt: (Benötigt Google Caliper 0.1-rc5)
UPDATE: Ich habe einen neuen Algorithmus entwickelt, der in einigen Szenarien schneller und in anderen langsamer ist. Ich habe unterschiedliche Benchmarks basierend auf unterschiedlichen Eingaben erhalten. Wenn wir Modulo berechnen
0xFFFFFF = 3 x 3 x 5 x 7 x 13 x 17 x 241
, können wir 97,82% der Zahlen eliminieren, die keine Quadrate sein können. Dies kann (irgendwie) in einer Zeile mit 5 bitweisen Operationen erfolgen:Der resultierende Index ist entweder 1) der Rückstand, 2) der Rückstand
+ 0xFFFFFF
oder 3) der Rückstand+ 0x1FFFFFE
. Natürlich brauchen wir eine Nachschlagetabelle für Reste Modulo0xFFFFFF
, bei der es sich um eine 3-MB-Datei handelt (in diesem Fall als ASCII-Text-Dezimalzahlen gespeichert, nicht optimal, aber mit aByteBuffer
und so weiter eindeutig verbesserbar . Aber da dies eine Vorberechnung ist, ist dies nicht der Fall.) Es ist nicht so wichtig. Sie können die Datei hier finden (oder selbst generieren):Ich lade es in ein
boolean
Array wie folgt:Beispiel Laufzeit. Es schlug
Durron
(Version eins) in jedem Versuch, den ich lief.quelle
sqrtps
Durchsatz oder sogarsqrtpd
(doppelte Genauigkeit) sind bei Skylake nicht schlecht, aber nicht viel besser als die Latenz bei alten CPUs. Wie auch immer, 7-cpu.com/cpu/Haswell.html hat einige nette experimentelle Zahlen und Seiten für andere CPUs. Agner Fogs Microarchdouble
Präzision, um zu vermeiden, dass eine Ganzzahl außerhalb des Bereichs + -2 ^ 24 gerundet wird (eine 32-Bit-Ganzzahl kann also außerhalb dieses Bereichs liegen). Siesqrtpd
ist langsamer alssqrtps
und verarbeitet nur halb so viele Elemente pro Befehl (pro SIMD-Vektor). .Es sollte viel schneller sein, die Newtonsche Methode zur Berechnung der ganzzahligen Quadratwurzel zu verwenden, diese Zahl dann zu quadrieren und zu überprüfen, wie Sie es in Ihrer aktuellen Lösung tun. Die Newtonsche Methode ist die Grundlage für die in einigen anderen Antworten erwähnte Carmack-Lösung. Sie sollten in der Lage sein, eine schnellere Antwort zu erhalten, da Sie nur an dem ganzzahligen Teil der Wurzel interessiert sind, sodass Sie den Approximationsalgorithmus früher stoppen können.
Eine weitere Optimierung, die Sie ausprobieren können: Wenn die digitale Wurzel einer Zahl nicht mit 1, 4, 7 oder 9 endet, ist die Zahl kein perfektes Quadrat. Dies kann als schnelle Methode verwendet werden, um 60% Ihrer Eingaben zu eliminieren, bevor der langsamere Quadratwurzel-Algorithmus angewendet wird.
quelle
Math.sqrt()
funktioniert mit doppelten Eingabeparametern, sodass Sie für Ganzzahlen größer als 2 ^ 53 keine genauen Ergebnisse erhalten .quelle
Ein anderer Ansatz besteht darin, die Hauptzerlegung zu verwenden. Wenn jeder Faktor der Zerlegung gerade ist, ist die Zahl ein perfektes Quadrat. Sie möchten also sehen, ob eine Zahl als Produkt aus Quadraten von Primzahlen zerlegt werden kann. Natürlich müssen Sie eine solche Zerlegung nicht erhalten, nur um zu sehen, ob sie existiert.
Erstellen Sie zuerst eine Tabelle mit Quadraten von Primzahlen, die niedriger als 2 ^ 32 sind. Dies ist weitaus kleiner als eine Tabelle aller Ganzzahlen bis zu dieser Grenze.
Eine Lösung wäre dann:
Ich denke, es ist ein bisschen kryptisch. In jedem Schritt wird überprüft, ob das Quadrat einer Primzahl die eingegebene Zahl teilt. Wenn dies der Fall ist, wird die Zahl so lange wie möglich durch das Quadrat geteilt, um dieses Quadrat aus der Hauptzerlegung zu entfernen. Wenn wir durch diesen Prozess zu 1 kamen, war die eingegebene Zahl eine Zerlegung des Quadrats der Primzahlen. Wenn das Quadrat größer als die Zahl selbst wird, kann dieses Quadrat oder größere Quadrate es auf keinen Fall teilen, sodass die Zahl keine Zerlegung von Quadraten von Primzahlen sein kann.
Angesichts der heutigen Hardware und der Notwendigkeit, hier Primzahlen zu berechnen, ist diese Lösung wahrscheinlich viel langsamer. Aber es sollte bessere Ergebnisse liefern als eine Lösung mit sqrt, die über 2 ^ 54 nicht funktioniert, wie mrzl in seiner Antwort sagt.
quelle
sqrtsd
Durchsatz von Core2 eins pro 6-58c. Esidiv
ist eins pro 12-36 Zyklen. (Latenzen ähnlich wie Durchsätze: Keine Einheit ist über eine Pipeline verbunden).Es wurde darauf hingewiesen, dass die letzten
d
Ziffern eines perfekten Quadrats nur bestimmte Werte annehmen können. Die letztend
Ziffern (in der Basisb
) einer Zahln
sind die gleichen wie der Rest, wenn sien
durch geteilt werdenb
d
, d. H. in C-Notationn % pow(b, d)
.Dies kann auf jeden Modul verallgemeinert werden
m
, dh.n % m
kann verwendet werden, um einen bestimmten Prozentsatz von Zahlen als perfekte Quadrate auszuschließen. Der Modul, den Sie derzeit verwenden, ist 64, was 12 erlaubt, dh. 19% der Reste als mögliche Quadrate. Mit ein wenig Codierung fand ich den Modul 110880, der nur 2016 erlaubt, dh. 1,8% der verbleibenden Quadrate. Abhängig von den Kosten einer Moduloperation (dh Division) und einer Tabellensuche im Vergleich zu einer Quadratwurzel auf Ihrem Computer ist die Verwendung dieses Moduls möglicherweise schneller.Übrigens, wenn Java eine Möglichkeit hat, ein gepacktes Array von Bits für die Nachschlagetabelle zu speichern, verwenden Sie es nicht. 110880 32-Bit-Wörter sind heutzutage nicht viel RAM und das Abrufen eines Maschinenworts ist schneller als das Abrufen eines einzelnen Bits.
quelle
idiv
Kosten für Integer Division ( ) sindsqrtsd
auf der aktuellen x86-Hardware gleich oder schlechter als bei FP sqrt ( ). Nicht einverstanden mit der Vermeidung von Bitfeldern. Die Cache-Trefferquote ist mit einem Bitfeld um ein Vielfaches besser, und das Testen eines Bits in einem Bitfeld ist nur ein oder zwei einfachere Anweisungen als das Testen eines ganzen Bytes. (Für winzige Tabellen, die auch als Nicht-Bitfelder in den Cache passen, ist ein Byte-Array am besten geeignet, nicht 32-Bit-Ints. X86 verfügt über einen Einzelbyte-Zugriff mit der gleichen Geschwindigkeit wie 32-Bit-Wörter.)Ein ganzzahliges Problem verdient eine ganzzahlige Lösung. Somit
Führen Sie eine binäre Suche nach den (nicht negativen) Ganzzahlen durch, um die größte Ganzzahl t so zu finden, dass
t**2 <= n
. Dann testen Sie obr**2 = n
genau. Dies dauert einige Zeit O (log n).Wenn Sie nicht wissen, wie man die positiven ganzen Zahlen binär durchsucht, weil die Menge unbegrenzt ist, ist es einfach. Sie beginnen mit der Berechnung Ihrer zunehmenden Funktion f (oben
f(t) = t**2 - n
) auf Zweierpotenzen. Wenn Sie sehen, dass es positiv wird, haben Sie eine Obergrenze gefunden. Dann können Sie eine standardmäßige binäre Suche durchführen.quelle
O((log n)^2)
weil die Multiplikation nicht zeitkonstant ist, sondern tatsächlich eine Untergrenze von hatO(log n)
, was bei der Arbeit mit großen Zahlen mit mehrfacher Genauigkeit deutlich wird. Aber der Umfang dieses Wikis scheint 64-Bit zu sein, also ist es vielleicht nbd.Die folgende Vereinfachung der Lösung von maaartinus scheint die Laufzeit um einige Prozentpunkte zu verkürzen, aber ich bin nicht gut genug im Benchmarking, um einen Benchmark zu erstellen, dem ich vertrauen kann:
Es lohnt sich zu prüfen, wie der erste Test weggelassen wird.
würde die Leistung beeinträchtigen.
quelle
Für die Leistung müssen Sie sehr oft einige Komprimierungen durchführen. Andere haben verschiedene Methoden ausgedrückt. Sie haben jedoch festgestellt, dass Carmacks Hack bis zu bestimmten Werten von N schneller war. Dann sollten Sie das "n" überprüfen. Wenn es kleiner als diese Zahl N ist, verwenden Sie Carmacks Hack, andernfalls verwenden Sie eine andere beschriebene Methode in den Antworten hier.
quelle
Dies ist die schnellste Java-Implementierung, die ich mit einer Kombination von Techniken entwickeln konnte, die von anderen in diesem Thread vorgeschlagen wurden.
Ich habe auch mit diesen Modifikationen experimentiert, aber sie haben die Leistung nicht verbessert:
quelle
Sie sollten den 2-Potenz-Teil von N von Anfang an loswerden.
2. Bearbeiten Der magische Ausdruck für m unten sollte sein
und nicht wie geschrieben
Ende der 2. Bearbeitung
1. Bearbeitung:
Kleinere Verbesserung:
Ende der 1. Bearbeitung
Fahren Sie nun wie gewohnt fort. Auf diese Weise haben Sie zu dem Zeitpunkt, an dem Sie zum Gleitkomma-Teil gelangen, bereits alle Zahlen entfernt, deren 2-Potenz-Teil ungerade ist (ungefähr die Hälfte), und dann berücksichtigen Sie nur noch 1/8 von dem, was übrig ist. Dh Sie führen den Gleitkommateil auf 6% der Zahlen aus.
quelle
Project Euler wird in den Tags erwähnt und viele der darin enthaltenen Probleme erfordern die Überprüfung von Nummern >>
2^64
. Die meisten der oben genannten Optimierungen funktionieren nicht einfach, wenn Sie mit einem 80-Byte-Puffer arbeiten.Ich habe Java BigInteger und eine leicht modifizierte Version von Newtons Methode verwendet, die besser mit ganzen Zahlen funktioniert. Das Problem war, dass exakte Quadrate
n^2
konvergierten(n-1)
statt zun
weiln^2-1 = (n-1)(n+1)
und der endgültige Fehler nur einen Schritt unter dem endgültigen Teiler lag und der Algorithmus beendet wurde. Es war einfach zu beheben, indem man dem ursprünglichen Argument eins hinzufügte, bevor der Fehler berechnet wurde. (Fügen Sie zwei für Kubikwurzeln usw. hinzu.)Ein schönes Attribut dieses Algorithmus ist, dass Sie sofort erkennen können, ob die Zahl ein perfektes Quadrat ist - der endgültige Fehler (keine Korrektur) in Newtons Methode ist Null. Mit einer einfachen Änderung können Sie auch schnell
floor(sqrt(x))
anstelle der nächsten Ganzzahl berechnen . Dies ist praktisch bei mehreren Euler-Problemen.quelle
Dies ist eine Überarbeitung des alten Marchant-Rechner-Algorithmus (sorry, ich habe keine Referenz) von dezimal nach binär in Ruby, die speziell für diese Frage angepasst wurde:
Hier ist eine Aufarbeitung von etwas Ähnlichem (bitte stimmen Sie mich nicht für Codierungsstil / Gerüche oder klobiges O / O ab - es ist der Algorithmus, der zählt, und C ++ ist nicht meine Muttersprache). In diesem Fall suchen wir nach Rest == 0:
quelle
Der sqrt-Aufruf ist, wie bereits erwähnt, nicht genau, aber es ist interessant und lehrreich, dass er die anderen Antworten in Bezug auf die Geschwindigkeit nicht wegbläst. Immerhin ist die Reihenfolge der Assembler-Anweisungen für ein sqrt winzig. Intel hat eine Hardware-Anweisung, die meines Erachtens nicht von Java verwendet wird, da sie nicht mit IEEE übereinstimmt.
Warum ist es langsam? Weil Java tatsächlich eine C-Routine über JNI aufruft und dies tatsächlich langsamer ist als das Aufrufen einer Java-Subroutine, die selbst langsamer ist als die Inline-Routine. Dies ist sehr ärgerlich, und Java hätte eine bessere Lösung finden sollen, dh bei Bedarf Gleitkomma-Bibliotheksaufrufe einzubauen. Naja.
Ich vermute, dass in C ++ alle komplexen Alternativen an Geschwindigkeit verlieren würden, aber ich habe sie nicht alle überprüft. Was ich getan habe und was Java-Leute nützlich finden werden, ist ein einfacher Hack, eine Erweiterung der von A. Rex vorgeschlagenen Sonderfalltests. Verwenden Sie einen einzelnen langen Wert als Bit-Array, dessen Grenzen nicht überprüft werden. Auf diese Weise haben Sie eine 64-Bit-Boolesche Suche.
Die Routine isPerfectSquare5 läuft auf meinem Core2-Duo-Computer in etwa 1/3 der Zeit. Ich vermute, dass weitere Optimierungen in der gleichen Richtung die Zeit im Durchschnitt weiter verkürzen könnten, aber jedes Mal, wenn Sie dies überprüfen, tauschen Sie mehr Tests gegen mehr Eliminierung aus, sodass Sie auf dieser Straße nicht zu weit gehen können.
Anstatt einen separaten Test auf Negativ durchzuführen, können Sie die hohen 6 Bits auf die gleiche Weise überprüfen.
Beachten Sie, dass ich nur mögliche Quadrate eliminiere, aber wenn ich einen potenziellen Fall habe, muss ich das ursprüngliche, inline eingefügte isPerfectSquare aufrufen.
Die Routine init2 wird einmal aufgerufen, um die statischen Werte von pp1 und pp2 zu initialisieren. Beachten Sie, dass ich in meiner Implementierung in C ++ Long Long ohne Vorzeichen verwende. Da Sie also signiert sind, müssen Sie den Operator >>> verwenden.
Es ist nicht unbedingt erforderlich, das Array zu überprüfen, aber Javas Optimierer muss dies ziemlich schnell herausfinden, deshalb beschuldige ich sie nicht dafür.
quelle
pp2
? Ich verstehe, dass diespp1
zum Testen der sechs niedrigstwertigen Bits verwendet wird, aber ich glaube nicht, dass das Testen der nächsten sechs Bits Sinn macht.Ich mag die Idee, bei einigen Eingaben eine fast korrekte Methode zu verwenden. Hier ist eine Version mit einem höheren "Offset". Der Code scheint zu funktionieren und besteht meinen einfachen Testfall.
Ersetzen Sie einfach Ihre:
Code mit diesem:
quelle
In Anbetracht der allgemeinen Bitlänge (obwohl ich hier einen bestimmten Typ verwendet habe) habe ich versucht, ein vereinfachtes Algo wie folgt zu entwerfen. Zunächst ist eine einfache und offensichtliche Prüfung auf 0,1,2 oder <0 erforderlich. Das Folgende ist in dem Sinne einfach, dass es nicht versucht, vorhandene mathematische Funktionen zu verwenden. Die meisten Operatoren können durch bitweise Operatoren ersetzt werden. Ich habe jedoch keine Benchmark-Daten getestet. Ich bin weder Experte für Mathematik noch für das Design von Computeralgorithmen. Ich würde mich freuen, wenn Sie auf ein Problem hinweisen. Ich weiß, dass es dort viele Verbesserungschancen gibt.
quelle
Ich habe alle möglichen Ergebnisse überprüft, wenn die letzten n Bits eines Quadrats beobachtet werden. Durch sukzessives Untersuchen von mehr Bits können bis zu 5/6 der Eingänge eliminiert werden. Ich habe dies tatsächlich entworfen, um den Faktorisierungsalgorithmus von Fermat zu implementieren, und es ist dort sehr schnell.
Das letzte Bit des Pseudocodes kann verwendet werden, um die Tests zu erweitern und mehr Werte zu eliminieren. Die obigen Tests gelten für k = 0, 1, 2, 3
Es testet zuerst, ob es einen quadratischen Rest mit einem Potenzmodul von zwei hat, dann testet es basierend auf einem Endmodul und verwendet dann Math.sqrt, um einen Endtest durchzuführen. Ich kam auf die Idee vom obersten Beitrag und versuchte, sie zu erweitern. Ich freue mich über Kommentare oder Vorschläge.
Update: Unter Verwendung des Tests mit einem Modul (modSq) und einer Modulbasis von 44352 läuft mein Test in 96% der Zeit des Tests im OP-Update für Zahlen bis zu 1.000.000.000.
quelle
Hier ist eine Lösung zum Teilen und Erobern.
Wenn die Quadratwurzel einer natürlichen Zahl (
number
) eine natürliche Zahl (solution
) ist, können Sie einen Bereich fürsolution
basierend auf der Anzahl der Ziffern vonnumber
:number
hat 1 Stelle:solution
im Bereich = 1 - 4number
hat 2 Ziffern:solution
im Bereich = 3 - 10number
hat 3 Ziffern:solution
im Bereich = 10 - 40number
hat 4 Ziffern:solution
im Bereich = 30 - 100number
hat 5 Ziffern:solution
im Bereich = 100 - 400Beachten Sie die Wiederholung?
Sie können diesen Bereich in einem binären Suchansatz verwenden, um festzustellen, ob es einen gibt,
solution
für den:Hier ist der Code
Hier ist meine Klasse SquareRootChecker
Und hier ist ein Beispiel, wie man es benutzt.
quelle
toString
ist im Vergleich zu bitweisen Operatoren eine unglaublich teure Operation. Um das Ziel der Frage - Leistung - zu erfüllen, müssen Sie daher bitweise Operatoren anstelle von Zeichenfolgen der Basis 10 verwenden. Auch hier gefällt mir Ihr Konzept sehr gut. Ungeachtet dessen ist Ihre Implementierung (wie sie jetzt ist) bei weitem die langsamste aller möglichen Lösungen, die für die Frage veröffentlicht wurden.Wenn es um Geschwindigkeit geht, können Sie die am häufigsten verwendeten Eingaben und ihre Werte in eine Nachschlagetabelle aufteilen und dann den optimierten magischen Algorithmus ausführen, den Sie für die Ausnahmefälle entwickelt haben.
quelle
Es sollte möglich sein, das 'kann kein perfektes Quadrat sein, wenn die letzten X-Ziffern N sind' viel effizienter zu packen! Ich werde Java 32-Bit-Ints verwenden und genügend Daten erzeugen, um die letzten 16 Bits der Zahl zu überprüfen - das sind 2048 hexadezimale Int-Werte.
...
OK. Entweder bin ich auf eine Zahlentheorie gestoßen, die ein wenig über mich hinausgeht, oder es gibt einen Fehler in meinem Code. In jedem Fall ist hier der Code:
und hier sind die Ergebnisse:
(ed: elided für schlechte Leistung in prettify.js; Revisionsverlauf anzeigen, um zu sehen.)
quelle
Newtonsche Methode mit ganzzahliger Arithmetik
Wenn Sie nicht ganzzahlige Operationen vermeiden möchten, können Sie die folgende Methode verwenden. Grundsätzlich wird die für die Ganzzahlarithmetik modifizierte Newtonsche Methode verwendet.
Diese Implementierung kann nicht mit verwendeten Lösungen konkurrieren
Math.sqrt
. Die Leistung kann jedoch durch Verwendung der in einigen anderen Beiträgen beschriebenen Filtermechanismen verbessert werden.quelle
Die Berechnung der Quadratwurzeln nach Newtons Methode ist entsetzlich schnell ... vorausgesetzt, der Startwert ist angemessen. Es gibt jedoch keinen vernünftigen Startwert, und in der Praxis enden wir mit Halbierungs- und Protokollverhalten (2 ^ 64).
Um wirklich schnell zu sein, brauchen wir einen schnellen Weg, um zu einem vernünftigen Startwert zu gelangen, und das bedeutet, dass wir in die Maschinensprache absteigen müssen. Wenn ein Prozessor im Pentium einen Befehl wie POPCNT bereitstellt, der die führenden Nullen zählt, können wir diesen verwenden, um einen Startwert mit der Hälfte der signifikanten Bits zu erhalten. Mit Sorgfalt können wir eine feste Anzahl von Newton-Schritten finden, die immer ausreichen wird. (Somit entfällt die Notwendigkeit einer Schleife und einer sehr schnellen Ausführung.)
Eine zweite Lösung ist die Gleitkommafunktion, die eine schnelle SQL-Berechnung haben kann (wie der i87-Coprozessor). Selbst eine Exkursion über exp () und log () kann schneller sein als Newton, der zu einer binären Suche entartet ist. Dies hat einen kniffligen Aspekt: Eine prozessorabhängige Analyse dessen, was und ob eine weitere Verfeinerung erforderlich ist.
Eine dritte Lösung löst ein etwas anderes Problem, ist jedoch erwähnenswert, da die Situation in der Frage beschrieben wird. Wenn Sie sehr viele Quadratwurzeln für Zahlen berechnen möchten, die sich geringfügig unterscheiden, können Sie die Newton-Iteration verwenden, wenn Sie den Startwert nie neu initialisieren, sondern ihn einfach dort belassen, wo die vorherige Berechnung aufgehört hat. Ich habe dies mit Erfolg in mindestens einem Euler-Problem verwendet.
quelle
Quadratwurzel einer Zahl, vorausgesetzt, die Zahl ist ein perfektes Quadrat.
Die Komplexität ist log (n)
quelle
Wenn Sie Geschwindigkeit wünschen, da Ihre Ganzzahlen eine endliche Größe haben, besteht der schnellste Weg vermutlich darin, (a) die Parameter nach Größe zu partitionieren (z. B. nach größter Bitmenge in Kategorien) und dann den Wert anhand eines Arrays perfekter Quadrate zu überprüfen innerhalb dieses Bereichs.
quelle
In Bezug auf die Carmac-Methode scheint es ziemlich einfach zu sein, nur noch einmal zu iterieren, was die Anzahl der Genauigkeitsstellen verdoppeln sollte. Es ist immerhin eine extrem verkürzte iterative Methode - Newtons, mit einer sehr guten ersten Vermutung.
In Bezug auf Ihr aktuelles Bestes sehe ich zwei Mikrooptimierungen:
Dh:
Noch besser könnte ein einfacher sein
Natürlich wäre es interessant zu wissen, wie viele Zahlen an jedem Kontrollpunkt ausgesondert werden - ich bezweifle eher, dass die Kontrollen wirklich unabhängig sind, was die Sache schwierig macht.
quelle