Am'm în căutarea pentru cel mai rapid mod de a determina dacă un "lung" valoarea este un pătrat perfect (adică rădăcina pătrată este un alt număr întreg):
Matematica.sqrt()
funcția, dar am'm întrebam dacă există o modalitate de a face mai repede de
limitează-te la întreg-doar domeniu.Aici este foarte simplă și directă cale de mi'm fac acum:
public final static boolean isPerfectSquare(long n)
{
if (n < 0)
return false;
long tst = (long)(Math.sqrt(n) + 0.5);
return tst*tst == n;
}
Notă: am'm a utiliza această funcție, în multe Proiect Euler probleme. Deci nimeni nu va mai trebui să mențină acest cod. Și acest tip de micro-optimizare-ar putea face de fapt o diferență, deoarece o parte din provocare este de a face fiecare algoritm în mai puțin de un minut, și această funcție va trebui să fie numit de milioane de ori în unele probleme.
Am'am încercat diferite soluții la această problemă:
0.5
pentru rezultatul de Matematica.sqrt (), nu este necesar, cel puțin nu pe masina mea.. Aceasta este, probabil pentru Matematica.sqrt()
foloseste ceva similar cu Newton's Metoda, dar implementat în hardware-ul deci's mult mai rapid decât în Java. De asemenea, Newton's Metoda încă necesară utilizarea de camere duble.Mi-am dat seama o metodă care funcționează ~35% mai rapid decât 6bits+Carmack+sqrt cod, cel puțin cu CPU (x86) și limbaj de programare (C/C++). Rezultatele pot varia, mai ales pentru că eu nu't știu cum Java factor va juca. Abordarea mea este triplu:
dacă( x < 0 || (x&2) || ((x & 7) == 5) || ((x & 11) == 8) ) return false; if( x == 0 ) return true;
Pentru a verifica dacă reziduul este un pătrat, mă uit în sus răspunsul într-un precalculate masă.int64 y = x; y = (y & 4294967295LL) + (y >> 32); y = (y & 65535) + (y >> 16); y = (y & 255) + ((y >> 8) & 255) + (y >> 16); // În acest moment, y este intre 0 si 511. Mai mult cod poate reduce mai departe.
dacă( bad255[y] ) return false; // Cu toate acestea, am folosi un tabel de dimensiune 512
În acest moment, pentru numărul nostru de a fi un pătrat, acesta trebuie să fie 1 mod 8.dacă((x & 4294967295LL) == 0) x >>= 32; dacă((x & 65535) == 0) x >>= 16; dacă((x & 255) == 0) x >>= 8; dacă((x & 15) == 0) x >>= 4; dacă((x & 3) == 0) x >>= 2;
Structura de bază a Hensel's lema este următoarea. (Notă: cod netestat; în cazul în care nu't de lucru, încercați t=2 sau 8.)dacă((x & 7) != 1) return false;
Ideea este că, la fiecare iterație, puteți adăuga un pic pe r, "curent" rădăcină pătrată a lui x; fiecare rădăcină pătrată sunt corecte modulo o mai mare și mai mare putere a lui 2, și anume t/2. La final, r și t/2-r va fi rădăcina pătrată a lui x modulo t/2. (Rețineți că, dacă r este o rădăcină pătrată a lui x, atunci așa este -r. Acest lucru este valabil chiar modulo numere, dar feriți-vă, modulo unele numere, lucrurile pot fi chiar mai mult de 2 rădăcini pătrate; în special, aceasta include puteri ale lui 2.) Deoarece reale rădăcina pătrată este mai mică decât 2^32, la acel moment, putem, de fapt, doar a verifica dacă r sau t/2-r sunt reale rădăcini pătrate. În codul actual, am utilizat următoarele modificat bucla:int64 t = 4, r = 1; t <<= 1; r += ((x - r * r) & t) >> 1; t <<= 1; r += ((x - r * r) & t) >> 1; t <<= 1; r += ((x - r * r) & t) >> 1; // Se repetă până când t este 2^33 sau cam asa ceva. Utilizați o buclă, dacă vrei.
Viteza de aici se obține în trei moduri: precalculate valoare de pornire (echivalent cu ~10 iterații a buclei), mai devreme ieșirea din buclă, și sărind peste unele t valori. Pentru ultima parte, mă uit laint64 r, t, z; r = a începe[(x >> 3) & 1023]; face { z = x - r * r; if( z == 0 ) return true; dacă( z < 0 ) return false; t = z & (-z); r += (z & t) >> 1; dacă( r > (t >> 1) ) r = t - r; } în timp ce( t <= (1LL << 33) );
typedef semnat long long int int64; int start[1024] = {1,3,1769,5,1937,1741,7,1451,479,157,9,91,945,659,1817,11, 1983,707,1321,1211,1071,13,1479,405,415,1501,1609,741,15,339,1703,203, 129,1411,873,1669,17,1715,1145,1835,351,1251,887,1573,975,19,1127,395, 1855,1981,425,453,1105,653,327,21,287,93,713,1691,1935,301,551,587, 257,1277,23,763,1903,1075,1799,1877,223,1437,1783,859,1201,621,25,779, 1727,573,471,1979,815,1293,825,363,159,1315,183,27,241,941,601,971, 385,131,919,901,273,435,647,1493,95,29,1417,805,719,1261,1177,1163, 1599,835,1367,315,1361,1933,1977,747,31,1373,1079,1637,1679,1581,1753,1355, 513,1539,1815,1531,1647,205,505,1109,33,1379,521,1627,1457,1901,1767,1547, 1471,1853,1833,1349,559,1523,967,1131,97,35,1975,795,497,1875,1191,1739, 641,1149,1385,133,529,845,1657,725,161,1309,375,37,463,1555,615,1931, 1343,445,937,1083,1617,883,185,1515,225,1443,1225,869,1423,1235,39,1973, 769,259,489,1797,1391,1485,1287,341,289,99,1271,1701,1713,915,537,1781, 1215,963,41,581,303,243,1337,1899,353,1245,329,1563,753,595,1113,1589, 897,1667,407,635,785,1971,135,43,417,1507,1929,731,207,275,1689,1397, 1087,1725,855,1851,1873,397,1607,1813,481,163,567,101,1167,45,1831,1205, 1025,1021,1303,1029,1135,1331,1017,427,545,1181,1033,933,1969,365,1255,1013, 959,317,1751,187,47,1037,455,1429,609,1571,1463,1765,1009,685,679,821, 1153,387,1897,1403,1041,691,1927,811,673,227,137,1499,49,1005,103,629, 831,1091,1449,1477,1967,1677,697,1045,737,1117,1737,667,911,1325,473,437, 1281,1795,1001,261,879,51,775,1195,801,1635,759,165,1871,1645,1049,245, 703,1597,553,955,209,1779,1849,661,865,291,841,997,1265,1965,1625,53, 1409,893,105,1925,1297,589,377,1579,929,1053,1655,1829,305,1811,1895,139, 575,189,343,709,1711,1139,1095,277,993,1699,55,1435,655,1491,1319,331, 1537,515,791,507,623,1229,1529,1963,1057,355,1545,603,1615,1171,743,523, 447,1219,1239,1723,465,499,57,107,1121,989,951,229,1521,851,167,715, 1665,1923,1687,1157,1553,1869,1415,1749,1185,1763,649,1061,561,531,409,907, 319,1469,1961,59,1455,141,1209,491,1249,419,1847,1893,399,211,985,1099, 1793,765,1513,1275,367,1587,263,1365,1313,925,247,1371,1359,109,1561,1291, 191,61,1065,1605,721,781,1735,875,1377,1827,1353,539,1777,429,1959,1483, 1921,643,617,389,1809,947,889,981,1441,483,1143,293,817,749,1383,1675, 63,1347,169,827,1199,1421,583,1259,1505,861,457,1125,143,1069,807,1867, 2047,2045,279,2043,111,307,2041,597,1569,1891,2039,1957,1103,1389,231,2037, 65,1341,727,837,977,2035,569,1643,1633,547,439,1307,2033,1709,345,1845, 1919,637,1175,379,2031,333,903,213,1697,797,1161,475,1073,2029,921,1653, 193,67,1623,1595,943,1395,1721,2027,1761,1955,1335,357,113,1747,1497,1461, 1791,771,2025,1285,145,973,249,171,1825,611,265,1189,847,1427,2023,1269, 321,1475,1577,69,1233,755,1223,1685,1889,733,1865,2021,1807,1107,1447,1077, 1663,1917,1129,1147,1775,1613,1401,555,1953,2019,631,1243,1329,787,871,885, 449,1213,681,1733,687,115,71,1301,2017,675,969,411,369,467,295,693, 1535,509,233,517,401,1843,1543,939,2015,669,1527,421,591,147,281,501, 577,195,215,699,1489,525,1081,917,1951,2013,73,1253,1551,173,857,309, 1407,899,663,1915,1519,1203,391,1323,1887,739,1673,2011,1585,493,1433,117, 705,1603,1111,965,431,1165,1863,533,1823,605,823,1179,625,813,2009,75, 1279,1789,1559,251,657,563,761,1707,1759,1949,777,347,335,1133,1511,267, 833,1085,2007,1467,1745,1805,711,149,1695,803,1719,485,1295,1453,935,459, 1151,381,1641,1413,1263,77,1913,2005,1631,541,119,1317,1841,1773,359,651, 961,323,1193,197,175,1651,441,235,1567,1885,1481,1947,881,2003,217,843, 1023,1027,745,1019,913,717,1031,1621,1503,867,1015,1115,79,1683,793,1035, 1089,1731,297,1861,2001,1011,1593,619,1439,477,585,283,1039,1363,1369,1227, 895,1661,151,645,1007,1357,121,1237,1375,1821,1911,549,1999,1043,1945,1419, 1217,957,599,571,81,371,1351,1003,1311,931,311,1381,1137,723,1575,1611, 767,253,1047,1787,1169,1997,1273,853,1247,413,1289,1883,177,403,999,1803, 1345,451,1495,1093,1839,269,199,1387,1183,1757,1207,1051,783,83,423,1995, 639,1155,1943,123,751,1459,1671,469,1119,995,393,219,1743,237,153,1909, 1473,1859,1705,1339,337,909,953,1771,1055,349,1993,613,1393,557,729,1717, 511,1533,1257,1541,1425,819,519,85,991,1693,503,1445,433,877,1305,1525, 1601,829,809,325,1583,1549,1991,1941,927,1059,1097,1819,527,1197,1881,1333, 383,125,361,891,495,179,633,299,863,285,1399,987,1487,1517,1639,1141, 1729,579,87,1989,593,1907,839,1557,799,1629,201,155,1649,1837,1063,949, 255,1283,535,773,1681,461,1785,683,735,1123,1801,677,689,1939,487,757, 1857,1987,983,443,1327,1267,313,1173,671,221,695,1509,271,1619,89,565, 127,1405,1431,1659,239,1101,1159,1067,607,1565,905,1755,1231,1299,665,373, 1985,701,1879,1221,849,627,1465,789,543,1187,1591,923,1905,979,1241,181}; bool bad255[512] = {0,0,1,1,0,1,1,1,1,0,1,1,1,1,1,0,0,1,1,0,1,0,1,1,1,0,1,1,1,1,0,1, 1,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,0,1,1,1,1,0,1,1,1, 0,1,0,1,1,0,0,1,1,1,1,1,0,1,1,1,1,0,1,1,0,0,1,1,1,1,1,1,1,1,0,1, 1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,0,1,1,1,0,1,1,1,1,0,0,1,1,1,1,1,1, 1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,0,1,1,0,1,1,1,1,1, 1,1,1,1,1,1,0,1,1,0,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1, 1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1, 1,0,1,1,1,0,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1, 0,0,1,1,0,1,1,1,1,0,1,1,1,1,1,0,0,1,1,0,1,0,1,1,1,0,1,1,1,1,0,1, 1,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,0,1,1,1,1,0,1,1,1, 0,1,0,1,1,0,0,1,1,1,1,1,0,1,1,1,1,0,1,1,0,0,1,1,1,1,1,1,1,1,0,1, 1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,0,1,1,1,0,1,1,1,1,0,0,1,1,1,1,1,1, 1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,0,1,1,0,1,1,1,1,1, 1,1,1,1,1,1,0,1,1,0,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1, 1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1, 1,0,1,1,1,0,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1, 0,0}; inline bool pătrat( int64 x ) { // Quickfail dacă( x < 0 || (x&2) || ((x & 7) == 5) || ((x & 11) == 8) ) return false; if( x == 0 ) return true; // Check mod 255 = 3 * 5 * 17, for fun int64 y = x; y = (y & 4294967295LL) + (y >> 32); y = (y & 65535) + (y >> 16); y = (y & 255) + ((y >> 8) & 255) + (y >> 16); if( bad255[y] ) return false; // Divide out powers of 4 using binary search if((x & 4294967295LL) == 0) x >>= 32; if((x & 65535) == 0) x >>= 16; if((x & 255) == 0) x >>= 8; if((x & 15) == 0) x >>= 4; if((x & 3) == 0) x >>= 2; if((x & 7) != 1) return false; // Compute sqrt using something like Hensel's lemma int64 r, t, z; r = start[(x >> 3) & 1023]; do { z = x - r * r; if( z == 0 ) return true; if( z < 0 ) return false; t = z & (-z); r += (z & t) >> 1; if( r > (t >> 1) ) r = t - r; } while( t <= (1LL << 33) ); return false;
}
Am'm destul de târziu la petrecere, dar sper să ofere un răspuns mai bun; mai scurte și (presupunând mea [referință][src] este corect), de asemenea, mult [repede][viteza].
long goodMask; // 0xC840C04048404040 computed below
{
for (int i=0; i<64; ++i) goodMask |= Long.MIN_VALUE >>> (i*i);
}
public boolean isSquare(long x) {
// This tests if the 6 least significant bits are right.
// Moving the to be tested bit to the highest position saves us masking.
if (goodMask << x >= 0) return false;
final int numberOfTrailingZeros = Long.numberOfTrailingZeros(x);
// Each square ends with an even number of zeros.
if ((numberOfTrailingZeros & 1) != 0) return false;
x >>= numberOfTrailingZeros;
// Now x is either 0 or odd.
// In binary each odd square ends with 001.
// Postpone the sign test until now; handle zero in the branch.
if ((x&7) != 1 | x <= 0) return x == 0;
// Do it in the classical way.
// The correctness is not trivial as the conversion from long to double is lossy!
final long tst = (long) Math.sqrt(x);
return tst * tst == x;
}
Primul test prinde cele mai non-pătrate rapid. Se folosește un 64-element de masă de ambalat într-o mult timp, deci nu's nici o matrice de acces cost (indirectare și limitele controalelor). Pentru o uniform aleatoare "lung", acolo's o 81.25% probabilitatea de a se încheie aici.
Cel de-al doilea test prinde toate numerele având un număr impar de câte doi în factorizare. Metoda Mult.numberOfTrailingZeros` este foarte rapid ca acesta devine JIT-ed într-un singur i86 instrucțiuni.
După cădere zerouri terminale, cel de-al treilea test se ocupă de numere se termină cu 011, 101, sau 111 în binar, care nu sunt pătrate perfecte. De asemenea, îi pasă de numere negative și, de asemenea, se ocupă de 0.
Testul final revine la "dublu" aritmetică. Ca "dublu" are doar 53 de biți mantisa, conversia de la "lung" la "dublu" include rotunjire pentru valori mari. Cu toate acestea, testul este corect (cu excepția cazului în care dovada este greșit).
Încearcă să includă mod255 ideea era't succes.
[src]: https://www.dropbox.com/s/ad4r6oxfcilqkch/IsSquareBenchmark.java?dl=0
Te'll au de a face unele de benchmarking. Cel mai bun algoritm va depinde de distribuția de inputuri.
Algoritm poate fi aproape optime, dar s-ar putea să doriți să faceți o verificare rapidă pentru a exclude unele posibilități înainte de a apela rădăcină pătrată de rutină. De exemplu, uita-te la ultima cifră din numărul tău în hex de a face un pic înțelept "și." pătrate Perfecte se poate încheia numai în 0, 1, 4, 9 sau în baza 16, Asa ca pentru 75% din intrări (presupunând că acestea sunt uniform distribuite) puteți evita un apel la rădăcină pătrată, în schimbul unor foarte repede pic twiddling.
Kip evaluate următoarele aplicare a codului hex truc. Atunci când se testează numere de la 1 la 100,000,000, acest cod a fugit de două ori la fel de repede ca cele originale.
public final static boolean isPerfectSquare(long n)
{
if (n < 0)
return false;
switch((int)(n & 0xF))
{
case 0: case 1: case 4: case 9:
long tst = (long)Math.sqrt(n);
return tst*tst == n;
default:
return false;
}
}
Atunci când am testat analog cod în C++, de fapt a fugit mai lent decât originalul. Cu toate acestea, atunci când am eliminat switch, hex truc face din nou codul de două ori la fel de repede.
int isPerfectSquare(int n)
{
int h = n & 0xF; // h is the last hex "digit"
if (h > 9)
return 0;
// Use lazy evaluation to jump out of the if statement as soon as possible
if (h != 2 && h != 3 && h != 5 && h != 6 && h != 7 && h != 8)
{
int t = (int) floor( sqrt((double) n) + 0.5 );
return t*t == n;
}
return 0;
}
Eliminarea comutator declarație a avut un efect redus asupra C# cod.
Am fost de gândire despre oribil ori am'am petrecut în Analiza Numerica curs.
Și apoi îmi amintesc, a existat această funcție circling în jurul 'net de la Cutremurul de cod Sursă:
float Q_rsqrt( float number )
{
long i;
float x2, y;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = * ( long * ) &y; // evil floating point bit level hacking
i = 0x5f3759df - ( i >> 1 ); // wtf?
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed
#ifndef Q3_VM
#ifdef __linux__
assert( !isnan(y) ); // bk010122 - FPE?
#endif
#endif
return y;
}
Care, practic, se calculează o rădăcină pătrată, folosind Newton's aproximare a funcției (nu-mi amintesc exact numele).
Ar trebui să fie ușor de utilizat și ar putea fi chiar mai repede, l's de la unul dintre fenomenal id software's joc!
L's scris în C++, dar aceasta nu ar trebui să fie prea greu pentru a reutiliza aceeași tehnică în Java odată ce ai prins ideea:
Inițial am găsit la: http://www.codemaestro.com/reviews/9
Newton's metoda explicată la wikipedia: http://en.wikipedia.org/wiki/Newton%27s_method
Aveți posibilitatea să urmați link-ul pentru mai multe explicații despre cum funcționează, dar dacă nu't pasă prea mult, atunci acest lucru este aproximativ ceea ce-mi amintesc din ce am citit pe blog și de a lua Analiză Numerică curs:
* (long*) &y
este de fapt un mod rapid de a converti la timp funcția astfel încât întreg operațiuni pot fi aplicate pe prime bytes.y = y * ( threehalfs - ( x2 * y * y ) )
linie bascially reiterează valoarea peste din nou funcția.Apropierea funcție oferă valori mai precise mai repeta funcția de peste rezultatul. În Quake's caz, o iterație este "destul de bun", dar dacă nu era't pentru tine, atunci ați putea adăuga la fel de mult iterație ca ai nevoie.
Acest lucru ar trebui să fie mai rapid, deoarece se reduce numărul de operațiuni de divizare face în naivă pătrat înrădăcinare în jos de la un simplu divide cu 2 (de fapt, un * 0.5 F
multiplica funcționare) și înlocuiți-l cu cateva număr fix de multiplicare operațiuni în loc.
Am'm nu sunt sigur dacă ar fi mai rapid, sau chiar corecte, dar ai putea folosi John Carmack's Magic Square Root, algoritm pentru a rezolva rădăcina pătrată mai repede. Probabil ai putea testa cu ușurință acest lucru pentru toate posibile 32 de biți numere întregi, și să valideze că tu de fapt ai rezultate corecte, precum's doar un appoximation. Cu toate acestea, acum că mă gândesc la asta, folosind dublu este, de asemenea, apropierea, așa că am'm nu sunt sigur cum că ar intra în joc.
Dacă faci un binar taie pentru a încerca să găsească "dreapta" rădăcină pătrată, puteți destul de ușor de a detecta dacă valoarea pe care o'ai, e destul de aproape să-i spun:
(n+1)^2 = n^2 + 2n + 1
(n-1)^2 = n^2 - 2n + 1
Deci, având calculate n^2
, opțiunile sunt:
n^2 = țintă
: gata, return truen^2 + 2n + 1 > țintă > n^2
: te're de aproape, dar's nu-i perfect: return falsen^2 - 2n + 1 < țintă < n^2
: idem: binar se toaca pe un mic
n`: binar taie pe o mai mare
n`(Îmi pare rău, aceasta folosește n
tău actual și "țintă" pentru parametru. Cer scuze pentru confuzie!)
Eu nu't știu dacă acest lucru va fi mai rapid sau nu, dar's valoare de o încercare.
EDIT: binar taie nu't trebuie să ia în întreaga gamă de numere întregi, fie (2^x)^2 = 2^(2x)
, astfel încât odată ce te'am gasit set top pic în țintă (care se poate face cu un pic-twiddling truc; am uitat exact cat), puteți obține rapid o serie de răspunsuri potențiale. Tine minte, un naiv binar chop este încă doar de gând să ia până la 31 sau 32 de iterații.
Mi-am făcut propria analiză a mai multor algoritmi în acest thread si am venit cu ceva nou rezultatele. Puteți vedea cele vechi rezultate în editare istorie de acest răspuns, dar're nu sunt corecte, ca am facut o greseala, si timp pierdut analizând o serie de algoritmi care sunt't de aproape. Cu toate acestea, trăgând învățăminte din mai multe răspunsuri diferite, acum am doi algoritmi care crush "câștigător" din acest thread. Aici's nucleu lucru pe care îl fac diferit de ceilalți:
// This is faster because a number is divisible by 2^4 or more only 6% of the time
// and more than that a vanishingly small percentage.
while((x & 0x3) == 0) x >>= 2;
// This is effectively the same as the switch-case statement used in the original
// answer.
if((x & 0x7) != 1) return false;
Cu toate acestea, această linie simplă, care de multe ori adaugă una sau două foarte repede instrucțiuni, simplifică foarte mult switch-caz
declarație în if. Cu toate acestea, se poate adăuga la runtime dacă multe dintre testat numere au putere semnificativă-de-doi factori.
Algoritmii de mai jos sunt după cum urmează:
Aici este un eșantion de execuție dacă numerele sunt generate folosind Matematica.abs(java.util.Aleatoare.nextLong())`
0% Scenario{vm=java, trial=0, benchmark=Internet} 39673.40 ns; ?=378.78 ns @ 3 trials
33% Scenario{vm=java, trial=0, benchmark=Durron} 37785.75 ns; ?=478.86 ns @ 10 trials
67% Scenario{vm=java, trial=0, benchmark=DurronTwo} 35978.10 ns; ?=734.10 ns @ 10 trials
benchmark us linear runtime
Internet 39.7 ==============================
Durron 37.8 ============================
DurronTwo 36.0 ===========================
vm: java
trial: 0
Și aici este un eșantion de execuție dacă-l's a rula pe primul milion doreste doar:
0% Scenario{vm=java, trial=0, benchmark=Internet} 2933380.84 ns; ?=56939.84 ns @ 10 trials
33% Scenario{vm=java, trial=0, benchmark=Durron} 2243266.81 ns; ?=50537.62 ns @ 10 trials
67% Scenario{vm=java, trial=0, benchmark=DurronTwo} 3159227.68 ns; ?=10766.22 ns @ 3 trials
benchmark ms linear runtime
Internet 2.93 ===========================
Durron 2.24 =====================
DurronTwo 3.16 ==============================
vm: java
trial: 0
După cum puteți vedea, DurronTwo
nu mai mari intrări, pentru că se ajunge la utilizarea truc de magie foarte foarte des, dar sunt distruse, comparativ cu primul algoritm și Matematica.sqrtdeoarece numerele sunt mult mai mici. Între timp, mai simplu
Durron` este un mare câștigător pentru că nu trebuie să împartă cu 4 de multe ori în prima milioane de numere.
Aici's Durron
:
public final static boolean isPerfectSquareDurron(long n) {
if(n < 0) return false;
if(n == 0) return true;
long x = n;
// This is faster because a number is divisible by 16 only 6% of the time
// and more than that a vanishingly small percentage.
while((x & 0x3) == 0) x >>= 2;
// This is effectively the same as the switch-case statement used in the original
// answer.
if((x & 0x7) == 1) {
long sqrt;
if(x < 410881L)
{
int i;
float x2, y;
x2 = x * 0.5F;
y = x;
i = Float.floatToRawIntBits(y);
i = 0x5f3759df - ( i >> 1 );
y = Float.intBitsToFloat(i);
y = y * ( 1.5F - ( x2 * y * y ) );
sqrt = (long)(1.0F/y);
} else {
sqrt = (long) Math.sqrt(x);
}
return sqrt*sqrt == x;
}
return false;
}
Și DurronTwo
public final static boolean isPerfectSquareDurronTwo(long n) {
if(n < 0) return false;
// Needed to prevent infinite loop
if(n == 0) return true;
long x = n;
while((x & 0x3) == 0) x >>= 2;
if((x & 0x7) == 1) {
long sqrt;
if (x < 41529141369L) {
int i;
float x2, y;
x2 = x * 0.5F;
y = x;
i = Float.floatToRawIntBits(y);
//using the magic number from
//http://www.lomont.org/Math/Papers/2003/InvSqrt.pdf
//since it more accurate
i = 0x5f375a86 - (i >> 1);
y = Float.intBitsToFloat(i);
y = y * (1.5F - (x2 * y * y));
y = y * (1.5F - (x2 * y * y)); //Newton iteration, more accurate
sqrt = (long) ((1.0F/y) + 0.2);
} else {
//Carmack hack gives incorrect answer for n >= 41529141369.
sqrt = (long) Math.sqrt(x);
}
return sqrt*sqrt == x;
}
return false;
}
Și-mi referință cablaj: (Necesită Google etrier 0.1-rc5)
public class SquareRootBenchmark {
public static class Benchmark1 extends SimpleBenchmark {
private static final int ARRAY_SIZE = 10000;
long[] trials = new long[ARRAY_SIZE];
@Override
protected void setUp() throws Exception {
Random r = new Random();
for (int i = 0; i < ARRAY_SIZE; i++) {
trials[i] = Math.abs(r.nextLong());
}
}
public int timeInternet(int reps) {
int trues = 0;
for(int i = 0; i < reps; i++) {
for(int j = 0; j < ARRAY_SIZE; j++) {
if(SquareRootAlgs.isPerfectSquareInternet(trials[j])) trues++;
}
}
return trues;
}
public int timeDurron(int reps) {
int trues = 0;
for(int i = 0; i < reps; i++) {
for(int j = 0; j < ARRAY_SIZE; j++) {
if(SquareRootAlgs.isPerfectSquareDurron(trials[j])) trues++;
}
}
return trues;
}
public int timeDurronTwo(int reps) {
int trues = 0;
for(int i = 0; i < reps; i++) {
for(int j = 0; j < ARRAY_SIZE; j++) {
if(SquareRootAlgs.isPerfectSquareDurronTwo(trials[j])) trues++;
}
}
return trues;
}
}
public static void main(String... args) {
Runner.main(Benchmark1.class, args);
}
}
ACTUALIZARE: am'am facut un nou algoritm, care este mai rapid în unele scenarii, mai lent în altele, am'am ajuns criterii de referință diferite, bazate pe diferite intrări. Dacă vom calcula modulo 0xFFFFFF = 3 x 3 x 5 x 7 x 13 x 17 x 241
, putem elimina 97.82% din numere care nu pot fi pătrate. Acest lucru poate fi (un fel de) a făcut într-o singură linie, cu 5 operațiuni la nivel de bit:
if (!goodLookupSquares[(int) ((n & 0xFFFFFFl) + ((n >> 24) & 0xFFFFFFl) + (n >> 48))]) return false;
Rezultate index este fie 1) reziduu, 2) reziduul + 0xFFFFFF, sau 3) reziduul
+ 0x1FFFFFE. Desigur, trebuie să avem un tabel de căutare pentru reziduuri modulo
0xFFFFFF, care se află la o 3mb fișier (în acest caz stocate ca text ascii zecimal numere, nu optimă, dar în mod clar îmbunătățit cu un ByteBuffer
și așa mai departe. Dar din moment ce precalculation nu't contează atât de mult. Puteți găsi fișierul aici (sau de a genera singur):
public final static boolean isPerfectSquareDurronThree(long n) {
if(n < 0) return false;
if(n == 0) return true;
long x = n;
while((x & 0x3) == 0) x >>= 2;
if((x & 0x7) == 1) {
if (!goodLookupSquares[(int) ((n & 0xFFFFFFl) + ((n >> 24) & 0xFFFFFFl) + (n >> 48))]) return false;
long sqrt;
if(x < 410881L)
{
int i;
float x2, y;
x2 = x * 0.5F;
y = x;
i = Float.floatToRawIntBits(y);
i = 0x5f3759df - ( i >> 1 );
y = Float.intBitsToFloat(i);
y = y * ( 1.5F - ( x2 * y * y ) );
sqrt = (long)(1.0F/y);
} else {
sqrt = (long) Math.sqrt(x);
}
return sqrt*sqrt == x;
}
return false;
}
Am încărcați-l într-un "boolean" matrice astfel:
private static boolean[] goodLookupSquares = null;
public static void initGoodLookupSquares() throws Exception {
Scanner s = new Scanner(new File("24residues_squares.txt"));
goodLookupSquares = new boolean[0x1FFFFFE];
while(s.hasNextLine()) {
int residue = Integer.valueOf(s.nextLine());
goodLookupSquares[residue] = true;
goodLookupSquares[residue + 0xFFFFFF] = true;
goodLookupSquares[residue + 0x1FFFFFE] = true;
}
s.close();
}
Exemplu de execuție. Se bate Durron
(versiunea o) în fiecare proces am fugit.
0% Scenario{vm=java, trial=0, benchmark=Internet} 40665.77 ns; ?=566.71 ns @ 10 trials
33% Scenario{vm=java, trial=0, benchmark=Durron} 38397.60 ns; ?=784.30 ns @ 10 trials
67% Scenario{vm=java, trial=0, benchmark=DurronThree} 36171.46 ns; ?=693.02 ns @ 10 trials
benchmark us linear runtime
Internet 40.7 ==============================
Durron 38.4 ============================
DurronThree 36.2 ==========================
vm: java
trial: 0
Ar trebui să fie mult mai rapid de a utiliza Newton's metoda pentru a calcula Integer Square Root, atunci pătrat de acest număr și de a verifica, cum faci într-o soluție de curent. Newton's metoda este baza pentru Carmack soluție menționat în unele alte răspunsuri. Tu ar trebui să fie capabil de a obține un răspuns mai rapid deoarece ai're interesat doar partea întreagă a rădăcinii, permițându-vă pentru a opri algoritm de aproximare mai devreme.
O alta optimizare pe care le puteți încerca: Dacă Digital Root de un număr nu't end în 1, 4, 7, sau 9 numărul este nu un pătrat perfect. Acest lucru poate fi folosit ca o modalitate rapidă de a elimina 60% din intrările dvs. înainte de a aplica mai lent rădăcină pătrată algoritm.
vreau aceasta functie pentru a lucra cu toate pozitiv 64-bit semnat numere întregi
Matematica.sqrt()
funcționează cu dublu ca parametrii de intrare, deci ai câștigat't obține rezultate exacte pentru numere întregi mai mare decât 2^53.
Doar pentru înregistrare, o altă abordare este de a utiliza prim descompunere. Dacă fiecare factor de descompunere este chiar, atunci numărul este pătrat perfect. Deci, ceea ce vrei este de a vedea dacă un număr se poate descompune ca un produs de pătrate de numere prime. Desigur, nu't nevoie pentru a obține un astfel de descompunere, doar pentru a vedea dacă acesta există.
În primul rând construi un tabel de pătrate de numere prime care sunt mai mici decât 2^32. Acest lucru este mult mai mică decât o masă de toate numere întregi până la această limită.
O soluție ar fi ca aceasta:
boolean isPerfectSquare(long number)
{
if (number < 0) return false;
if (number < 2) return true;
for (int i = 0; ; i++)
{
long square = squareTable[i];
if (square > number) return false;
while (number % square == 0)
{
number /= square;
}
if (number == 1) return true;
}
}
Cred că's un pic criptic. Ceea ce face este verificarea la fiecare pas că pătratul unui număr prim împărțiți numărul de intrare. Dacă nu, atunci se împarte la numărul de pătrate atâta timp cât este posibil, pentru a elimina acest pătrat de prim descompunere. Dacă prin acest proces, am ajuns la 1, atunci numărul de intrare a fost o descompunere de metri de numere prime. În cazul în care piața devine mai mare decât numărul în sine, atunci nu există nici o modalitate de aceasta piata, sau mai mari, pătrate, puteți împărți, astfel încât numărul nu poate fi o descompunere de pătrate de numere prime.
Având în zilele noastre' sqrt făcut în hardware și necesitatea de a calcula numere prime aici, cred că această soluție este mult mai lent. Dar ar trebui să dea rezultate mai bune decât o soluție cu sqrt care a câștigat't de lucru peste 2^54, după cum spune mrzl în răspunsul său.
Un număr întreg problemă merită o soluție număr întreg. Astfel
Face binar de căutare pe (ne-negativă) de numere întregi pentru a găsi cel mai mare număr întreg t astfel ca t2 <= n. Atunci testa dacă
r2 = n` exact. Acest lucru ia timp O(log n).
Dacă tu nu't știu cum să binar de căutare numere întregi pozitive, deoarece setul este nemărginită, l's ușor. Incepand de calcul ta creșterea funcției f (sus f(t) = t**2 - n
) pe puterile lui doi. Când o vezi transforma pozitiv, ai'am găsit o limită superioară. Apoi, puteți face acest standard binar de căutare.
L's-a subliniat că ultimul " d " cifre ale unui pătrat perfect poate lua numai anumite valori. Ultima " d "cifre (în baza "b") de un număr " n "este la fel ca restul, atunci când" n "este împărțit de către" b " " d " , ie. în C notație n % pow(b, d)
.
Acest lucru poate fi generalizată la orice modulul "m", ie. n % m
pot fi utilizate pentru a exclude un procent de numere de a fi pătrate perfecte. Modulul utilizați în prezent este de 64, care permite 12, de exemplu. 19% din rest, posibil pătrate. Cu un pic de codificare am gasit modulul 110880, care permite doar 2016, ie. 1,8% din resturile posibil pătrate. Deci, în funcție de costul unui modul de funcționare (de exemplu. divizia a) și un tabel de căutare, comparativ cu o rădăcină pătrată pe mașină, folosind acest modul ar putea fi mai rapid.
Apropo, dacă Java are un mod de a stoca un pachet matrice de biți pentru tabelul de căutare, don't-l folosească. 110880 32-bit cuvinte nu este cu mult RAM în aceste zile și preluarea o mașină de cuvânt va fi mai rapid decât preluarea unui singur bit.
Pentru performanță, de foarte multe ori trebuie să faci unele compromsies. Alții și-au exprimat diverse metode, cu toate acestea, a remarcat Carmack's hack a fost mai rapid până la anumite valori ale N. Apoi, ar trebui să verificați "n" și dacă aceasta este mai mică decât numărul N, utilizarea Carmack's hack, altceva utilizați o altă metodă descrisă în răspunsurile aici.
Următoarele simplificarea maaartinus's soluție pare să-și radă câteva puncte procentuale pe cea de execuție, dar am'nu sunt destul de bun la benchmarking pentru a produce un punct de referință pot avea încredere:
long goodMask; // 0xC840C04048404040 computed below
{
for (int i=0; i<64; ++i) goodMask |= Long.MIN_VALUE >>> (i*i);
}
public boolean isSquare(long x) {
// This tests if the 6 least significant bits are right.
// Moving the to be tested bit to the highest position saves us masking.
if (goodMask << x >= 0) return false;
// Remove an even number of trailing zeros, leaving at most one.
x >>= (Long.numberOfTrailingZeros(x) & (-2);
// Repeat the test on the 6 least significant remaining bits.
if (goodMask << x >= 0 | x <= 0) return x == 0;
// Do it in the classical way.
// The correctness is not trivial as the conversion from long to double is lossy!
final long tst = (long) Math.sqrt(x);
return tst * tst == x;
}
Ar fi în valoare de verificat cum omițând primul test,
if (goodMask << x >= 0) return false;
ar putea afecta performanța.
Aceasta este cea mai rapidă implementare Java-am putut inventa, folosind o combinație de tehnici sugerate de altii in acest thread.
De asemenea, am experimentat cu aceste modificări, dar acestea nu ajuta la performanta:
public class SquareTester {
public static boolean isPerfectSquare(long n) {
if (n < 0) {
return false;
} else {
switch ((byte) n) {
case -128: case -127: case -124: case -119: case -112:
case -111: case -103: case -95: case -92: case -87:
case -79: case -71: case -64: case -63: case -60:
case -55: case -47: case -39: case -31: case -28:
case -23: case -15: case -7: case 0: case 1:
case 4: case 9: case 16: case 17: case 25:
case 33: case 36: case 41: case 49: case 57:
case 64: case 65: case 68: case 73: case 81:
case 89: case 97: case 100: case 105: case 113:
case 121:
long i = (n * INV3465) >>> 52;
if (! good3465[(int) i]) {
return false;
} else {
long r = round(Math.sqrt(n));
return r*r == n;
}
default:
return false;
}
}
}
private static int round(double x) {
return (int) Double.doubleToRawLongBits(x + (double) (1L << 52));
}
/** 3465<sup>-1</sup> modulo 2<sup>64</sup> */
private static final long INV3465 = 0x8ffed161732e78b9L;
private static final boolean[] good3465 =
new boolean[0x1000];
static {
for (int r = 0; r < 3465; ++ r) {
int i = (int) ((r * r * INV3465) >>> 52);
good3465[i] = good3465[i+1] = true;
}
}
}
Tu ar trebui să scape de la 2-puterea parte de N chiar de la început.
Al 2-lea Edit Magic expresie pentru m mai jos ar trebui să fie
m = N - (N & (N-1));
și nu cum este scris
Sfârșitul de al 2-lea edit
m = N & (N-1); // the lawest bit of N
N /= m;
byte = N & 0x0F;
if ((m % 2) || (byte !=1 && byte !=9))
return false;
1 a Edita:
Îmbunătățiri minore:
m = N & (N-1); // the lawest bit of N
N /= m;
if ((m % 2) || (N & 0x07 != 1))
return false;
Sfârșitul de 1 edit
Acum continua ca de obicei. În acest fel, până când ajunge la punct plutitoare parte, deja ai scapat de toate numerele a căror 2-puterea parte este ciudat (aproximativ jumătate), și apoi ia în considerare numai 1/8 din ce-a mai rămas. I. e. executați plutitoare punct de reper pe 6% din numere.
Proiect Euler este menționat în tag-uri și multe dintre problemele în ea necesită verificarea numere >> 2^64
. De cele mai multe optimizări menționate mai sus nu't de lucru cu ușurință atunci când lucrați cu o 80 de octeți buffer.
Am folosit java BigInteger și o versiune ușor modificată a lui Newton's metoda, care funcționează mai bine cu numere întregi. Problema a fost că exact pătrate n^2
convergente a (n-1)
în loc de n
pentru că `n^2-1 = (n-1)(n+1) și final de eroare a fost doar un pas mai jos de ultimul divizor și algoritmul se încheie. A fost ușor să se stabilească prin adăugarea unul original argument înainte de calcul eroarea. (Se adaugă două pentru rădăcini cubice, etc.)
Cel mai frumos atribut al acestui algoritm este că puteți să-i spuneți imediat dacă numărul este pătrat perfect - la final de eroare (nu de corecție) în Newton's metoda va fi zero. O simpla modificare, de asemenea, vă permite să calculeze rapid floor(sqrt(x))
în loc de cel mai apropiat număr întreg. Acest lucru este la îndemână cu mai multe Euler probleme.
Asta o rework din zecimal în binar vechi Marchant calculator algoritm (îmi pare rău, eu nu't au o referință), în Ruby, adaptate în mod special pentru această întrebare:
def isexactsqrt(v)
value = v.abs
residue = value
root = 0
onebit = 1
onebit <<= 8 while (onebit < residue)
onebit >>= 2 while (onebit > residue)
while (onebit > 0)
x = root + onebit
if (residue >= x) then
residue -= x
root = x + onebit
end
root >>= 1
onebit >>= 2
end
return (residue == 0)
end
Aici's o analiză de ceva similar (vă rugăm să don't votați-mă jos pentru stil de codificare/miroase sau greoaie O/O - it's algoritmul care contează, și C++ nu este casa mea limbă). În acest caz, ne-am're în căutarea pentru reziduuri == 0:
#include <iostream>
using namespace std;
typedef unsigned long long int llint;
class ISqrt { // Integer Square Root
llint value; // Integer whose square root is required
llint root; // Result: floor(sqrt(value))
llint residue; // Result: value-root*root
llint onebit, x; // Working bit, working value
public:
ISqrt(llint v = 2) { // Constructor
Root(v); // Take the root
};
llint Root(llint r) { // Resets and calculates new square root
value = r; // Store input
residue = value; // Initialise for subtracting down
root = 0; // Clear root accumulator
onebit = 1; // Calculate start value of counter
onebit <<= (8*sizeof(llint)-2); // Set up counter bit as greatest odd power of 2
while (onebit > residue) {onebit >>= 2; }; // Shift down until just < value
while (onebit > 0) {
x = root ^ onebit; // Will check root+1bit (root bit corresponding to onebit is always zero)
if (residue >= x) { // Room to subtract?
residue -= x; // Yes - deduct from residue
root = x + onebit; // and step root
};
root >>= 1;
onebit >>= 2;
};
return root;
};
llint Residue() { // Returns residue from last calculation
return residue;
};
};
int main() {
llint big, i, q, r, v, delta;
big = 0; big = (big-1); // Kludge for "big number"
ISqrt b; // Make q sqrt generator
for ( i = big; i > 0 ; i /= 7 ) { // for several numbers
q = b.Root(i); // Get the square root
r = b.Residue(); // Get the residue
v = q*q+r; // Recalc original value
delta = v-i; // And diff, hopefully 0
cout << i << ": " << q << " ++ " << r << " V: " << v << " Delta: " << delta << "\n";
};
return 0;
};
Îmi place ideea de a folosi-o aproape corect metoda pe unele de intrare. Aici este o versiune cu o mai mare "offset". Codul pare să funcționeze și trece mele test simplu caz.
Doar înlocui:
if(n < 410881L){...}
codul cu asta:
if (n < 11043908100L) {
//John Carmack hack, converted to Java.
// See: http://www.codemaestro.com/reviews/9
int i;
float x2, y;
x2 = n * 0.5F;
y = n;
i = Float.floatToRawIntBits(y);
//using the magic number from
//http://www.lomont.org/Math/Papers/2003/InvSqrt.pdf
//since it more accurate
i = 0x5f375a86 - (i >> 1);
y = Float.intBitsToFloat(i);
y = y * (1.5F - (x2 * y * y));
y = y * (1.5F - (x2 * y * y)); //Newton iteration, more accurate
sqrt = Math.round(1.0F / y);
} else {
//Carmack hack gives incorrect answer for n >= 11043908100.
sqrt = (long) Math.sqrt(n);
}
La sqrt apel nu este perfect corecte, așa cum a fost menționat, dar se's interesant și instructiv, care nu't sufla departe alte răspunsuri în termeni de viteză. După toate, secvența de limbaj de asamblare instrucțiuni pentru o sqrt este mic. Intel are un hardware de instrucțiuni, care este't folosit de Java cred pentru că nu't sunt conforme cu standardul IEEE.
De ce este atât de lent? Deoarece Java este de fapt un C rutină prin JNI, și-l's de fapt mai lent de a face atât de mult pentru a apela un Java subrutină, care în sine este mai lentă decât a face o linie. Acest lucru este foarte enervant, și Java ar fi trebuit să vină cu o soluție mai bună, de exemplu, clădirea în virgulă mobilă bibliotecă solicită, dacă este necesar. Ei bine.
În C++, bănuiesc că tot complexul alternative ar pierde pe viteza, dar nu am't le-ai verificat pe toate. Ceea ce am făcut, și de ce Java oamenii vor găsi util, este un hack simplu, o extensie de caz special de testare a sugerat de către A. Rex. De a folosi un singur timp valoarea ca un pic matrice, care este't limitele verificat. În acest fel, trebuie 64 bit boolean căutare.
typedef unsigned long long UVLONG
UVLONG pp1,pp2;
void init2() {
for (int i = 0; i < 64; i++) {
for (int j = 0; j < 64; j++)
if (isPerfectSquare(i * 64 + j)) {
pp1 |= (1 << j);
pp2 |= (1 << i);
break;
}
}
cout << "pp1=" << pp1 << "," << pp2 << "\n";
}
inline bool isPerfectSquare5(UVLONG x) {
return pp1 & (1 << (x & 0x3F)) ? isPerfectSquare(x) : false;
}
Rutina isPerfectSquare5 se execută în aproximativ 1/3 din timp pe core2 duo mașină. Bănuiesc că alte trucuri de-a lungul aceleași linii ar putea reduce timpul în continuare, în medie, dar de fiecare dată când verificați, sunt de tranzacționare pe mai multe teste pentru mai multe eliminând, astfel încât să puteți't merge prea departe pe acest drum.
Cu siguranță, mai degrabă decât un test separat pentru negativ, ai putea verifica înaltă de 6 biți în același mod.
Rețineți că toate I'm a face este eliminarea pătrate posibil, dar atunci când am un potențial caz trebuie să-l sun originale, inline isPerfectSquare.
La init2 de rutină este numit o dată pentru a inițializa valorile statice de pp1 și pp2. Rețineți că în implementare în C++, am'm folosind unsigned long long, deci din moment ce're a semnat, ai'd trebuie să utilizați >>> operator.
Nu există nici o nevoie intrinsecă de a verifica limitele matrice, dar Java's de optimizare trebuie să rezolv destul de repede, așa că am don't-i condamn pentru asta.