web-dev-qa-db-de.com

Der schnellste Weg, um festzustellen, ob die Quadratwurzel einer Ganzzahl eine Ganzzahl ist

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): 

  1. Ich habe es auf die einfache Art und Weise gemacht, indem ich die eingebaute Funktion 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.
  2. Das Aufrechterhalten einer Nachschlagetabelle ist unpraktisch (da es ungefähr Gibt. 231,5 ganze Zahlen, deren Quadrat weniger als 2 ist63).

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:

  • Nach ausführlichen Tests habe ich festgestellt, dass das Hinzufügen von 0.5 zum Ergebnis von Math.sqrt () nicht erforderlich ist, zumindest nicht auf meinem Computer.
  • Die fast-inverse Quadratwurzel war zwar schneller, ergab aber für n> = 410881 falsche Ergebnisse. Wie von BobbyShaftoe vorgeschlagen, können wir jedoch den FISR-Hack für n <410881 verwenden.
  • Newtons Methode war ein bisschen langsamer als 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.
  • Eine modifizierte Newton-Methode, bei der nur ein paar Tricks verwendet wurden, sodass nur die Ganzzahl-Mathematik erforderlich war, erforderte einige Hacks, um einen Überlauf zu vermeiden (ich möchte, dass diese Funktion mit allen positiven 64-Bit-Ganzzahlen mit Vorzeichen funktioniert), und sie war noch langsamer als Math.sqrt().
  • Binäres Hacken war noch langsamer. Dies ist sinnvoll, da der binäre Schnitt im Durchschnitt 16 Durchgänge benötigt, um die Quadratwurzel einer 64-Bit-Zahl zu finden.
  • Laut Johns Tests ist die Verwendung von or-Anweisungen in C++ schneller als mit einer switch. In Java und C # scheint es jedoch keinen Unterschied zwischen or und switch zu geben.
  • Ich habe auch versucht, eine Nachschlagetabelle zu erstellen (als privates statisches Array mit 64 booleschen Werten). Dann würde ich anstelle von switch oder 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. 
1318
Kip

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:

  1. Filtern Sie zunächst offensichtliche Antworten. Dies beinhaltet negative Zahlen und das Betrachten der letzten 4 Bits. (Ich habe festgestellt, dass die letzten sechs nicht hilfreich waren.) Ich antworte auch mit "0". (Beim Lesen des folgenden Codes ist zu beachten, dass meine Eingabe int64 x ist.)
    if( x < 0 || (x&2) || ((x & 7) == 5) || ((x & 11) == 8) )
        return false;
    if( x == 0 )
        return true;
  2. Prüfen Sie als Nächstes, ob es sich um ein quadratisches Modulo 255 = 3 * 5 * 17 handelt. Da dies ein Produkt aus drei verschiedenen Primzahlen ist, sind nur etwa 1/8 der Residuen mod 255 Quadrate. Nach meiner Erfahrung kostet das Aufrufen des Modulo-Operators (%) jedoch mehr als der Nutzen, den man erhält, daher verwende ich Bit-Tricks mit 255 = 2 ^ 8-1, um den Rest zu berechnen. (Zum Guten oder Schlechten benutze ich nicht den Trick, einzelne Bytes aus einem Word zu lesen, sondern nur bitweise -und und verschiebt.)
    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
    
  3. Versuchen Sie schließlich, die Quadratwurzel mit einer ähnlichen Methode wie Hensels Lemma zu berechnen. (Ich denke nicht, dass es direkt anwendbar ist, aber es funktioniert mit einigen Modifikationen.) Bevor ich dies tue, teile ich alle Potenzen von 2 mit einer binären Suche auf:
    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;
}
649
A. Rex

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.

317
maaartinus

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.

125
John D. Cook

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.

  • die * (long*) &y ist im Grunde eine Funktion zum schnellen Konvertieren in lange, so dass Ganzzahloperationen auf die rohen Bytes angewendet werden können.
  • die 0x5f3759df - (i >> 1);-Zeile ist ein vorberechneter Startwert für die Näherungsfunktion.
  • die * (float*) &i konvertiert den Wert zurück in Fließkommazahl.
  • die 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.

48
chakrit

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.

36
Kibbee

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ück
  • n^2 + 2n + 1 > target > n^2: Sie sind nah dran, aber es ist nicht perfekt: geben Sie false zurück
  • n^2 - 2n + 1 < target < n^2: dito
  • target < 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.

32
Jon Skeet

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:

  • Internet - Kip's gepostete Antwort
  • Durron - Meine modifizierte Antwort mit der One-Pass-Antwort als Basis
  • DurronTwo - Meine modifizierte Antwort mit der Antwort mit zwei Durchläufen (von @JohnnyHeggheim), mit einigen anderen geringfügigen Modifikationen.

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
22
durron597

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.

16
Bill the Lizard

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.

14
mrzl

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.

12
Cyrille Ka

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.

11
Colonel Panic

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 wirdddh 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.

10
Hugh Allen

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.

9
BobbyShaftoe

Dies ist die schnellste Java-Implementierung, die ich mit einer Kombination von Techniken entwickeln könnte, die von anderen in diesem Thread vorgeschlagen wurden.

  • Mod-256-Test
  • Inexact mod-3465 test (vermeidet ganzzahlige Division auf Kosten einiger Fehlalarme)
  • Fließkomma-Quadratwurzel, rund und mit dem Eingabewert vergleichen

Ich habe auch mit diesen Modifikationen experimentiert, aber sie haben die Leistung nicht verbessert:

  • Zusätzlicher Mod-255-Test
  • Teilen des Eingangswerts durch Potenzen von 4
  • Fast Inverse Square Root (für hohe N-Werte sind 3 Iterationen erforderlich, ausreichend, um langsamer als die Hardware-Square-Root-Funktion zu sein.)

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;
        }
    }

}
8
finnw

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.

8
dfeuer

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.

7
David Lehavi

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);
}
6
Jonny Heggheim

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;
};
6

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.

6
bgiles

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.

6
hydrodog

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

  • a hat die Form (3 << 2k) - 1
  • b hat die Form (2 << 2k)
  • c hat die Form (2 << 2k + 2) - 1
  • d hat die Form (2 << 2k - 1) * 10

    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.

  • 5
    Fractaly

    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;  
    }  
    
    5
    nabam serbang

    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);
    
    1
    Hemil

    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.

    1
    Elijah

    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.

    1
    Steve Storck

    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.)

    1
    paulmurray

    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. 

    0
    Viktor

    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:

    • verschiebe den Scheck gegen 0 nach dem Scheck mit mod255
    • ordnen Sie die Teilungsstärke von vier neu an, um alle Überprüfungen für den üblichen Fall (75%) zu überspringen. 

    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.

    0
    Ben

    Newton-Methode mit Ganzzahlarithmetik

    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.

    0
    aventurin

    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 - 4
    • number hat 2 Ziffern: solution im Bereich = 3 - 10
    • number hat 3 Ziffern: solution im Bereich = 10 - 40
    • number hat 4 Ziffern: solution im Bereich = 30 - 100
    • number hat 5 Ziffern: solution im Bereich = 100 - 400

    Beachten 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"
    
    0
    MWB

    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. 

    0

    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);
        }
    }
    
    0
    MWB

    "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.

    0
    dstibbe

    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.