Szukam najszybszego sposobu ustalenia, czy long
wartość jest idealnym kwadratem (tzn. Jej pierwiastek kwadratowy jest inną liczbą całkowitą):
- Zrobiłem to w prosty sposób, korzystając z wbudowanej
Math.sqrt()
funkcji, ale zastanawiam się, czy istnieje sposób, aby to zrobić szybciej, ograniczając się do domeny zawierającej tylko liczby całkowite. - Utrzymywanie tabeli odnośników jest niepraktyczne (ponieważ istnieje około 2 31,5 liczb całkowitych, których kwadrat jest mniejszy niż 2 63 ).
Oto bardzo prosty i bezpośredni sposób, w jaki to teraz robię:
public final static boolean isPerfectSquare(long n)
{
if (n < 0)
return false;
long tst = (long)(Math.sqrt(n) + 0.5);
return tst*tst == n;
}
Uwaga: Używam tej funkcji w wielu problemach z Project Euler . Dlatego nikt inny nie będzie musiał utrzymywać tego kodu. Tego rodzaju mikrooptymalizacja może w rzeczywistości coś zmienić, ponieważ częścią wyzwania jest wykonanie każdego algorytmu w mniej niż minutę, a funkcja ta będzie musiała zostać wywołana miliony razy w przypadku niektórych problemów.
Próbowałem różnych rozwiązań tego problemu:
- Po wyczerpujących testach odkryłem, że dodawanie
0.5
do wyniku Math.sqrt () nie jest konieczne, przynajmniej nie na moim komputerze. - Szybko odwrotność pierwiastka kwadratowego był szybszy, ale to dało nieprawidłowych wyników dla n> = 410881. Jednak, jak sugeruje BobbyShaftoe , możemy użyć hack FISR dla n <410881.
- Metoda Newtona była o wiele wolniejsza niż
Math.sqrt()
. Jest tak prawdopodobnie dlatego, żeMath.sqrt()
używa czegoś podobnego do metody Newtona, ale zaimplementowanej w sprzęcie, dzięki czemu jest znacznie szybsza niż w Javie. Ponadto Metoda Newtona nadal wymagała użycia podwójnych. - Zmodyfikowana metoda Newtona, która wykorzystywała kilka sztuczek, aby zaangażować tylko matematykę całkowitą, wymagała kilku hacków, aby uniknąć przepełnienia (chcę, aby ta funkcja działała ze wszystkimi dodatnimi liczbami całkowitymi ze znakiem 64-bitowym), i nadal była wolniejsza niż
Math.sqrt()
. - Binarny kotlet był jeszcze wolniejszy. Ma to sens, ponieważ przecięcie binarne wymaga średnio 16 przejść, aby znaleźć pierwiastek kwadratowy liczby 64-bitowej.
- Według testów Johna używanie
or
instrukcji w C ++ jest szybsze niż używanie aswitch
, ale w Javie i C # wydaje się, że nie ma różnicy międzyor
iswitch
. - Próbowałem także utworzyć tabelę odnośników (jako prywatną statyczną tablicę 64 wartości boolowskich). Następnie zamiast przełącznika lub
or
instrukcji, powiedziałbym tylkoif(lookup[(int)(n&0x3F)]) { test } else return false;
. Ku mojemu zaskoczeniu było to (tylko nieco) wolniejsze. Wynika to z faktu, że granice tablic są sprawdzane w Javie .
((1<<(n&15))|65004) != 0
, zamiast trzech osobnych kontroli.Odpowiedzi:
Opracowałem metodę, która działa ~ 35% szybciej niż twój 6-bitowy kod + Carmack + kod sqrt, przynajmniej z moim procesorem (x86) i językiem programowania (C / C ++). Twoje wyniki mogą się różnić, szczególnie dlatego, że nie wiem, jak będzie się grał czynnik Java.
Moje podejście jest trojakie:
int64 x
).z = r - x * x
i ustawiam t na największą potęgę 2 dzielących z odrobiną sztuczki. To pozwala mi pominąć wartości t, które i tak nie wpłynęłyby na wartość r. Wstępnie obliczona wartość początkowa w moim przypadku wybiera moduł „najmniejszego dodatniego” pierwiastka kwadratowego 8192.Nawet jeśli ten kod nie działa szybciej dla Ciebie, mam nadzieję, że podoba Ci się niektóre zawarte w nim pomysły. Następuje pełny, przetestowany kod, w tym wstępnie obliczone tabele.
źródło
9 < 0 => false
,9&2 => 0
,9&7 == 5 => false
,9&11 == 8 => false
.Jestem spóźniony na przyjęcie, ale mam nadzieję, że udzielę lepszej odpowiedzi; krótszy i (zakładając, że mój test jest poprawny) również znacznie szybszy .
Pierwszy test szybko wychwytuje większość elementów innych niż kwadraty. Używa 64-elementowej tabeli zapakowanej w długi, więc nie ma żadnych kosztów dostępu do tablicy (pośrednie i sprawdzanie granic). Dla równomiernie losowego
long
prawdopodobieństwo, że skończy się tutaj, wynosi 81,25%.Drugi test wyłapuje wszystkie liczby o nieparzystej liczbie dwójkowej w rozkładzie na czynniki. Metoda
Long.numberOfTrailingZeros
jest bardzo szybka, ponieważ przekształca JIT w pojedynczą instrukcję i86.Po usunięciu końcowych zer trzeci test obsługuje liczby kończące się na 011, 101 lub 111 w systemie binarnym, które nie są idealnymi kwadratami. Dba również o liczby ujemne, a także obsługuje 0.
Ostatni test powraca do
double
arytmetyki. Podobnie jakdouble
ma tylko 53 bity mantysy, konwersja zlong
nadouble
obejmuje zaokrąglanie dużych wartości. Niemniej jednak test jest poprawny (chyba że dowód jest błędny).Próba wprowadzenia pomysłu mod255 nie powiodła się.
źródło
goodMask
badanie to robi, ale robi to przed właściwym przesunięciem. Musisz więc to powtórzyć, ale w ten sposób jest to prostsze i AFAIK trochę szybsze i równie dobre.if ((x & (7 | Integer.MIN_VALUE)) != 1) return x == 0;
.Będziesz musiał przeprowadzić testy porównawcze. Najlepszy algorytm będzie zależeć od rozkładu twoich danych wejściowych.
Twój algorytm może być prawie optymalny, ale możesz zrobić szybkie sprawdzenie, aby wykluczyć pewne możliwości przed wywołaniem procedury pierwiastka kwadratowego. Na przykład spójrz na ostatnią cyfrę swojego numeru szesnastkowego, wykonując nieco „i”. Idealne kwadraty mogą kończyć się na 0, 1, 4 lub 9 na podstawie 16, więc dla 75% twoich danych wejściowych (zakładając, że są one równomiernie rozmieszczone) możesz uniknąć wezwania do pierwiastka kwadratowego w zamian za bardzo szybkie kręcenie bitów.
Kip przeprowadził analizę porównawczą następującego kodu implementującego sztuczkę szesnastkową. Podczas testowania liczb od 1 do 100 000 000 ten kod działał dwa razy szybciej niż oryginał.
Kiedy testowałem analogiczny kod w C ++, faktycznie działał wolniej niż oryginał. Kiedy jednak wyeliminowałem instrukcję switch, sztuczka szesnastkowa ponownie sprawia, że kod jest dwa razy szybszy.
Wyeliminowanie instrukcji switch miało niewielki wpływ na kod C #.
źródło
Myślałem o okropnych czasach, które spędziłem na kursie analizy numerycznej.
A potem pamiętam, że ta funkcja krążyła po sieci z kodu źródłowego Quake:
Który zasadniczo oblicza pierwiastek kwadratowy, używając funkcji aproksymacji Newtona (nie pamiętam dokładnej nazwy).
Powinien być użyteczny, a może nawet szybszy, pochodzi z jednej z fenomenalnych gier oprogramowania id!
Jest napisany w C ++, ale nie powinno być zbyt trudne ponowne użycie tej samej techniki w Javie, gdy tylko wpadniesz na pomysł:
Pierwotnie znalazłem na: http://www.codemaestro.com/reviews/9
Metoda Newtona wyjaśniona na wikipedii: http://en.wikipedia.org/wiki/Newton%27s_method
Możesz skorzystać z linku, aby uzyskać więcej informacji o tym, jak to działa, ale jeśli nie przejmujesz się tym, to mniej więcej to pamiętam z czytania bloga i z kursu analizy numerycznej:
* (long*) &y
jest w zasadzie fast funkcja konwersji do operacji tak długo całkowita może być stosowany na surowych bajtów.0x5f3759df - (i >> 1);
jest to wartość nasion uprzednio obliczone przez funkcję aproksymacji.* (float*) &i
konwertuje wartość z powrotem do zmiennoprzecinkowych.y = y * ( threehalfs - ( x2 * y * y ) )
linia bascially iteracje wartości ponad funkcję ponownie.Funkcja aproksymacji podaje bardziej precyzyjne wartości, im bardziej iterujesz funkcję nad wynikiem. W przypadku Quake'a jedna iteracja jest „wystarczająco dobra”, ale jeśli to nie było dla ciebie ... to możesz dodać tyle iteracji, ile potrzebujesz.
Powinno to być szybsze, ponieważ zmniejsza liczbę operacji dzielenia wykonanych naiwnym kwadratowym rootowaniu do zwykłego dzielenia przez 2 (w rzeczywistości
* 0.5F
operację mnożenia) i zastępuje ją kilkoma ustalonymi liczbami operacji mnożenia.źródło
Nie jestem pewien, czy byłoby to szybsze, czy nawet dokładne, ale możesz użyć algorytmu Magical Square Root Johna Carmacka , aby szybciej rozwiązać pierwiastek kwadratowy. Prawdopodobnie mógłbyś łatwo przetestować to dla wszystkich możliwych 32-bitowych liczb całkowitych i sprawdzić, czy faktycznie masz poprawne wyniki, ponieważ jest to tylko przybliżenie. Jednak teraz, gdy o tym myślę, użycie podwójnych jest również przybliżone, więc nie jestem pewien, jak to by się stało.
źródło
Jeśli wykonasz dwójkę binarną, aby znaleźć „właściwy” pierwiastek kwadratowy, możesz dość łatwo wykryć, czy wartość, którą masz, jest wystarczająco bliska, aby powiedzieć:
Po obliczeniu
n^2
opcje są następujące:n^2 = target
: gotowe, zwróć wartość truen^2 + 2n + 1 > target > n^2
: jesteś blisko, ale to nie jest idealne: zwróć falsen^2 - 2n + 1 < target < n^2
: to samotarget < n^2 - 2n + 1
: binarny kotlet na niższym poziomien
target > n^2 + 2n + 1
: dwójkowy na wyższymn
(Przepraszamy, ten parametr jest używany
n
jako bieżące przypuszczenie itarget
za parametr. Przepraszamy za zamieszanie!)Nie wiem, czy to będzie szybsze, czy nie, ale warto spróbować.
EDYCJA: Binarny rąbek nie musi również przyjmować całego zakresu liczb całkowitych,
(2^x)^2 = 2^(2x)
więc gdy już znajdziesz bit najwyższego zestawu w swoim celu (można to zrobić za pomocą sztuczki polegającej na kręceniu się; zapominam dokładnie jak) możesz szybko uzyskać szereg potencjalnych odpowiedzi. Pamiętaj, że naiwny binarny kotlet nadal będzie wymagał tylko 31 lub 32 iteracji.źródło
Przeprowadziłem własną analizę kilku algorytmów w tym wątku i opracowałem kilka nowych wyników. Możesz zobaczyć te stare wyniki w historii edycji tej odpowiedzi, ale nie są one dokładne, ponieważ popełniłem błąd i straciłem czas na analizę kilku algorytmów, które nie są blisko. Jednak wyciągając wnioski z kilku różnych odpowiedzi, mam teraz dwa algorytmy, które miażdżą „zwycięzcę” tego wątku. Oto podstawowa rzecz, którą robię inaczej niż wszyscy inni:
Jednak ten prosty wiersz, który przez większość czasu dodaje jedną lub dwie bardzo szybkie instrukcje, znacznie upraszcza
switch-case
instrukcję w jedną instrukcję if. Może jednak zwiększyć czas działania, jeśli wiele z testowanych liczb ma znaczącą moc dwóch czynników.Poniższe algorytmy są następujące:
Oto przykładowe środowisko wykonawcze, jeśli liczby są generowane przy użyciu
Math.abs(java.util.Random.nextLong())
Oto przykładowe środowisko uruchomieniowe, jeśli działa tylko na pierwszych milionach długości:
Jak widać,
DurronTwo
lepiej sprawdza się w przypadku dużych nakładów, ponieważ bardzo często korzysta z magicznej sztuczki, ale staje się nieczytelny w porównaniu z pierwszym algorytmem iMath.sqrt
ponieważ liczby są znacznie mniejsze. Tymczasem prostszeDurron
jest ogromnym zwycięzcą, ponieważ nigdy nie musi dzielić 4 razy wiele razy w pierwszym milionie liczb.Oto
Durron
:I
DurronTwo
I moja uprząż porównawcza: (wymaga suwmiarki Google 0.1-rc5)
AKTUALIZACJA: Stworzyłem nowy algorytm, który jest szybszy w niektórych scenariuszach, wolniejszy w innych, otrzymałem różne testy porównawcze na podstawie różnych danych wejściowych. Obliczając modulo
0xFFFFFF = 3 x 3 x 5 x 7 x 13 x 17 x 241
, możemy wyeliminować 97,82% liczb, które nie mogą być kwadratami. Można to zrobić (w pewnym sensie) w jednym wierszu za pomocą 5 operacji bitowych:Otrzymany wskaźnik to albo 1) reszta, 2) reszta
+ 0xFFFFFF
, albo 3) reszta+ 0x1FFFFFE
. Oczywiście musimy mieć tabelę przeglądową dla reszt modulo0xFFFFFF
, która ma rozmiar około 3 MB (w tym przypadku jest przechowywana jako liczby dziesiętne tekstu ascii, nie jest optymalna, ale można ją łatwo poprawić za pomocąByteBuffer
a itd. Ale ponieważ jest to wstępne obliczenie , nie robi tego) to ważne. Możesz znaleźć plik tutaj (lub sam go wygenerować):Ładuję go do
boolean
tablicy takiej jak ta:Przykładowe środowisko wykonawcze. Pokonał
Durron
(wersja pierwsza) w każdym badaniu, które prowadziłem.źródło
sqrtps
a nawetsqrtpd
(podwójna precyzja) nie jest tak zła na Skylake, ale niewiele lepsza niż opóźnienie na starych procesorach. W każdym razie 7-cpu.com/cpu/Haswell.html ma fajne liczby eksperymentalne i strony dla innych procesorów. Pdf przewodnik mikroprocesora Agner Fog zawiera pewne opóźnienia w pamięci podręcznej dla uarches Intela i AMD: agner.org/optimizedouble
precyzji, aby uniknąć zaokrąglania liczby całkowitej poza zakresem + -2 ^ 24 (więc liczba całkowita 32-bitowa może znajdować się poza tym), isqrtpd
jest wolniejsza niż,sqrtps
a także przetwarza tylko połowę liczby elementów na instrukcję (na wektor SIMD) .Zastosowanie metody Newtona powinno być znacznie szybsze , aby obliczyć pierwiastek kwadratowy z liczby całkowitej , a następnie wyprostować tę liczbę i sprawdzić, tak jak ma to miejsce w obecnym rozwiązaniu. Metoda Newtona jest podstawą rozwiązania Carmack wspomnianego w kilku innych odpowiedziach. Powinieneś być w stanie uzyskać szybszą odpowiedź, ponieważ interesuje Cię tylko całkowita liczba części root, co pozwala wcześniej zatrzymać algorytm aproksymacji.
Kolejna optymalizacja, którą możesz wypróbować: Jeśli pierwiastek cyfrowy liczby nie kończy się na 1, 4, 7 lub 9, liczba nie jest idealnym kwadratem. Można to wykorzystać jako szybki sposób na wyeliminowanie 60% danych wejściowych przed zastosowaniem algorytmu wolniejszego pierwiastka kwadratowego.
źródło
Math.sqrt()
działa z podwójnymi jako parametrami wejściowymi, więc nie uzyskasz dokładnych wyników dla liczb całkowitych większych niż 2 ^ 53 .źródło
Dla przypomnienia, innym podejściem jest wykorzystanie pierwotnego rozkładu. Jeśli każdy czynnik rozkładu jest parzysty, liczba jest idealnym kwadratem. Chcecie więc sprawdzić, czy liczbę można rozłożyć na iloczyn kwadratów liczb pierwszych. Oczywiście nie trzeba uzyskiwać takiego rozkładu, aby sprawdzić, czy on istnieje.
Najpierw zbuduj tabelę kwadratów liczb pierwszych, które są mniejsze niż 2 ^ 32. Jest to znacznie mniej niż tabela wszystkich liczb całkowitych do tego limitu.
Rozwiązanie byłoby wtedy takie:
Myślę, że to trochę tajemnicze. Na każdym kroku sprawdza, czy kwadrat liczby pierwszej dzieli liczbę wejściową. Jeśli tak, dzieli liczbę przez kwadrat tak długo, jak to możliwe, aby usunąć ten kwadrat z głównego rozkładu. Jeśli w wyniku tego procesu dojdziemy do 1, to liczbą wejściową był rozkład kwadratu liczb pierwszych. Jeśli kwadrat staje się większy niż sama liczba, to nie ma możliwości, aby ten kwadrat lub jakiekolwiek większe kwadraty mogły go podzielić, więc liczba nie może być rozkładem kwadratów liczb pierwszych.
Biorąc pod uwagę dzisiejszy sqrt wykonywany sprzętowo i potrzebę obliczenia liczb pierwszych tutaj, myślę, że to rozwiązanie jest znacznie wolniejsze. Ale powinno to dać lepsze wyniki niż rozwiązanie z sqrt, które nie będzie działać powyżej 2 ^ 54, jak mówi mrzl w swojej odpowiedzi.
źródło
sqrtsd
Przepustowość Core2 wynosi 1 na 6-58c. Jestidiv
to jeden na 12-36 motocykli. (opóźnienia podobne do przepływności: żadna jednostka nie jest potokowana).Wskazano, że ostatnie
d
cyfry idealnego kwadratu mogą przyjmować tylko określone wartości. Ostatnied
cyfry (w bazieb
) liczbyn
są takie same jak reszta, gdyn
jest podzielona przezb
d
, tj. w notacji Cn % pow(b, d)
.Można to uogólnić na dowolny moduł
m
, tj.n % m
można użyć, aby wykluczyć pewien procent liczb z idealnych kwadratów. Moduł, którego obecnie używasz, wynosi 64, co pozwala na 12, tj. 19% pozostałych, jak to możliwe kwadratów. Przy odrobinie kodowania znalazłem moduł 110880, który pozwala tylko 2016, tj. 1,8% pozostałych jako możliwe kwadraty. Tak więc w zależności od kosztu operacji modułu (tj. Podziału) i wyszukiwania tabeli względem pierwiastka kwadratowego na komputerze, użycie tego modułu może być szybsze.Nawiasem mówiąc, jeśli Java ma sposób na przechowywanie spakowanej tablicy bitów dla tabeli odnośników, nie używaj jej. 110880 32-bitowe słowa w dzisiejszych czasach to niewiele pamięci RAM, a pobranie słowa maszynowego będzie szybsze niż pobranie jednego bitu.
źródło
idiv
) jest równe lub gorsze kosztem FP sqrt (sqrtsd
) na obecnym sprzęcie x86. Ponadto całkowicie nie zgadzaj się z unikaniem pól bitowych. Współczynnik trafień w pamięci podręcznej będzie o wiele lepszy w przypadku pola bitowego, a testowanie nieco w polu bitowym to tylko jedna lub dwie prostsze instrukcje niż testowanie całego bajtu. (W przypadku małych tabel, które mieszczą się w pamięci podręcznej nawet jako pola niebędące bitami, najlepsza byłaby tablica bajtów, a nie 32-bitowe int. X86 ma dostęp do jednego bajtu z prędkością równą 32-bitowemu dwordowi.)Problem liczb całkowitych zasługuje na rozwiązanie liczb całkowitych. A zatem
Wykonaj wyszukiwanie binarne na (nieujemnych) liczbach całkowitych, aby znaleźć największą liczbę całkowitą taką, że
t**2 <= n
. Następnie sprawdź, czyr**2 = n
dokładnie. To zajmuje czas O (log n).Jeśli nie wiesz, jak przeszukiwać binarnie liczby całkowite dodatnie, ponieważ zestaw jest nieograniczony, jest to łatwe. Zaczynasz od obliczenia rosnącej funkcji f (powyżej
f(t) = t**2 - n
) na potęgach dwóch. Kiedy zobaczysz, że zmienia się na dodatnią, znalazłeś górną granicę. Następnie możesz wykonać standardowe wyszukiwanie binarne.źródło
O((log n)^2)
dlatego, że mnożenie nie jest czasem stałym, ale w rzeczywistości ma dolną granicęO(log n)
, co staje się widoczne podczas pracy z dużymi liczbami o wielu dokładnościach. Ale zasięg tej wiki wydaje się być 64-bitowy, więc może jest to nbd.Następujące uproszczenie rozwiązania maaartinus wydaje się zmniejszać o kilka punktów procentowych czas działania, ale nie jestem wystarczająco dobry w testowaniu porównawczym, aby stworzyć benchmark, któremu mogę zaufać:
Warto sprawdzić, jak pominąć pierwszy test,
wpłynie na wydajność.
źródło
Aby uzyskać wydajność, bardzo często trzeba iść na kompromisy. Inni wyrażali różne metody, jednak zauważyłeś, że hack Carmacka był szybszy do pewnych wartości N. Następnie powinieneś sprawdzić „n”, a jeśli jest on mniejszy niż liczba N, użyj hacka Carmacka, w przeciwnym razie użyj innej opisanej metody w odpowiedziach tutaj.
źródło
Jest to najszybsza implementacja Java, jaką mogłem wymyślić, wykorzystując kombinację technik sugerowanych przez innych w tym wątku.
Eksperymentowałem również z tymi modyfikacjami, ale nie pomogły one w wydajności:
źródło
Powinieneś pozbyć się 2-częściowej mocy N od samego początku.
2. edycja Magiczne wyrażenie dla m poniżej powinno być
a nie jak napisano
Koniec 2. edycji
1. edycja:
Niewielka poprawa:
Koniec 1. edycji
Teraz kontynuuj jak zwykle. W ten sposób, zanim dotrzesz do części zmiennoprzecinkowej, pozbyłeś się już wszystkich liczb, których część 2-mocowa jest nieparzysta (około połowa), a następnie rozważasz tylko 1/8 pozostałej części. Czyli uruchamiasz część zmiennoprzecinkową na 6% liczb.
źródło
Project Euler jest wymieniony w tagach i wiele problemów w nim wymaga sprawdzania liczb >>
2^64
. Większość wyżej opisanych optymalizacji nie działa łatwo, gdy pracujesz z 80-bajtowym buforem.Użyłem java BigInteger i nieco zmodyfikowanej wersji metody Newtona, która działa lepiej z liczbami całkowitymi. Problem polegał na tym, że dokładne kwadraty były
n^2
zbieżne(n-1)
zamiastn
ponieważn^2-1 = (n-1)(n+1)
a błąd końcowy był tylko o krok poniżej dzielnika końcowego i algorytm zakończył się. Łatwo było to naprawić, dodając jeden do oryginalnego argumentu przed obliczeniem błędu. (Dodaj dwa dla pierwiastków kostki itp.)Jedną z fajnych cech tego algorytmu jest to, że można natychmiast stwierdzić, czy liczba jest idealnym kwadratem - błąd końcowy (nie korekta) w metodzie Newtona wyniesie zero. Prosta modyfikacja pozwala również szybko obliczyć
floor(sqrt(x))
zamiast najbliższej liczby całkowitej. Jest to przydatne w przypadku kilku problemów Eulera.źródło
To przeróbka z dziesiętnej na dwójkową starego algorytmu kalkulatora Marchanta (przepraszam, nie mam referencji) w Rubim, dostosowanego specjalnie do tego pytania:
Oto kilka podobnych rzeczy (proszę nie głosować na mnie za kodowanie stylu / zapachów lub niezdarnego O / O - liczy się algorytm, a C ++ nie jest moim językiem ojczystym). W tym przypadku szukamy pozostałości == 0:
źródło
Jak już wspomniano, wywołanie sqrt nie jest idealnie dokładne, ale jest interesujące i pouczające, że nie rozwala innych odpowiedzi pod względem szybkości. W końcu sekwencja instrukcji języka asemblera dla sqrt jest niewielka. Intel ma instrukcję sprzętową, która, jak sądzę, nie jest używana przez Javę, ponieważ nie jest zgodna z IEEE.
Dlaczego więc jest wolny? Ponieważ Java w rzeczywistości wywołuje procedurę C za pośrednictwem JNI, i jest to wolniejsze niż wywoływanie podprogramu Java, co samo w sobie jest wolniejsze niż wykonywanie go bezpośrednio. Jest to bardzo denerwujące i Java powinna była wymyślić lepsze rozwiązanie, tj. Wbudować w zmiennoprzecinkowe wywołania biblioteki, jeśli to konieczne. No cóż.
Podejrzewam, że w C ++ wszystkie złożone alternatywy straciłyby na szybkości, ale nie sprawdziłem ich wszystkich. To, co zrobiłem i co ludzie Javy będą przydatni, to prosty hack, rozszerzenie specjalnych testów przypadków sugerowanych przez A. Rexa. Użyj pojedynczej długiej wartości jako tablicy bitowej, która nie jest zaznaczona granicami. W ten sposób masz 64-bitowe wyszukiwanie boolowskie.
Procedura toPerfectSquare5 działa w około 1/3 czasu na mojej maszynie Core2 Duo. Podejrzewam, że dalsze poprawki w tej samej linii mogą średnio skrócić czas, ale za każdym razem, gdy sprawdzasz, wymieniasz więcej testów na więcej eliminacji, więc nie możesz iść zbyt daleko na tej drodze.
Z pewnością zamiast osobnego testu na wynik ujemny, możesz sprawdzić wysokie 6 bitów w ten sam sposób.
Zauważ, że wszystko, co robię, to eliminowanie możliwych kwadratów, ale gdy mam potencjalny przypadek, muszę zadzwonić do oryginału, wstawiony jestPerfectSquare.
Procedura init2 jest wywoływana raz, aby zainicjować wartości statyczne pp1 i pp2. Zauważ, że w mojej implementacji w C ++ używam niepodpisanego długiego, więc ponieważ jesteś zalogowany, będziesz musiał użyć operatora >>>.
Nie ma wewnętrznej potrzeby sprawdzania tablicy, ale optymalizator Javy musi szybko to rozgryźć, więc nie winię ich za to.
źródło
pp2
? Rozumiem, żepp1
służy do testowania sześciu najmniej znaczących bitów, ale nie sądzę, aby testowanie kolejnych sześciu bitów miało jakikolwiek sens.Podoba mi się pomysł użycia prawie poprawnej metody na niektórych danych wejściowych. Oto wersja z wyższym „przesunięciem”. Kod wydaje się działać i przekazuje mój prosty przypadek testowy.
Wystarczy wymienić:
kod z tym:
źródło
Biorąc pod uwagę ogólną długość bitów (chociaż użyłem tutaj określonego typu), próbowałem zaprojektować uproszczone algo, jak poniżej. Początkowo wymagana jest prosta i oczywista kontrola dla wartości 0,1,2 lub <0. Poniższe jest proste w tym sensie, że nie próbuje używać żadnych istniejących funkcji matematycznych. Większość operatorów można zastąpić operatorami bitowymi. Nie testowałem jednak żadnych danych porównawczych. Nie jestem ekspertem w matematyce ani w szczególności w projektowaniu algorytmów komputerowych. Chciałbym zobaczyć, jak zwracasz uwagę na problem. Wiem, że istnieje wiele szans na poprawę.
źródło
Sprawdziłem wszystkie możliwe wyniki, gdy zaobserwowano ostatnie n bitów kwadratu. Poprzez sukcesywne badanie większej liczby bitów można wyeliminować do 5/6 wejść. Właściwie zaprojektowałem to, aby zaimplementować algorytm faktoryzacji Fermata i jest tam bardzo szybki.
Ostatni bit pseudokodu można wykorzystać do rozszerzenia testów w celu wyeliminowania większej liczby wartości. Powyższe testy dotyczą k = 0, 1, 2, 3
Najpierw sprawdza, czy ma kwadratową resztę z modułami potęgi dwóch, następnie testuje na podstawie końcowego modułu, a następnie używa Math.sqrt, aby wykonać test końcowy. Wpadłem na pomysł z pierwszego postu i próbowałem go rozwinąć. Doceniam wszelkie komentarze lub sugestie.
Aktualizacja: Używając testu według modułu (modSq) i podstawy modułu 44352, mój test działa w 96% czasu w porównaniu z aktualizacją OP dla liczb do 1 000 000 000.
źródło
Oto rozwiązanie dziel i zwyciężaj.
Jeśli pierwiastek kwadratowy z liczby naturalnej (
number
) jest liczbą naturalną (solution
), możesz łatwo określić zakres nasolution
podstawie liczby cyfrnumber
:number
ma 1 cyfrę:solution
w zakresie = 1 - 4number
ma 2 cyfry:solution
w zakresie = 3 - 10number
ma 3 cyfry:solution
w zakresie = 10 - 40number
ma 4 cyfry:solution
w zakresie = 30 - 100number
ma 5 cyfr:solution
w zakresie = 100 - 400Zwróć uwagę na powtórzenie?
Możesz użyć tego zakresu w podejściu do wyszukiwania binarnego, aby sprawdzić, czy istnieje
solution
:Oto kod
Oto moja klasa SquareRootChecker
A oto przykład, jak go używać.
źródło
toString
jest niezwykle kosztowną operacją w porównaniu do operatorów bitowych. Tak więc, aby spełnić cel pytania - wydajność - musisz użyć operatorów bitowych zamiast 10 ciągów podstawowych. Znowu bardzo podoba mi się twoja koncepcja. Niezależnie od tego, twoja implementacja (w obecnej formie) jest zdecydowanie najwolniejsza ze wszystkich możliwych rozwiązań opublikowanych dla tego pytania.Jeśli chodzi o szybkość, to dlaczego nie podzielić na partycje najczęściej używanego zestawu danych wejściowych i ich wartości, a następnie wykonać zoptymalizowany algorytm magiczny, który wymyśliłeś w wyjątkowych przypadkach?
źródło
Powinno być możliwe spakowanie „nie może być idealnym kwadratem, jeśli ostatnie X cyfr to N” znacznie wydajniej! Użyję 32-bitowych liczb całkowitych java i wygeneruję wystarczającą ilość danych, aby sprawdzić ostatnie 16 bitów liczby - to 2048 liczb szesnastkowych int.
...
Ok. Albo natknąłem się na teorię liczb, która jest trochę poza mną, albo w moim kodzie jest błąd. W każdym razie oto kod:
a oto wyniki:
(ed: elided za niską wydajność w prettify.js; zobacz historię wersji, aby zobaczyć.)
źródło
Metoda Newtona z arytmetyką liczb całkowitych
Jeśli chcesz uniknąć operacji na liczbach całkowitych, możesz skorzystać z poniższej metody. Zasadniczo wykorzystuje Metodę Newtona zmodyfikowaną dla arytmetyki liczb całkowitych.
Ta implementacja nie może konkurować z używanymi rozwiązaniami
Math.sqrt
. Jednak jego wydajność można poprawić za pomocą mechanizmów filtrujących opisanych w niektórych innych postach.źródło
Obliczanie pierwiastków kwadratowych metodą Newtona jest niesamowicie szybkie ... pod warunkiem, że wartość początkowa jest rozsądna. Jednak nie ma rozsądnej wartości początkowej, aw praktyce kończymy zachowaniem dzielenia i logowania (2 ^ 64).
Aby być naprawdę szybkim, potrzebujemy szybkiego sposobu na uzyskanie rozsądnej wartości początkowej, a to oznacza, że musimy zejść do języka maszynowego. Jeśli procesor dostarcza instrukcję taką jak POPCNT w Pentium, która zlicza zera wiodące, możemy użyć tej wartości, aby uzyskać wartość początkową z połową znaczących bitów. Ostrożnie możemy znaleźć stałą liczbę kroków Newtona, które zawsze będą wystarczające. (W ten sposób zrezygnowano z konieczności wykonywania pętli i wykonywania bardzo szybko).
Drugim rozwiązaniem jest funkcja zmiennoprzecinkowa, która może mieć szybkie obliczenia sqrt (takie jak koprocesor i87). Nawet wycieczka przez exp () i log () może być szybsza niż Newton zdegenerowana do wyszukiwania binarnego. Jest to trudny aspekt, zależna od procesora analiza tego, co i jeśli konieczne jest późniejsze udoskonalenie.
Trzecie rozwiązanie rozwiązuje nieco inny problem, ale warto o nim wspomnieć, ponieważ sytuacja jest opisana w pytaniu. Jeśli chcesz obliczyć bardzo wiele pierwiastków kwadratowych dla liczb, które różnią się nieznacznie, możesz użyć iteracji Newtona, jeśli nigdy nie ponownie zainicjujesz wartości początkowej, ale po prostu zostaw ją tam, gdzie poprzednio zostało obliczone. Użyłem tego z powodzeniem w co najmniej jednym problemie Eulera.
źródło
Pierwiastek kwadratowy liczby, biorąc pod uwagę, że liczba ta jest kwadratem idealnym.
Złożoność to log (n)
źródło
Jeśli chcesz prędkości, biorąc pod uwagę, że liczby całkowite mają skończony rozmiar, podejrzewam, że najszybszym sposobem byłoby (a) podzielenie parametrów według rozmiaru (np. Na kategorie według największego zestawu bitów), a następnie sprawdzenie wartości względem tablicy idealnych kwadratów w tym zakresie.
źródło
Jeśli chodzi o metodę Carmaca, wydaje się, że dość łatwo byłoby powtórzyć jeszcze raz, co powinno podwoić liczbę cyfr dokładności. Jest to w końcu niezwykle skrócona metoda iteracyjna - metoda Newtona, z bardzo dobrym pierwszym odgadnięciem.
Jeśli chodzi o twoje obecne najlepsze, widzę dwie mikrooptymalizacje:
To znaczy:
Jeszcze lepiej może być proste
Oczywiście byłoby interesujące wiedzieć, ile liczb zostaje wyzerowanych w każdym punkcie kontrolnym - raczej wątpię, czy kontrole są naprawdę niezależne, co sprawia, że wszystko jest trudne.
źródło