Ich suche nach dem schnellsten Weg, um festzustellen, ob ein long
-Wert ein perfektes Quadrat ist (d. H. Seine Quadratwurzel ist eine andere ganze Zahl):
Math.sqrt()
Verwendet habe, aber ich frage mich, ob es eine Möglichkeit gibt, dies schneller zu tun, indem Sich auf eine Ganzzahl-Domäne beschränkt.Hier ist der sehr einfache und unkomplizierte Weg, den ich 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. Also muss niemand sonst diesen Code beibehalten. Und diese Art der Mikrooptimierung könnte tatsächlich einen Unterschied machen, da es zum Teil die Herausforderung ist, 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:
0.5
zum Ergebnis von Math.sqrt () nicht erforderlich ist, zumindest nicht auf meinem Computer.Math.sqrt()
. Dies ist wahrscheinlich darauf zurückzuführen, dass Math.sqrt()
etwas ähnelt, das der Newton-Methode ähnelt, jedoch in der Hardware implementiert ist und daher viel schneller als in Java ist. Außerdem erforderte das Newtonsche Verfahren immer noch die Verwendung von Doubles.Math.sqrt()
.or
-Anweisungen in C++ schneller als mit einer switch
. In Java und C # scheint es jedoch keinen Unterschied zwischen or
und switch
zu geben.or
nur if(lookup[(int)(n&0x3F)]) { test } else return false;
sagen. Zu meiner Überraschung war das (nur ein bisschen) langsamer. Dies liegt daran, dass Array-Grenzen in Java geprüft werden. Ich habe eine Methode gefunden, die ~ 35% schneller arbeitet als Ihr 6-Bit-Code + Carmack + sqrt-Code, zumindest bei meiner CPU (x86) und meiner Programmiersprache (C/C++). Ihre Ergebnisse können variieren, vor allem, weil ich nicht weiß, wie sich der Java-Faktor auswirkt.
Mein Ansatz ist dreifach:
int64 x
ist.) if( x < 0 || (x&2) || ((x & 7) == 5) || ((x & 11) == 8) )
return false;
if( x == 0 )
return true;
int64 y = x;
y = (y & 4294967295LL) + (y >> 32);
y = (y & 65535) + (y >> 16);
y = (y & 255) + ((y >> 8) & 255) + (y >> 16);
// At this point, y is between 0 and 511. More code can reduce it farther.
Um zu überprüfen, ob der Rückstand tatsächlich ein Quadrat ist, schaue ich die Antwort in einer vorberechneten Tabelle nach.if( bad255[y] )
return false;
// However, I just use a table of size 512
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;
Damit unsere Zahl ein Quadrat sein kann, muss es 1 Mod 8 sein.if((x & 7) != 1)
return false;
Die Grundstruktur von Hensels Lemma ist die folgende. (Hinweis: ungeprüfter Code; wenn dies nicht funktioniert, versuchen Sie t = 2 oder 8.) 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;
// Repeat until t is 2^33 or so. Use a loop if you want.
Die Idee ist, dass Sie bei jeder Iteration ein Bit zu r hinzufügen, der "aktuellen" Quadratwurzel von x; Jede Quadratwurzel ist genau Modulo eine größere und größere Potenz von 2, nämlich t/2. Am Ende sind r und t/2-r Quadratwurzeln von x modulo t/2. (Beachten Sie, dass, wenn r eine Quadratwurzel von x ist, auch -r ist. Dies gilt auch für Modulo-Zahlen. Beachten Sie jedoch, dass Modulo einige Zahlen enthält. Die Werte können sogar mehr als 2 Quadratwurzeln haben, insbesondere Potenzen von 2. ) Da unsere tatsächliche Quadratwurzel weniger als 2 ^ 32 beträgt, können wir an diesem Punkt tatsächlich nur prüfen, ob r oder t/2-r echte Quadratwurzeln sind. In meinem aktuellen Code verwende ich die folgende modifizierte Schleife: 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) );
Die Beschleunigung wird hier auf drei Arten erhalten: vorberechneter Startwert (entspricht ~ 10 Iterationen der Schleife), früheres Beenden der Schleife und Überspringen einiger t-Werte. Für den letzten Teil schaue ich auf z = r - x * x
und setze t auf die größte Potenz von 2, die z mit einem kleinen Trick teilt. Dadurch kann ich Werte überspringen, die den Wert von r nicht beeinflusst hätten. Der vorberechnete Startwert in meinem Fall wählt die "kleinste positive" Quadratwurzel modulo 8192 ..__ aus.Auch wenn dieser Code für Sie nicht schneller funktioniert, hoffe ich, dass Ihnen einige der darin enthaltenen Ideen gefallen. Es folgt vollständiger, getesteter Code einschließlich der vorberechneten Tabellen.
typedef signed 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 square( int64 x ) {
// Quickfail
if( 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;
}
Ich bin ziemlich spät auf der Party, aber ich hoffe, dass ich eine bessere Antwort geben kann. kürzer und (vorausgesetzt mein Benchmark ist korrekt) auch viel schneller .
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;
}
Der erste Test fängt die meisten Nichtquadrate schnell. Es verwendet eine Tabelle mit 64 Elementen, die in einer langen Packung gepackt ist, so dass keine Array-Zugriffskosten anfallen (Indirektion und Begrenzungsprüfungen). Für eine einheitlich zufällige long
besteht hier eine Wahrscheinlichkeit von 81,25%.
Der zweite Test erfasst alle Zahlen mit einer ungeraden Anzahl von Zweien in ihrer Faktorisierung. Die Methode Long.numberOfTrailingZeros
ist sehr schnell, da sie in eine einzige i86-Anweisung umgewandelt wird.
Nach dem Ablegen der abschließenden Nullen verarbeitet der dritte Test Zahlen, die mit 011, 101 oder 111 enden und binäre Zahlen sind, bei denen es sich nicht um perfekte Quadrate handelt. Es kümmert sich auch um negative Zahlen und behandelt auch 0.
Der letzte Test fällt auf double
Arithmetik zurück. Da double
nur 53 Bits Mantisse hat, enthält Die Konvertierung von long
in double
die Rundung für große Werte. Trotzdem ist der Test korrekt (es sei denn der proof ist falsch).
Der Versuch, die Idee von mod255 zu integrieren, war nicht erfolgreich.
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 können jedoch eine schnelle Überprüfung durchführen, um einige Möglichkeiten auszuschließen, bevor Sie Ihre Quadratwurzel-Routine aufrufen. Sehen Sie sich zum Beispiel die letzte Ziffer Ihrer Zahl in Hex an, indem Sie "und" bitweise ausführen. Perfekte Quadrate können nur in 0, 1, 4 oder 9 in der Basis 16 enden. Für 75% Ihrer Eingaben (vorausgesetzt, sie sind gleichmäßig verteilt) können Sie einen Aufruf an die Quadratwurzel im Austausch gegen einige sehr schnelle Bit-Verwirrungen vermeiden.
Kip hat den folgenden Code mit einem Hex-Trick verglichen. Beim Testen der Nummern 1 bis 100.000.000 lief dieser Code doppelt so schnell wie das Original.
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;
}
}
Als ich den analogen Code in C++ getestet habe, lief er tatsächlich langsamer als das Original. Wenn ich jedoch die switch-Anweisung beseitigt, macht der Hex-Trick den Code doppelt so schnell.
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;
}
Die Eliminierung der switch-Anweisung hatte wenig Auswirkungen auf den C # -Code.
Ich habe über die schrecklichen Zeiten nachgedacht, die ich im Numerical Analysis-Kurs verbracht habe.
Und dann erinnere ich mich, dass sich diese Funktion um das 'Netz aus dem Quake Source-Code drehte:
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;
}
Was im Grunde eine Quadratwurzel berechnet, unter Verwendung der Newton-Approximationsfunktion (kann sich nicht an den genauen Namen erinnern).
Es sollte verwendbar sein und vielleicht sogar schneller sein, es stammt aus einem der phänomenalen Id-Software-Spiele!
Es ist in C++ geschrieben, aber es sollte nicht zu schwer 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 auf Wikipedia erklärt: http://en.wikipedia.org/wiki/Newton%27s_method
Sie können dem Link folgen, um weitere Informationen zur Funktionsweise zu erhalten. Wenn Sie sich jedoch nicht besonders darum kümmern, kann ich mich an das Lesen des Blogs und an den Numerical Analysis-Kurs erinnern.
* (long*) &y
ist im Grunde eine Funktion zum schnellen Konvertieren in lange, so dass Ganzzahloperationen auf die rohen Bytes angewendet werden können.0x5f3759df - (i >> 1);
-Zeile ist ein vorberechneter Startwert für die Näherungsfunktion.* (float*) &i
konvertiert den Wert zurück in Fließkommazahl.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 es nicht für Sie wäre ... dann könnten Sie so viel Iteration hinzufügen, wie Sie benötigen.
Dies sollte schneller sein, da dadurch die Anzahl der durch naive Quadratwurzeln durchgeführten Divisionsoperationen auf eine einfache Division durch 2 (eigentlich eine * 0.5F
-Multiplikationsoperation) reduziert und stattdessen durch einige feste Anzahl von Multiplikationsoperationen ersetzt wird.
Ich bin nicht sicher, ob es schneller oder sogar genauer wäre, aber Sie könnten den John Carmacks magischen Quadratwurzel - Algorithmus verwenden, um die Quadratwurzel schneller zu lösen. Sie können dies wahrscheinlich für alle möglichen 32-Bit-Ganzzahlen testen und bestätigen, dass Sie tatsächlich korrekte Ergebnisse erhalten haben, da dies nur eine Annäherung ist. Aber jetzt, wo ich darüber nachdenke, ist die Verwendung von Doubles auch näherungsweise, daher bin ich mir nicht sicher, wie das ins Spiel kommen würde.
Wenn Sie einen binären Chop ausführen, um zu versuchen, die "richtige" Quadratwurzel zu finden, können Sie ziemlich leicht feststellen, ob der Wert, den Sie erhalten, nahe genug ist, um Folgendes zu erkennen:
(n+1)^2 = n^2 + 2n + 1
(n-1)^2 = n^2 - 2n + 1
Nachdem Sie n^2
berechnet haben, sind die Optionen:
n^2 = target
: fertig, gebe true zurückn^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 einer niedrigeren n
target > n^2 + 2n + 1
: binärer Chop auf einer höheren n
(Entschuldigung, dies verwendet n
als aktuellen Schätzwert und target
für den Parameter. Bitte entschuldigen Sie die Verwirrung!)
Ich weiß nicht, ob es schneller geht oder nicht, aber es ist einen Versuch wert.
BEARBEITEN: Der binäre Chop muss nicht den gesamten Bereich der ganzen Zahlen, (2^x)^2 = 2^(2x)
, berücksichtigen. Wenn Sie also das oberste gesetzte Bit in Ihrem Ziel gefunden haben (was mit einem etwas verwirrenden Trick möglich ist, vergesse ich genau, wie ) Sie können schnell eine Reihe möglicher Antworten erhalten. Wohlgemerkt, ein naiver binärer Chop wird immer noch bis zu 31 oder 32 Iterationen dauern.
Ich führte eine eigene Analyse einiger Algorithmen in diesem Thread durch und kam zu neuen Ergebnissen. Sie können diese alten Ergebnisse im Bearbeitungsverlauf dieser Antwort sehen, aber sie sind nicht genau, da ich einen Fehler gemacht habe und Zeit verschwendete, um mehrere Algorithmen zu analysieren, die nicht nahe liegen. Ich habe jedoch aus verschiedenen Antworten Lektionen gezogen und habe nun zwei Algorithmen, die den "Gewinner" dieses Threads zerstören. Hier ist der Kern, den ich anders mache als alle anderen:
// 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;
Diese einfache Zeile, die meistens ein oder zwei sehr schnelle Anweisungen hinzufügt, vereinfacht die switch-case
-Anweisung jedoch stark in eine if-Anweisung. Es kann jedoch zur Laufzeit beitragen, wenn viele der getesteten Zahlen signifikante Zweierpotenzfaktoren haben.
Die folgenden Algorithmen sind wie folgt:
Hier ist eine Beispiellaufzeit, wenn die Nummern mit Math.abs(Java.util.Random.nextLong())
generiert werden.
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
Und hier ist eine Beispiellaufzeit, wenn sie nur auf der ersten Million Longs läuft:
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
Wie Sie sehen, ist DurronTwo
für große Eingaben besser geeignet, da dieser den Zaubertrick sehr oft verwendet, aber im Vergleich zum ersten Algorithmus und Math.sqrt
wegen der viel kleineren Zahlen etwas unklarer wird. In der Zwischenzeit ist die einfachere Durron
ein großer Gewinner, da sie sich in den ersten Millionen Zahlen nie viele Male durch 4 teilen muss.
Hier ist 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;
}
Und 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;
}
Und mein Benchmark-Gurtzeug: (Benötigt Google Caliper 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);
}
}
UPDATE: Ich habe einen neuen Algorithmus entwickelt, der in einigen Szenarien schneller und in anderen langsamer ist. Ich habe unterschiedliche Benchmarks erhalten, die auf verschiedenen Eingaben basieren. Wenn wir modulo 0xFFFFFF = 3 x 3 x 5 x 7 x 13 x 17 x 241
berechnen, können wir 97,82% von Zahlen entfernen, die keine Quadrate sein können. Dies kann (in einer Art) in einer Zeile mit 5 bitweisen Operationen erfolgen:
if (!goodLookupSquares[(int) ((n & 0xFFFFFFl) + ((n >> 24) & 0xFFFFFFl) + (n >> 48))]) return false;
Der resultierende Index ist entweder 1) der Rest, 2) der Rest + 0xFFFFFF
oder 3) der Rest + 0x1FFFFFE
. Natürlich benötigen wir eine Nachschlagetabelle für die Residuen modulo 0xFFFFFF
, bei der es sich um eine 3-MB-Datei handelt (in diesem Fall als ASCII-Text-Dezimalzahl gespeichert, nicht optimal, aber eindeutig mit einer ByteBuffer
usw. verbesserbar. Aber da dies eine Vorberechnung ist ist nicht so wichtig. Sie finden die Datei hier (oder erzeugen Sie sie selbst):
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;
}
Ich lade es in ein boolean
-Array wie folgt:
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();
}
Beispiel Laufzeit. Es schlug Durron
(Version eins) in jeder von mir durchgeführten Prüfung.
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
Es sollte viel schneller sein, Newtons Methode zu verwenden, um Ganzzahlwurzel zu berechnen, diese Zahl dann zu quadrieren und zu prüfen, wie Sie es in Ihrer aktuellen Lösung tun. Die Newton-Methode ist die Basis für die in 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 Digital Root einer Zahl nicht auf 1, 4, 7 oder 9 endet, ist die Zahl nicht ein perfektes Quadrat. Auf diese Weise können Sie 60% Ihrer Eingaben schnell entfernen, bevor Sie den langsameren Quadratwurzel-Algorithmus anwenden.
Ich möchte, dass diese Funktion mit allen .__ funktioniert. positive 64-Bit-Ganzzahlen mit Vorzeichen
Math.sqrt()
arbeitet mit double-Werten als Eingabeparameter, so dass Sie für Ganzzahlen größer als 2 ^ 53 keine genauen Ergebnisse erhalten.
Für das Protokoll ist ein anderer Ansatz die Verwendung der primären Zerlegung. Wenn jeder Faktor der Zerlegung gerade ist, ist die Zahl ein perfektes Quadrat. Sie wollen also sehen, ob eine Zahl als Produkt aus Quadraten von Primzahlen zerlegt werden kann. Natürlich brauchen Sie eine solche Zerlegung nicht zu erhalten, nur um zu sehen, ob sie existiert.
Baue zuerst eine Tabelle mit Quadraten mit Primzahlen, die niedriger als 2 ^ 32 sind. Dies ist viel kleiner als eine Tabelle aller ganzen Zahlen bis zu dieser Grenze.
Eine Lösung wäre dann so:
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;
}
}
Ich denke, es ist ein bisschen kryptisch. In jedem Schritt wird geprüft, ob das Quadrat einer Primzahl die eingegebene Nummer teilt. Wenn dies der Fall ist, dividiert es die Zahl durch das Quadrat, so lange es möglich ist, dieses Quadrat aus der primären Zerlegung zu entfernen. Wenn wir durch diesen Vorgang auf 1 kamen, war die eingegebene Zahl eine Zerlegung des Quadrats von Primzahlen. Wenn das Quadrat größer wird als die Zahl selbst, dann gibt es keine Möglichkeit, dieses Quadrat oder größere Quadrate zu teilen, so dass die Anzahl keine Quadrate von Primzahlen sein kann.
Ich denke, diese Lösung ist viel langsamer, da die Hardware heutzutage in Hardware gemacht wird und hier Primzahlen berechnet werden müssen. Aber es sollte bessere Ergebnisse geben als eine Lösung mit sqrt, die über 2 ^ 54 nicht funktioniert, wie Mrzl in seiner Antwort sagt.
Ein ganzzahliges Problem verdient eine ganzzahlige Lösung. Somit
Führen Sie die binäre Suche nach (nicht negativen) Ganzzahlen durch, um die größte Ganzzahl t zu finden, z. B. t**2 <= n
. Dann testen Sie, ob r**2 = n
genau. Dies dauert Zeit O (log n).
Wenn Sie nicht wissen, wie die positiven ganzen Zahlen binär gesucht werden sollen, weil die Menge unbegrenzt ist, ist das einfach. Sie beginnen damit, Ihre aufsteigende Funktion f (über f(t) = t**2 - n
) auf Zweierpotenzen zu berechnen. Wenn Sie sehen, dass es positiv wird, haben Sie eine obere Grenze gefunden. Dann können Sie die Standard-Binärsuche durchführen.
Es wurde darauf hingewiesen, dass die letzten d
-Ziffern eines perfekten Quadrats nur bestimmte Werte annehmen können. Die letzten d
-Ziffern (in Basis b
) einer Zahl n
sind die gleichen wie die restlichen, wenn n
durch b
geteilt wirdd
dh in C-Notation n % pow(b, d)
.
Dies kann auf jeden Modul m
verallgemeinert werden, dh. n % m
kann verwendet werden, um einen bestimmten Prozentsatz von Zahlen von perfekten Quadraten auszuschließen. Das Modul, das Sie derzeit verwenden, beträgt 64, was 12 erlaubt, d. H. 19% der Reste, möglichst Quadrate. Mit etwas Codierung fand ich den Modul 110880, der nur 2016 erlaubt, dh. 1,8% der Reste als mögliche Quadrate. Abhängig von den Kosten einer Moduloperation (dh Division) und einer Tabellensuche im Vergleich zu einer Quadratwurzel auf Ihrer Maschine kann die Verwendung dieses Moduls schneller sein.
Wenn Java ein gepacktes Bit-Array für die Nachschlagetabelle speichern kann, verwenden Sie es übrigens nicht. 110880 32-Bit-Wörter sind heutzutage nicht viel RAM und das Abrufen einer Maschine Word wird schneller als das Abrufen eines einzelnen Bits.
Für die Leistung müssen Sie häufig einige Kompromisse machen. Andere haben verschiedene Methoden zum Ausdruck gebracht, Sie haben jedoch festgestellt, dass Carmacks Hack bis zu bestimmten Werten von N schneller war. Dann sollten Sie das "n" überprüfen und wenn es weniger als diese Zahl N ist, verwenden Sie den Carmack-Hack in den Antworten hier.
Dies ist die schnellste Java-Implementierung, die ich mit einer Kombination von Techniken entwickeln könnte, die von anderen in diesem Thread vorgeschlagen wurden.
Ich habe auch mit diesen Modifikationen experimentiert, aber sie haben die Leistung nicht verbessert:
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;
}
}
}
Die folgende Vereinfachung der Lösung von maaartinus scheint die Laufzeit um ein paar Prozentpunkte zu reduzieren, aber ich bin nicht gut genug beim Benchmarking, um einen Benchmark zu erstellen, dem ich vertrauen kann:
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;
}
Es lohnt sich zu prüfen, wie der erste Test ausgelassen wird,
if (goodMask << x >= 0) return false;
würde die Leistung beeinflussen.
Du solltest den 2-Power-Teil von N von Anfang an loswerden.
2nd Edit Der magische Ausdruck für m sollte sein
m = N - (N & (N-1));
und nicht wie geschrieben
Ende der 2. Bearbeitung
m = N & (N-1); // the lawest bit of N
N /= m;
byte = N & 0x0F;
if ((m % 2) || (byte !=1 && byte !=9))
return false;
1. Bearbeitung:
Kleine Verbesserung:
m = N & (N-1); // the lawest bit of N
N /= m;
if ((m % 2) || (N & 0x07 != 1))
return false;
Ende der 1. Bearbeitung
Nun weiter wie gewohnt. Auf diese Weise haben Sie zum Zeitpunkt, zu dem Sie den Fließkomma-Teil erreichen, bereits alle Zahlen losgelassen, deren 2-Potenz-Teil ungerade ist (ungefähr die Hälfte), und dann berücksichtigen Sie nur noch 1/8 der verbleibenden Werte. Das heißt Sie führen den Fließkommaanteil auf 6% der Zahlen.
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 Ihr:
if(n < 410881L){...}
code mit diesem Code:
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);
}
Dies ist eine Überarbeitung von dezimal nach binär des alten Marchant-Rechneralgorithmus (sorry, ich habe keine Referenz) in Ruby, der speziell für diese Frage angepasst wurde:
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
Hier ist eine Aufarbeitung von etwas Ähnlichem (bitte stimmen Sie mich nicht wegen Codierstil/-gerüchen oder plumpem O/O ab - es ist der Algorithmus, der zählt, und C++ ist nicht meine Sprache). In diesem Fall suchen wir nach Rückstand == 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;
};
Project Euler wird in den Tags erwähnt, und viele der darin enthaltenen Probleme erfordern eine Überprüfung der Nummern >> 2 ^ 64. Die meisten der oben genannten Optimierungen funktionieren nicht leicht, wenn Sie mit einem 80-Byte-Puffer arbeiten.
Ich habe Java BigInteger und eine leicht modifizierte Version der Newton-Methode verwendet, die mit Ganzzahlen besser funktioniert. Das Problem war, dass exakte Quadrate n ^ 2 zu (n-1) statt zu n konvergierten, weil n ^ 2-1 = (n-1) (n + 1) und der letzte Fehler nur eine Stufe unter dem endgültigen Divisor und dem Algorithmus beendet. Es war leicht zu beheben, indem vor dem Berechnen des Fehlers ein Argument zum ursprünglichen Argument hinzugefügt wurde. (Fügen Sie zwei für Würfelwurzeln usw. hinzu)
Ein schönes Attribut dieses Algorithmus ist, dass Sie sofort feststellen können, ob die Zahl ein perfektes Quadrat ist - der endgültige Fehler (nicht die Korrektur) in der Newton-Methode wird Null sein. Mit einer einfachen Änderung können Sie auch schnell den Boden (sqrt (x)) anstelle der nächsten Ganzzahl berechnen. Dies ist praktisch bei mehreren Euler-Problemen.
Der sqrt-Aufruf ist, wie bereits erwähnt, nicht ganz genau, aber es ist interessant und lehrreich, dass er die anderen Antworten in Bezug auf Geschwindigkeit nicht wegbläst. Immerhin ist die Reihenfolge der Anweisungen für die Assembler-Sprache für ein Quadrat klein. Intel hat eine Hardwareanweisung, die, so glaube ich, nicht von Java verwendet wird, da sie nicht IEEE-konform ist.
Warum ist es langsam? Weil Java tatsächlich eine C-Routine über JNI aufruft, und dies ist tatsächlich langsamer als das Aufrufen einer Java-Subroutine, die selbst langsamer ist als die Inline-Ausführung. Dies ist sehr ärgerlich, und Java sollte eine bessere Lösung finden, dh, wenn nötig, Aufrufe von Fließkommabibliotheken enthalten. Naja.
In C++ vermute ich, dass alle komplexen Alternativen an Geschwindigkeit verlieren werden, 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 des Spezialfalltests vorgeschlagen von A. Rex. Verwenden Sie einen einzelnen langen Wert als Bit-Array, das nicht geprüft wird. Auf diese Weise haben Sie eine 64-Bit-Boolesche Suche.
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;
}
Die Routine isPerfectSquare5 läuft in etwa 1/3 der Zeit auf meinem Core2-Duo-Computer. Ich vermute, dass weitere Anpassungen in derselben Richtung die Zeit im Durchschnitt weiter reduzieren könnten, aber jedes Mal, wenn Sie nachschauen, werden Sie mehr Tests für mehr Eliminierung durchführen, sodass Sie auf dieser Straße nicht viel weiter gehen können.
Anstatt einen separaten Negativtest durchzuführen, können Sie die hohen 6 Bits auf die gleiche Weise überprüfen.
Beachten Sie, dass ich nur mögliche Quadrate eliminieren muss, aber wenn ich einen potenziellen Fall habe, muss ich das ursprüngliche, eingebettete isPerfectSquare nennen.
Die init2-Routine wird einmal aufgerufen, um die statischen Werte von pp1 und pp2 ..__ zu initialisieren. Beachten Sie, dass ich in meiner Implementierung in C++ lange nicht signierte Zeichen gebe. Da Sie also signiert sind, müssen Sie das> verwenden >> Betreiber.
Es gibt keine intrinsische Notwendigkeit, das Array zu prüfen, aber Javas Optimierer muss das alles ziemlich schnell herausfinden, deshalb mache ich ihnen nichts dafür.
Ich habe alle möglichen Ergebnisse überprüft, wenn die letzten n Bits eines Quadrats beobachtet werden. Durch sukzessive Prüfung mehrerer Bits können bis zu 5/6 der Eingänge eliminiert werden. Ich habe das eigentlich so entworfen, dass es den Fermat-Faktorisierungsalgorithmus implementiert, und es ist dort sehr schnell.
public static boolean isSquare(final long val) {
if ((val & 2) == 2 || (val & 7) == 5) {
return false;
}
if ((val & 11) == 8 || (val & 31) == 20) {
return false;
}
if ((val & 47) == 32 || (val & 127) == 80) {
return false;
}
if ((val & 191) == 128 || (val & 511) == 320) {
return false;
}
// if((val & a == b) || (val & c == d){
// return false;
// }
if (!modSq[(int) (val % modSq.length)]) {
return false;
}
final long root = (long) Math.sqrt(val);
return root * root == val;
}
Das letzte Bit des Pseudocodes kann verwendet werden, um die Tests zu erweitern, um weitere Werte zu entfernen. Die obigen Tests gelten für k = 0, 1, 2, 3
Zuerst wird geprüft, ob es ein Quadrat mit zwei Potenzmodulen hat, dann wird anhand eines Endmoduls getestet. Anschließend wird mit Math.sqrt ein Abschlusstest durchgeführt. Ich kam auf die Idee vom obersten Posten und versuchte, darauf einzugehen. Ich freue mich über Kommentare oder Vorschläge.
Update: Mit dem Test mit einem Modul (modSq) und einer Modulbasis von 44352 läuft mein Test in 96% der Zeit des im Update des OP für Zahlen bis zu 1.000.000.000.
In Anbetracht der allgemeinen Bitlänge (obwohl ich hier einen bestimmten Typ verwendet habe), habe ich versucht, die vereinfachten Algorithmen wie folgt zu entwerfen. Eine einfache und offensichtliche Prüfung auf 0,1,2 oder <0 ist anfangs erforderlich. Das Folgende ist einfach in dem Sinne, dass es nicht versucht, vorhandene mathematische Funktionen zu verwenden. Die meisten Operatoren können durch bitweise Operatoren ersetzt werden. Ich habe jedoch noch keine Benchmark-Daten getestet. Ich bin weder ein Experte für Mathematik noch für das Design von Computeralgorithmen. Ich würde mich freuen, wenn Sie auf ein Problem aufmerksam werden. Ich weiß, dass es dort viele Verbesserungschancen gibt.
int main()
{
unsigned int c1=0 ,c2 = 0;
unsigned int x = 0;
unsigned int p = 0;
int k1 = 0;
scanf("%d",&p);
if(p % 2 == 0) {
x = p/2;
}
else {
x = (p/2) +1;
}
while(x)
{
if((x*x) > p) {
c1 = x;
x = x/2;
}else {
c2 = x;
break;
}
}
if((p%2) != 0)
c2++;
while(c2 < c1)
{
if((c2 * c2 ) == p) {
k1 = 1;
break;
}
c2++;
}
if(k1)
printf("\n Perfect square for %d", c2);
else
printf("\n Not perfect but nearest to :%d :", c2);
return 0;
}
Ich weiß nicht, ob dies schon erwähnt wurde. Aber ich habe eine Lösung gefunden hier :
int result = (int)(floor(sqrt(b)) - ceil(sqrt(a)) + 1);
Wenn Geschwindigkeit ein Problem ist, können Sie die am häufigsten verwendeten Eingaben und deren Werte in eine Nachschlagetabelle partitionieren und dann den optimierten magischen Algorithmus ausführen, den Sie für die Ausnahmefälle entwickelt haben.
Hier ist der einfachste und prägnanteste Weg, obwohl ich nicht weiß, wie es in Bezug auf die CPU-Zyklen ist. Dies funktioniert gut, wenn Sie nur wissen möchten, ob die Wurzel eine ganze Zahl ist. Wenn es Ihnen wirklich wichtig ist, ob es eine ganze Zahl ist, können Sie dies auch herausfinden. Hier ist eine einfache (und reine) Funktion:
public static boolean isRootWhole(double number) {
return Math.sqrt(number) % 1 == 0;
}
Wenn Sie keine Mikrooptimierung benötigen, ist diese Antwort in Bezug auf Einfachheit und Wartbarkeit besser. Wenn Sie negative Zahlen erhalten, möchten Sie vielleicht Math.abs () für das Argument number als Argument Math.sqrt () verwenden.
Bei meiner 3,6 GHz Intel i7-4790-CPU dauerte die Ausführung dieses Algorithmus bei 0 - 10.000.000 durchschnittlich 35 - 37 Nanosekunden pro Berechnung. Ich habe zehn aufeinanderfolgende Läufe gemacht und die durchschnittliche Zeit für jede der zehn Millionen Quadratmeter-Berechnungen ausgedruckt. Jeder Gesamtlauf dauerte nur etwas mehr als 600 ms.
Wenn Sie eine geringere Anzahl von Berechnungen durchführen, dauert die vorherige Berechnung etwas länger.
Es sollte möglich sein, das 'packen' nicht perfekt zu platzieren, wenn die letzten X-Ziffern viel effizienter sind als N! Ich verwende Java 32-Bit-Ints und produziere genug Daten, um die letzten 16 Bits der Zahl zu prü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:
public static void main(String[] args) {
final int BITS = 16;
BitSet foo = new BitSet();
for(int i = 0; i< (1<<BITS); i++) {
int sq = (i*i);
sq = sq & ((1<<BITS)-1);
foo.set(sq);
}
System.out.println("int[] mayBeASquare = {");
for(int i = 0; i< 1<<(BITS-5); i++) {
int kk = 0;
for(int j = 0; j<32; j++) {
if(foo.get((i << 5) | j)) {
kk |= 1<<j;
}
}
System.out.print("0x" + Integer.toHexString(kk) + ", ");
if(i%8 == 7) System.out.println();
}
System.out.println("};");
}
und hier sind die Ergebnisse:
(ed: entschuldigt für schlechte Leistung in prettify.js; sehen Sie den Revisionsverlauf an.)
Der beste Algorithmus für das Problem ist möglicherweise ein schneller Ganzzahl-Quadratwurzel-Algorithmus https://stackoverflow.com/a/51585204/5191852
@Kde behauptet, drei Iterationen der Newton-Methode würden für die Genauigkeit von ± 1 für 32-Bit-Ganzzahlen ausreichen. Sicherlich werden für 64-Bit-Ganzzahlen mehr Iterationen benötigt, möglicherweise 6 oder 7.
In Bezug auf die Carmac-Methode scheint es ziemlich einfach zu sein, noch einmal zu iterieren, was die Anzahl der Stellen der Genauigkeit verdoppeln sollte. Es ist immerhin eine extrem abgeschnittene iterative Methode - Newton's, mit einer sehr guten ersten Vermutung.
In Bezug auf Ihr derzeitiges Bestes sehe ich zwei Mikrooptimierungen:
I.e:
// Divide out powers of 4 using binary search
if((n & 0x3L) == 0) {
n >>=2;
if((n & 0xffffffffL) == 0)
n >>= 32;
if((n & 0xffffL) == 0)
n >>= 16;
if((n & 0xffL) == 0)
n >>= 8;
if((n & 0xfL) == 0)
n >>= 4;
if((n & 0x3L) == 0)
n >>= 2;
}
Noch besser könnte ein einfacher sein
while ((n & 0x03L) == 0) n >>= 2;
Natürlich wäre es interessant zu wissen, wie viele Zahlen an jedem Checkpoint ausgesondert werden.
Wenn Sie nicht ganzzahlige Operationen vermeiden möchten, können Sie die folgende Methode verwenden. Es verwendet im Wesentlichen die Newton-Methode, die für die Ganzzahlarithmetik modifiziert ist.
/**
* Test if the given number is a perfect square.
* @param n Must be greater than 0 and less
* than Long.MAX_VALUE.
* @return <code>true</code> if n is a perfect
* square, or <code>false</code> otherwise.
*/
public static boolean isSquare(long n)
{
long x1 = n;
long x2 = 1L;
while (x1 > x2)
{
x1 = (x1 + x2) / 2L;
x2 = n / x1;
}
return x1 == x2 && n % x1 == 0L;
}
Diese Implementierung kann nicht mit Lösungen konkurrieren, die Math.sqrt
verwenden. Die Leistung kann jedoch verbessert werden, indem die in einigen anderen Beiträgen beschriebenen Filtermechanismen verwendet werden.
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 leicht einen Bereich für solution
basierend auf der Anzahl der Ziffern von number
bestimmen:
number
hat 1 Ziffer: 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 einer binären Suchmethode verwenden, um zu sehen, ob es eine solution
gibt, für die:
number == solution * solution
Hier ist der Code
Hier ist meine Klasse SquareRootChecker
public class SquareRootChecker {
private long number;
private long initialLow;
private long initialHigh;
public SquareRootChecker(long number) {
this.number = number;
initialLow = 1;
initialHigh = 4;
if (Long.toString(number).length() % 2 == 0) {
initialLow = 3;
initialHigh = 10;
}
for (long i = 0; i < Long.toString(number).length() / 2; i++) {
initialLow *= 10;
initialHigh *= 10;
}
if (Long.toString(number).length() % 2 == 0) {
initialLow /= 10;
initialHigh /=10;
}
}
public boolean checkSquareRoot() {
return findSquareRoot(initialLow, initialHigh, number);
}
private boolean findSquareRoot(long low, long high, long number) {
long check = low + (high - low) / 2;
if (high >= low) {
if (number == check * check) {
return true;
}
else if (number < check * check) {
high = check - 1;
return findSquareRoot(low, high, number);
}
else {
low = check + 1;
return findSquareRoot(low, high, number);
}
}
return false;
}
}
Und hier ist ein Beispiel, wie man es benutzt.
long number = 1234567;
long square = number * number;
SquareRootChecker squareRootChecker = new SquareRootChecker(square);
System.out.println(square + ": " + squareRootChecker.checkSquareRoot()); //Prints "1524155677489: true"
long notSquare = square + 1;
squareRootChecker = new SquareRootChecker(notSquare);
System.out.println(notSquare + ": " + squareRootChecker.checkSquareRoot()); //Prints "1524155677490: false"
Wenn Sie Geschwindigkeit wünschen, da Ihre Ganzzahlen von begrenzter Größe sind, vermute ich, dass der schnellste Weg die (a) Partition der Parameter nach Größe (z. B. in Kategorien nach dem größten Bitsatz) beinhaltet und dann den Wert anhand eines Arrays aus perfekten Quadraten prüft innerhalb dieses Bereichs.
Ich bin nicht sicher, ob dies der schnellste Weg ist, aber das ist etwas, auf das ich (vor langer Zeit in der High School) gestoßen bin, als ich mich während meines Matheunterrichts gelangweilt hatte und mit meinem Taschenrechner spielte. Zu dieser Zeit war ich wirklich erstaunt, dass es funktionierte ...
public static boolean isIntRoot(int number) {
return isIntRootHelper(number, 1);
}
private static boolean isIntRootHelper(int number, int index) {
if (number == index) {
return true;
}
if (number < index) {
return false;
}
else {
return isIntRootHelper(number - 2 * index, index + 1);
}
}
"Ich suche den schnellsten Weg, um festzustellen, ob ein langer Wert ein perfektes Quadrat ist (d. H. Seine Quadratwurzel ist eine andere Ganzzahl)."
Die Antworten sind beeindruckend, aber ich habe keinen einfachen Check erhalten:
prüfen Sie, ob die erste Zahl rechts der langen Nummer ein Mitglied des Sets ist (0,1,4,5,6,9). Wenn dies nicht der Fall ist, kann es unmöglich ein "perfektes Quadrat" sein.
z.B.
4567 - kann kein perfektes Quadrat sein.
Quadratwurzeln nach Newton zu berechnen, ist unglaublich schnell ... vorausgesetzt der Startwert ist vernünftig. Es gibt jedoch keinen vernünftigen Startwert, und in der Praxis enden wir mit dem Halbierungs- und Protokollierungsverhalten (2 ^ 64).
Um wirklich schnell zu sein, brauchen wir einen schnellen Weg, um einen vernünftigen Startwert zu erreichen, und das bedeutet, dass wir in Maschinensprache absteigen müssen. Wenn ein Prozessor eine Anweisung wie POPCNT im Pentium bereitstellt, die die führenden Nullen zählt, können wir diese verwenden, um einen Startwert mit der Hälfte der signifikanten Bits zu haben. Mit Sorgfalt können wir eine feste Anzahl von Newton-Schritten finden, die immer ausreichen werden. (Damit entfällt das Erfordernis einer Schleife und eine sehr schnelle Ausführung.)
Eine zweite Lösung besteht in der Fließkomma-Funktion, die möglicherweise eine schnelle Berechnung (wie der i87-Coprozessor) erfordert. Selbst eine Abweichung über exp () und log () kann schneller sein, als Newton zu einer binären Suche. Hier gibt es einen kniffligen Aspekt: Eine prozessorabhängige Analyse, was und ob nachträglich verfeinert werden muss.
Eine dritte Lösung löst ein etwas anderes Problem, ist jedoch erwähnenswert, da die Situation in der Frage beschrieben wird. Wenn Sie eine große Anzahl von Quadratwurzeln für Zahlen berechnen möchten, die sich leicht unterscheiden, können Sie die Newton-Iteration verwenden, wenn Sie den Startwert nie neu initialisieren, sondern einfach dort belassen, wo die vorherige Berechnung aufgehört hat. Ich habe dies mit Erfolg bei mindestens einem Euler-Problem verwendet.