1559

long値が完全な平方であるかどうか(つまり、その平方根が別の整数であるかどうか)を判断するための最速の方法を探しています。

  1. 組み込み関数を使って簡単な方法でやったのMath.sqrt() ですが、整数のみのドメインに制限することでもっと速くできる方法があるのではないかと思います。
  2. ルックアップテーブルを維持することは実用的ではありません(2乗が2 63未満の整数が約231.5あるため)。

これが私が今やっている非常に単純で簡単な方法です:

public final static boolean isPerfectSquare(long n)
{
  if (n < 0)
    return false;

  long tst = (long)(Math.sqrt(n) + 0.5);
  return tst*tst == n;
}

注:私は多くのプロジェクトオイラー問題でこの関数を使用しています。したがって、他の誰もこのコードを維持する必要はありません。そして、この種のマイクロ最適化は実際に違いを生む可能性があります。課題の一部はすべてのアルゴリズムを1分未満で実行することであり、問​​題によってはこの関数を何百万回も呼び出す必要があるためです。


私はこの問題に対してさまざまな解決策を試しました。

  • 0.5徹底的なテストの結果、少なくとも私のマシンでは、Math.sqrt()の結果に追加する必要がないことがわかりました。
  • 高速逆平方根は高速でしたが、n> = 410881の場合は誤った結果になりました。ただし、BobbyShaftoeが示唆しているように n<410881の場合はFISRハックを使用できます。
  • ニュートン法は、よりも少し遅かっMath.sqrt()た。これはおそらく、Math.sqrt()ニュートン法に似たものを使用しているが、ハードウェアに実装されているため、Javaよりもはるかに高速であるためです。また、ニュートン法では、依然としてdoubleを使用する必要がありました。
  • 整数計算のみが含まれるようにいくつかのトリックを使用した修正ニュートン法は、オーバーフローを回避するためにいくつかのハックを必要とし(この関数をすべての正の64ビット符号付き整数で機能させたい)、それでも。よりも低速Math.sqrt()でした。
  • バイナリチョップはさらに遅くなりました。バイナリチョップは64ビット数の平方根を見つけるために平均16パスを必要とするため、これは理にかなっています。
  • Johnのテストによると、orステートメントの使用は、C ++では、を使用するよりも高速ですが、JavaとC#では、との間にswitch違いはないようです。orswitch
  • また、ルックアップテーブルを作成してみました(64ブール値のプライベート静的配列として)。次に、switchまたはorstatementの代わりに、と言いif(lookup[(int)(n&0x3F)]) { test } else return false;ます。驚いたことに、これは(ほんの少しだけ)遅くなりました。これは、Javaで配列の境界がチェックされるためです。
4

36 に答える 36

794

少なくとも私の CPU (x86) とプログラミング言語 (C/C++) では、6 ビット + Carmack + sqrt コードよりも ~35% 高速に動作する方法を見つけました。特に Java 要素がどのように展開するかはわからないため、結果は異なる場合があります。

私のアプローチは 3 つあります。

  1. まず、明白な回答を除外します。これには、負の数と最後の 4 ビットの参照が含まれます。(最後の 6 つを見ても役に立たないことがわかりました。) また、0 の場合は「はい」と答えます (以下のコードを読む際に、私の入力が であることに注意してくださいint64 x。)
    if( x < 0 || (x&2) || ((x & 7) == 5) || ((x & 11) == 8) )
        return false;
    if( x == 0 )
        return true;
  2. 次に、それが 255 を法とする平方 = 3 * 5 * 17 であるかどうかを確認します。これは 3 つの異なる素数の積であるため、255 を法とする剰余の約 1/8 のみが平方です。ただし、私の経験では、モジュロ演算子 (%) を呼び出すと得られるメリットよりもコストがかかるため、255 = 2^8-1 を含むビットトリックを使用して剰余を計算します。(良くも悪くも、単語から個々のバイトを読み取るトリックは使用しておらず、ビットごとの AND とシフトのみを使用しています。)
    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.
    
    剰余が正方形かどうかを実際に確認するために、事前に計算されたテーブルで答えを調べます。
    if( bad255[y] )
        return false;
    // However, I just use a table of size 512
    
  3. 最後に、ヘンセルの補題に似た方法を使用して平方根を計算してみます。(直接適用できるとは思いませんが、いくつかの変更を加えると機能します。)それを行う前に、二分探索で 2 のすべてのべき乗を除算します。
    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;
    この時点で、数が正方形であるためには、1 mod 8 でなければなりません。
    if((x & 7) != 1)
        return false;
    ヘンゼルの補題の基本構造は次のとおりです。(注: テストされていないコードです。動作しない場合は、t=2 または 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.
    アイデアは、反復ごとに、x の「現在の」平方根である r に 1 ビットを追加するというものです。各平方根は、2 の累乗、つまり t/2 をモジュロとして正確です。最後に、r と t/2-r は t/2 を法とする x の平方根になります。(r が x の平方根である場合、-r も同様であることに注意してください。これはモジュロ数でも当てはまりますが、モジュロ数によっては、2 つ以上の平方根を持つこともあることに注意してください。特に、これには 2 のべき乗が含まれます。 ) 実際の平方根は 2^32 より小さいため、その時点で、r または t/2-r が実際の平方根であるかどうかを実際に確認できます。私の実際のコードでは、次の変更されたループを使用します。
    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) );
    ここでの高速化は、事前計算された開始値 (ループの約 10 回の反復に相当)、ループの早期終了、いくつかの t 値のスキップの 3 つの方法で得られます。最後の部分では、 を見てz = r - x * x、ちょっとしたトリックで t を z を割る 2 の最大のべき乗に設定します。これにより、とにかく r の値に影響を与えなかった t 値をスキップできます。私の場合、事前計算された開始値は、8192 を法とする「最小の正の」平方根を選択します。

このコードが速く動作しない場合でも、そこに含まれるいくつかのアイデアを楽しんでいただければ幸いです。事前計算されたテーブルを含む、完全でテスト済みのコードが続きます。

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;
}
于 2009-01-08T16:32:42.587 に答える
136

ベンチマークを行う必要があります。最適なアルゴリズムは、入力の分布によって異なります。

アルゴリズムはほぼ最適化されているかもしれませんが、平方根ルーチンを呼び出す前に、いくつかの可能性を排除するために簡単なチェックを行うことができます。たとえば、ビット単位の「and」を実行して、数値の最後の桁を 16 進数で調べます。完全平方は底 16 の 0、1、4、または 9 でしか終わらないため、入力の 75% について (それらが均一に分散されていると仮定して)、非常に高速なビット操作と引き換えに平方根の呼び出しを回避できます。

Kip は、16 進数のトリックを実装する次のコードのベンチマークを行いました。1 から 100,000,000 までの数字をテストすると、このコードは元のコードの 2 倍の速さで実行されました。

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

類似のコードを C++ でテストしたところ、実際には元のコードよりも遅くなりました。ただし、switch ステートメントを削除すると、16 進数のトリックにより、コードが再び 2 倍速くなります。

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

switch ステートメントを削除しても、C# コードにはほとんど影響がありませんでした。

于 2008-11-17T14:27:17.310 に答える
56

数値解析のコースで過ごした恐ろしい時間を考えていました。

そして、私は覚えています、Quakeソースコードからネットの周りを回っているこの関数がありました:

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

これは基本的に、ニュートンの近似関数を使用して平方根を計算します(正確な名前を覚えておくことができません)。

使用可能で、さらに高速になる可能性があります。これは、驚異的なidソフトウェアのゲームの1つです。

これはC++で書かれていますが、アイデアが浮かんだら、Javaで同じ手法を再利用するのはそれほど難しいことではありません。

私はもともとそれを見つけました:http://www.codemaestro.com/reviews/9

ウィキペディアで説明されているニュートン法:http://en.wikipedia.org/wiki/Newton%27s_method

それがどのように機能するかについての詳細な説明についてはリンクをたどることができますが、あまり気にしないのであれば、これは私がブログを読んだり数値解析コースを受講したりして覚えていることです。

  • これ* (long*) &yは基本的に高速のlong変換関数であるため、整数演算をrawバイトに適用できます。
  • この0x5f3759df - (i >> 1);線は、近似関数の事前に計算されたシード値です。
  • * (float*) &i値を浮動小数点に変換し直します。
  • このy = y * ( threehalfs - ( x2 * y * y ) )行は、基本的に関数の値を再度繰り返します。

近似関数は、結果に対して関数を反復するほど、より正確な値を提供します。Quakeの場合、1回の反復で「十分」ですが、それが適切でない場合は、必要な数の反復を追加できます。

これは、単純な平方根で行われる除算演算の数を2による単純な除算(実際には* 0.5F乗算演算)に減らし、代わりにいくつかの固定数の乗算演算に置き換えるため、より高速になるはずです。

于 2008-11-17T13:50:19.817 に答える
39

それがより速いか、あるいは正確であるかどうかはわかりませんが、ジョン・カーマックのマジカル平方根アルゴリズムを使用して平方根をより速く解くことができます。おそらく、これをすべての可能な32ビット整数について簡単にテストし、実際に正しい結果が得られたことを検証できます。これは単なる概算であるためです。しかし、今考えてみると、ダブルスの使用も概算であり、それがどのように機能するかはわかりません。

于 2008-11-17T13:51:45.867 に答える
36

バイナリチョップを実行して「正しい」平方根を見つけようとすると、取得した値が次のように十分に近いかどうかをかなり簡単に検出できます。

(n+1)^2 = n^2 + 2n + 1
(n-1)^2 = n^2 - 2n + 1

したがって、計算するn^2と、オプションは次のようになります。

  • n^2 = target:完了、trueを返します
  • n^2 + 2n + 1 > target > n^2:あなたは近くにいますが、完璧ではありません:falseを返します
  • n^2 - 2n + 1 < target < n^2:同上
  • target < n^2 - 2n + 1:下部のバイナリチョップn
  • target > n^2 + 2n + 1:高次のバイナリチョップn

(申し訳ありませんが、これはn現在の推測として、およびtargetパラメーターとして使用されます。混乱をお詫びします!)

これが速くなるかどうかはわかりませんが、試してみる価値はあります。

編集:バイナリチョップも整数の全範囲を取り込む必要はない(2^x)^2 = 2^(2x)ので、ターゲットでトップセットビットを見つけたら(これはビットをいじるトリックで行うことができます;私は正確に方法を忘れています)さまざまな潜在的な答えをすばやく得ることができます。念のために言っておきますが、ナイーブなバイナリチョップはまだ31回または32回の反復しか必要としません。

于 2008-11-17T13:50:48.710 に答える
18

現在のソリューションで行っているように、ニュートン法を使用してInteger Square Rootを計算し、この数値を 2 乗してチェックする方がはるかに高速です。ニュートンの方法は、他のいくつかの回答で言及されているカーマックソリューションの基礎です。ルートの整数部分のみに関心があるため、より迅速に答えを得ることができるはずです。これにより、近似アルゴリズムをより早く停止できます。

試すことができる別の最適化: 数値のデジタル ルートが 1、4、7、または 9 で終わらない場合、その数値は完全な平方ではありません。これは、より遅い平方根アルゴリズムを適用する前に、入力の 60% を削除する簡単な方法として使用できます。

于 2008-11-17T14:58:24.453 に答える
15

この関数をすべての正の 64 ビット符号付き整数で動作させたい

Math.sqrt()double を入力パラメーターとして使用するため、 2^53より大きい整数に対しては正確な結果が得られません。

于 2008-11-18T15:57:44.473 に答える
13

記録のために、もう 1 つの方法は、素数分解を使用することです。分解のすべての要素が偶数である場合、その数は完全な二乗です。したがって、数値が素数の 2 乗の積として分解できるかどうかを確認する必要があります。もちろん、そのような分解を取得する必要はありません。存在するかどうかを確認するだけです。

最初に、2^32 より小さい素数の平方の表を作成します。これは、この制限までのすべての整数のテーブルよりもはるかに小さいです。

解決策は次のようになります。

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

私はそれが少し不可解だと思います。それが行うことは、素数の二乗が入力数を割ることをすべてのステップでチェックすることです。そうである場合、素数分解からこの平方を除去するために、可能な限りその数を平方で割ります。このプロセスで 1 になった場合、入力数値は素数の 2 乗分解です。正方形が数値自体よりも大きくなると、この正方形またはそれよりも大きな正方形でそれを割ることができなくなるため、その数値は素数の平方の分解ではありません。

最近のハードウェアで行われる sqrt と、ここで素数を計算する必要があることを考えると、このソリューションはかなり遅いと思います。しかし、彼の答えでmrzlが述べているように、2 ^ 54以上では機能しないsqrtを使用したソリューションよりも良い結果が得られるはずです。

于 2008-12-02T10:00:22.837 に答える
13

整数問題には整数解が必要です。したがって

(非負の) 整数に対して二分探索を行い、 となるような最大の整数 t を見つけますt**2 <= n。次に、正確かどうかをテストr**2 = nします。これには O(log n) の時間がかかります。

セットが無制限であるため、正の整数をバイナリ検索する方法がわからない場合は、簡単です。f(t) = t**2 - n2 の累乗で増加関数 f (上記) を計算することから始めます。正の値になると、上限が見つかりました。次に、標準のバイナリ検索を実行できます。

于 2013-10-15T15:48:34.123 に答える
11

d完全な正方形の最後の桁は特定の値しか取り得ないことが指摘されています。数値の最後のd桁 (基数b) は、を で割っnた余りと同じです。C表記で。nbdn % pow(b, d)

これは、任意の法に一般化できますmn % m数の一部を完全平方から除外するために使用できます。現在使用しているモジュラスは 64 で、12 が可能です。可能な正方形として、残りの 19%。少しコーディングすると、2016年のみを許可するモジュラス110880が見つかりました。可能な正方形としての残りの 1.8%。したがって、モジュラス演算 (つまり、除算) とテーブル ルックアップとマシンの平方根のコストによっては、このモジュラスを使用した方が高速になる場合があります。

ところで、Java にルックアップ テーブル用にパックされたビット配列を格納する方法がある場合は、それを使用しないでください。110880 最近の 32 ビット ワードはあまり RAM ではなく、マシン ワードのフェッチは 1 ビットのフェッチよりも高速になります。

于 2008-11-29T03:52:36.310 に答える
9

パフォーマンスのために、いくつかの妥協をしなければならないことがよくあります。他の人はさまざまな方法を表現していますが、Carmack のハックは N の特定の値までは高速であると指摘しました。次に、「n」を確認し、それがその数 N 未満の場合は Carmack のハックを使用し、そうでない場合は説明されている他の方法を使用します。ここの答えで。

于 2008-12-05T05:36:38.903 に答える
8

これは、このスレッドで他の人が提案した手法の組み合わせを使用して、私が思いつくことができる最速のJava実装です。

  • Mod-256テスト
  • 不正確なmod-3465テスト(誤検知を犠牲にして整数除算を回避します)
  • 浮動小数点平方根、丸めて入力値と比較

私もこれらの変更を試しましたが、パフォーマンスには役立ちませんでした。

  • 追加のmod-255テスト
  • 入力値を4の累乗で割る
  • 高速逆平方根(Nの値が高い場合は、3回の反復が必要であり、ハードウェアの平方根関数よりも遅くなります。)

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

}
于 2010-05-06T13:29:10.273 に答える
7

最初からNの2乗部分を取り除く必要があります。

2回目の編集 以下のmの魔法の表現は次のようになります

m = N - (N & (N-1));

書かれた通りではない

2回目の編集の終わり

m = N & (N-1); // the lawest bit of N
N /= m;
byte = N & 0x0F;
if ((m % 2) || (byte !=1 && byte !=9))
  return false;

最初の編集:

マイナーな改善:

m = N & (N-1); // the lawest bit of N
N /= m;
if ((m % 2) || (N & 0x07 != 1))
  return false;

最初の編集の終わり

いつものように続けます。このように、浮動小数点部分に到達するまでに、2乗部分が奇数(約半分)であるすべての数値をすでに取り除き、残りの1/8のみを考慮します。つまり、浮動小数点部分を数値の6%で実行します。

于 2009-01-01T22:12:38.813 に答える
7

プロジェクトオイラーはタグで言及されており、その中の問題の多くは数字をチェックする必要があります >> 2^64. 上記の最適化のほとんどは、80 バイトのバッファーを使用している場合、簡単には機能しません。

私は Java BigInteger と、ニュートンの方法をわずかに修正したバージョン (整数でより適切に機能するもの) を使用しました。問題は、正確な二乗がなぜならではなくにn^2収束し、最終的なエラーが最終的な除数の 1 ステップ下になり、アルゴリズムが終了したことでした。エラーを計算する前に、元の引数に 1 を追加することで簡単に修正できました。(立方根などは2つ足す)(n-1)nn^2-1 = (n-1)(n+1)

このアルゴリズムの優れた属性の 1 つは、数値が完全な 2 乗であるかどうかをすぐに判断できることです。ニュートン法での最終的な誤差 (補正ではない) はゼロになります。floor(sqrt(x))単純な変更により、最も近い整数の代わりにすばやく計算することもできます。これは、いくつかのオイラー問題で便利です。

于 2009-03-25T15:23:05.457 に答える
6

これは、Ruby の古い Marchant 電卓アルゴリズムの 10 進数から 2 進数へのリワークです (申し訳ありませんが、リファレンスはありません)。この質問に合わせて特別に調整されています。

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

これは似たようなものです(コーディングスタイル/匂いや不格好なO / Oについて投票しないでください-重要なのはアルゴリズムであり、C ++は私の母国語ではありません)。この場合、留数 == 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;
};
于 2009-01-01T22:27:43.587 に答える
6

一部の入力に対してほぼ正しい方法を使用するというアイデアが気に入っています。これは、より高い「オフセット」を持つバージョンです。コードは機能しているようで、簡単なテスト ケースに合格しています。

あなたを置き換えるだけです:

if(n < 410881L){...}

これでコード:

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);
}
于 2009-05-25T01:22:28.583 に答える
6

Considering for general bit length (though I have used specific type here), I tried to design simplistic algo as below. Simple and obvious check for 0,1,2 or <0 is required initially. Following is simple in sense that it doesn't try to use any existing maths functions. Most of the operator can be replaced with bit-wise operators. I haven't tested with any bench mark data though. I'm neither expert at maths or computer algorithm design in particular, I would love to see you pointing out problem. I know there is lots of improvement chances there.

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;  
}  
于 2010-12-04T01:22:34.910 に答える
5

正方形の最後の n ビットが観測されたときに考えられるすべての結果を確認しました。より多くのビットを連続的に検査することにより、最大 5/6 の入力を除去できます。私は実際にこれをフェルマーの因数分解アルゴリズムを実装するように設計しましたが、非常に高速です。

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

疑似コードの最後のビットを使用してテストを拡張し、より多くの値を削除できます。上記のテストは、k = 0、1、2、3 に対するものです。

  • a の形式は (3 << 2k) - 1
  • b の形式は (2 << 2k)
  • c の形式は (2 << 2k + 2) - 1
  • d の形式は (2 << 2k - 1) * 10

    まず、モジュラスが 2 の累乗の平方残差があるかどうかをテストし、次に最終モジュラスに基づいてテストし、次に Math.sqrt を使用して最終テストを行います。私はトップの投稿からアイデアを思いつき、それを拡張しようとしました. コメントや提案をお待ちしております。

    更新:モジュラス (modSq) と 44352 のモジュラス ベースによるテストを使用すると、1,000,000,000 までの数値について、OP の更新の 96% の時間でテストが実行されます。

  • 于 2011-12-04T04:30:52.463 に答える
    1

    速度が懸念される場合は、最も一般的に使用される入力のセットとその値をルックアップテーブルに分割してから、例外的なケースのために思いついた最適化された魔法のアルゴリズムを実行してみませんか?

    于 2008-11-17T23:29:58.337 に答える
    1

    「長い値が完全な 2 乗かどうか (つまり、平方根が別の整数かどうか) を判断する最速の方法を探しています。」

    答えは印象的ですが、簡単なチェックが見られませんでした:

    long の右側の最初の数値がセット (0,1,4,5,6,9) のメンバーであるかどうかを確認します。そうでない場合、「完全な正方形」である可能性はありません。

    例えば。

    4567 - 完全な正方形にはなりません。

    于 2009-09-24T09:46:57.980 に答える
    1

    それよりもはるかに効率的に「最後の X 桁が N の場合、完全な正方形にはなり得ない」をパックできるはずです! Java 32 ビット整数を使用し、数値の最後の 16 ビット (2048 の 16 進整数値) をチェックするのに十分なデータを生成します。

    ...

    Ok。私を少し超えた数論に出くわしたか、コードにバグがあります。いずれにせよ、コードは次のとおりです。

    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("};");
    }
    

    結果は次のとおりです。

    (ed: prettify.js のパフォーマンスが低いため省略されています。変更履歴を参照してください。)

    于 2009-02-22T06:22:01.027 に答える
    0

    整数のサイズが有限であることを考えると、速度が必要な場合は、(a)パラメーターをサイズごとに(たとえば、最大ビットセットごとにカテゴリに)分割し、完全な正方形の配列に対して値をチェックするのが最も簡単な方法だと思います。その範囲内。

    于 2008-11-17T13:48:29.870 に答える
    -3

    最速についてはわかりませんが、最も簡単なのは、通常の方法で平方根を取り、その結果を掛け合わせて、元の値と一致するかどうかを確認することです。

    ここでは整数について話しているので、断食にはおそらくルックアップを行うことができるコレクションが含まれます。

    于 2008-11-17T13:57:20.647 に答える