質問

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

  1. 組み込みのMath.sqrt()を使用して、簡単な方法で実行しました 関数、しかし私はそれをより速くする方法があるかどうか疑問に思っています 整数のみのドメインに制限します。
  2. ルックアップテーブルを維持することは実用的ではありません( 2 31.5 の2乗が2 63 未満の整数)。

私が今やっていることは非常にシンプルで簡単な方法です:

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

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

注:多くの Project Euler の問題でこの関数を使用しています。したがって、他の誰もこのコードを保守する必要はありません。そして、この種のマイクロ最適化は実際に違いを生む可能性があります。チャレンジの一部はすべてのアルゴリズムを1分未満で実行することであり、この関数はいくつかの問題で何百万回も呼び出される必要があるためです。


問題のさまざまな解決策を試しました:

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

解決

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

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

  1. まず、明白な答えを除外します。これには、負の数と最後の4ビットの確認が含まれます。 (最後の6つを見ても役に立たなかったことがわかりました。)また、0についてはいと答えます。
    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. 最後に、 Henselの補題と同様の方法を使用して、平方根を計算します。 (直接適用できるとは思いませんが、いくつかの修正を加えて動作します。)その前に、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.
    アイデアは、各反復で、rに1ビットを追加するというものです。 xの平方根。各平方根は、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) );
    ここでの高速化は、3つの方法で得られます。事前計算された開始値(ループの最大10回の繰り返しに相当)、ループの早期終了、およびt値のスキップ。最後の部分については、 z = r-x * x を見て、tを2のzを除算する最大の累乗になるようにビットトリックを設定します。これにより、とにかく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;
}

他のヒント

私はパーティーにかなり遅れていますが、より良い答えを提供したいと思っています。さらに短く(ベンチマークが正しいと仮定した場合も)高速

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

最初のテストでは、ほとんどの非正方形をすばやくキャッチします。長いパックされた64アイテムのテーブルを使用するため、配列へのアクセスコストは発生しません(インダイレクションと境界チェック)。一様にランダムな long の場合、ここで終了する確率は81.25%です。

2番目のテストでは、因数分解で2の奇数を持つすべての数値をキャッチします。メソッド Long.numberOfTrailingZeros は、JITで単一のi86命令に変換されるため、非常に高速です。

最後のゼロを削除した後、3番目のテストは、バイナリで011、101、または111で終わる数値を処理します。これらは完全な二乗ではありません。また、負の数を考慮し、0も処理します。

最終テストは、 double 算術にフォールバックします。 double には仮数部が53ビットしかないため、 long から double への変換には、大きな値の丸めが含まれます。それにもかかわらず、テストは正しいです(証明が間違っています)。

mod255アイデアの組み込みを試みても成功しませんでした。

ベンチマークを実行する必要があります。最適なアルゴリズムは、入力の分布に依存します。

あなたのアルゴリズムはほぼ最適かもしれませんが、平方根ルーチンを呼び出す前にいくつかの可能性を除外するために簡単なチェックをしたいかもしれません。たとえば、ビット単位の「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#コードにはほとんど影響がありません。

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

それから、Quake Sourceコードから「ネット」を周回するこの関数があったことを覚えています:

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 *)&amp; y は基本的に高速のlong-to-long関数であるため、整数演算を生のバイトに適用できます。
  • 0x5f3759df-(i&gt;&gt; 1); 行は、近似関数の事前に計算されたシード値です。
  • *(float *)&amp; i は値を浮動小数点に変換します。
  • y = y *(threehalfs-(x2 * y * y))行は、関数全体で値を基本的に繰り返します。

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

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

それがより高速か正確かは分かりませんが、 John CarmackのMagical Square Root 、平方根をより速く解決するアルゴリズム。おそらくすべての可能な32ビット整数についてこれを簡単にテストし、それが近似にすぎないため、実際に正しい結果を得たことを検証できます。ただし、今考えてみると、doubleを使用することも近似値であるため、それがどのように作用するかわかりません。

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

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

n ^ 2 を計算したため、オプションは次のとおりです。

  • n ^ 2 = target :完了、trueを返します
  • n ^ 2 + 2n + 1&gt;ターゲット&gt; n ^ 2 :近いですが、完全ではありません:falseを返します
  • n ^ 2-2n + 1&lt;ターゲット&lt; n ^ 2 :同上
  • ターゲット&lt; n ^ 2-2n + 1 :下位の n
  • でのバイナリチョップ
  • ターゲット&gt; n ^ 2 + 2n + 1 :上位の n
  • でのバイナリチョップ

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

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

編集:バイナリチョップは整数の全範囲を取り込む必要はありません。(2 ^ x)^ 2 = 2 ^(2x)なので、ターゲットの最上位ビット(ビット調整のトリックを使用して実行できます。正確な方法は忘れます)潜在的な回答の範囲をすばやく取得できます。気を付けてください、素朴なバイナリチョップはまだ最大31または32回の反復しかかかりません。

このスレッドのいくつかのアルゴリズムについて独自の分析を実行し、いくつかの新しい結果を思い付きました。これらの古い結果は、この回答の編集履歴で確認できますが、間違えたため正確ではなく、近いものではないいくつかのアルゴリズムの分析に時間を浪費します。しかし、いくつかの異なる答えから教訓を引き出して、「勝者」を押しつぶす2つのアルゴリズムを手に入れました。このスレッドの。これが私が他の皆とは違うやり方で行う中核的な事柄です:

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

ただし、ほとんどの場合1つまたは2つの非常に高速な命令を追加するこの単純な行により、 switch-case ステートメントが1つのifステートメントに大幅に簡素化されます。ただし、テストされた数値の多くに2のべき乗の要因がある場合、ランタイムに追加される可能性があります。

以下のアルゴリズムは次のとおりです。

  • インターネット-Kipの投稿された回答
  • Durron -ワンパス回答をベースとして使用して修正した回答
  • DurronTwo -2パスの回答(@JohnnyHeggheimによる)を使用して修正した回答、およびその他の若干の修正。

Math.abs(java.util.Random.nextLong())

を使用して数値を生成する場合のサンプルランタイムです。
 0% Scenario{vm=java, trial=0, benchmark=Internet} 39673.40 ns; ?=378.78 ns @ 3 trials
33% Scenario{vm=java, trial=0, benchmark=Durron} 37785.75 ns; ?=478.86 ns @ 10 trials
67% Scenario{vm=java, trial=0, benchmark=DurronTwo} 35978.10 ns; ?=734.10 ns @ 10 trials

benchmark   us linear runtime
 Internet 39.7 ==============================
   Durron 37.8 ============================
DurronTwo 36.0 ===========================

vm: java
trial: 0

また、最初の100万長でのみ実行される場合のサンプルランタイムは次のとおりです。

 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

ご覧のとおり、 DurronTwo は、大規模な入力では非常に頻繁にマジックトリックを使用するようになりますが、最初のアルゴリズムと Math.sqrt 数値が非常に小さいため。一方、より単純な Durron は、最初の100万の数で何度も4で割る必要がないため、大きな勝者です。

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

そして 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;
}

そして私のベンチマークハーネス:(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);
    }
}

更新:いくつかのシナリオではより速く、他のシナリオではより遅く、新しい入力に基づいて異なるベンチマークを取得する新しいアルゴリズムを作成しました。モジュロ 0xFFFFFF = 3 x 3 x 5 x 7 x 13 x 17 x 241 を計算すると、平方できない数値の97.82%を排除できます。これは、(ビットごとに)5つのビット演算で1行で実行できます:

if (!goodLookupSquares[(int) ((n & 0xFFFFFFl) + ((n >> 24) & 0xFFFFFFl) + (n >> 48))]) return false;

結果のインデックスは、1)残基、2)残基 + 0xFFFFFF 、または3)残基 + 0x1FFFFFE のいずれかです。もちろん、 0xFFFFFF を法とする剰余のルックアップテーブルが必要です。これは約3MBのファイルです(この場合、ASCIIテキストの10進数として保存されます。最適ではありませんが、 ByteBuffer など。ただし、これは事前計算であるため、それほど重要ではありません。あなたここでファイルを見つけることができます(または自分で生成します):

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

次のように boolean 配列に読み込みます:

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

ランタイムの例。実行したすべてのトライアルで Durron (バージョン1)を破りました。

 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

Newtonのメソッドを使用して整数の平方根、現在のソリューションで行うように、この数値を二乗して確認します。ニュートンの方法は、他のいくつかの回答で言及されたカーマックソリューションの基礎です。ルートの整数部分のみに関心があり、近似アルゴリズムをより早く停止できるため、より高速な回答が得られるはずです。

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

  

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

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

レコードについては、別のアプローチは素数分解を使用することです。分解のすべての要素が偶数である場合、数は完全な正方形です。したがって、必要なのは、数を素数の平方の積として分解できるかどうかを確認することです。もちろん、存在するかどうかを確認するためだけに、このような分解を取得する必要はありません。

まず、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を使用したソリューションよりも良い結果が得られます。

整数の問題は整数の解決に値します。したがって

(負ではない)整数のバイナリ検索を実行して、 t ** 2&lt; = n のような最大の整数tを見つけます。次に、 r ** 2 = n を正確にテストします。これには時間O(log n)がかかります。

セットが制限されていないために正の整数をバイナリ検索する方法がわからない場合は、簡単です。まず、2の累乗で増加する関数f( f(t)= t ** 2-n を超える)を計算します。ポジティブになったら、上限があります。その後、標準のバイナリ検索を実行できます。

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

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

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

パフォーマンスのために、多くの場合、妥協が必要です。他の人はさまざまな方法を表明しましたが、カーマックのハックは特定のNの値まで高速であることに気付きました。その後、「n」をチェックする必要があります。そして、それがその数Nより小さい場合は、カーマックのハックを使用します。それ以外の場合は、こちらの回答に記載されている他の方法を使用します。

これは、このスレッドの他の人によって提案された技術の組み合わせを使用して、私が思い付くことができる最速の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;
        }
    }

}

maaartinusのソリューションを次のように簡略化すると、ランタイムが数パーセント削減されるように見えますが、信頼できるベンチマークを作成するためのベンチマークは十分ではありません。

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

最初のテストをどのように省略したかを確認する価値があります

if (goodMask << x >= 0) return false;

パフォーマンスに影響します。

最初から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%で浮動小数点部分を実行します。

これは、Rubyの古いマルシャント計算アルゴリズムの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;
};

前述のように、sqrt呼び出しは完全に正確ではありませんが、速度の面で他の答えを吹き飛ばさないことは興味深いです。結局のところ、sqrtのアセンブリ言語命令のシーケンスはごくわずかです。 Intelにはハードウェア命令がありますが、これはIEEEに準拠していないためJavaでは使用されていません。

では、なぜ遅いのですか? Javaは実際にはJNIを介してCルーチンを呼び出しており、実際にインラインで実行するよりも遅いJavaサブルーチンを呼び出すよりも実際に遅いためです。これは非常に迷惑であり、Javaはより良い解決策、つまり必要に応じて浮動小数点ライブラリ呼び出しを組み込むことを考え出すべきでした。まあ。

C ++では、複雑な代替手段はすべて速度が低下すると思われますが、すべてを確認したわけではありません。 私がやったこと、そしてJavaの人々が役に立つと思うのは、単純なハックであり、A。Rexによって提案された特別なケーステストの拡張です。境界チェックされていない単一のlong値をビット配列として使用します。そうすれば、64ビットのブールルックアップができます。

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

isPerfectSquare5ルーチンは、core2デュオマシンで約1/3の時間で実行されます。同じ線に沿ってさらに微調整することで、平均で時間をさらに短縮できると思いますが、チェックするたびに、より多くのテストと引き換えに排除するので、その道をあまり遠くに行くことはできません。

確かに、陰性の個別のテストを行うのではなく、上位6ビットを同じ方法でチェックできます。

私がしていることは、可能性のある正方形を削除することだけですが、可能性がある場合は、元のインラインisPerfectSquareを呼び出す必要があります。

init2ルーチンは、pp1およびpp2の静的な値を初期化するために1回呼び出されます。 C ++での実装では、unsigned long longを使用しているため、署名しているので&gt;&gt;&gt;を使用する必要があることに注意してください。演算子。

配列を境界チェックする本質的な必要性はありませんが、Javaのオプティマイザーはこのようなことを非常に迅速に把握する必要があるため、それらを非難しません。

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

次のものを置き換えるだけです:

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

Project Eulerはタグで言及されており、その中の多くの問題は数字の確認が必要です&gt;&gt; 2 ^ 64 。上記の最適化のほとんどは、80バイトのバッファーを使用している場合は簡単に機能しません。

java BigIntegerと、Newtonのメソッドをわずかに変更したバージョンを使用しました。これは整数でより適切に機能するものです。問題は、 n ^ 2-1 = n ^ 2 (n-1)(n + 1)であり、最終エラーは最終除数の1ステップ下であり、アルゴリズムは終了しました。エラーを計算する前に元の引数に1を追加することで、簡単に修正できました。 (キューブルートなどに2つ追加)

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

正方形の最後のnビットが観測されたときに、考えられるすべての結果をチェックしました。より多くのビットを連続して調べることにより、最大5/6の入力を削除できます。 Fermatの因子分解アルゴリズムを実装するために実際にこれを設計しましたが、非常に高速です。

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&lt;&lt; 2k)-1     
  • bの形式は(2&lt;&lt; 2k)     
  • cの形式は(2&lt;&lt; 2k + 2)-1     
  • dは(2&lt;&lt; 2k-1)* 10

    の形式です

    まず、2のべき乗のモジュラスを持つ二乗残差があるかどうかをテストし、次に最終モジュラスに基づいてテストし、Math.sqrtを使用して最終テストを行います。私は一番上の投稿からアイデアを思いつき、それを拡張しようとしました。コメントや提案に感謝します。

    更新:モジュラス(modSq)およびモジュラスベース44352によるテストを使用して、テストは最大1,000,000,000のOPの更新の時間の96%で実行されます。

        
  • 一般的なビット長を考慮して(ここでは特定のタイプを使用していますが)、以下のように単純なアルゴリズムを設計しようとしました。最初に、0、1、2または&lt; 0の簡単で明白なチェックが必要です。 以下は、既存の数学関数を使用しようとしないという意味で単純です。ほとんどの演算子は、ビット単位の演算子に置き換えることができます。ただし、ベンチマークデータではテストしていません。私は特に数学やコンピューターアルゴリズムの設計の専門家ではありません。問題を指摘するのを楽しみにしています。私はそこに多くの改善のチャンスがあることを知っています。

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

    これが以前に言及されたかどうかはわかりません。しかし、こちら

    int result = (int)(floor(sqrt(b)) - ceil(sqrt(a)) + 1);
    

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

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

    ...

    わかりました。少し理論を超えた数論に遭遇したか、コードにバグがあります。いずれにせよ、ここにコードがあります:

    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のパフォーマンスが低下したため、省略しました。変更履歴を表示して確認してください。)

    これは最も単純で最も簡潔な方法ですが、CPUサイクルの観点から比較する方法はわかりません。これは、ルートが整数かどうかだけを知りたい場合に便利です。整数かどうかを本当に気にするなら、それも理解できます。シンプルな(そして純粋な)関数を次に示します。

    public static boolean isRootWhole(double number) {
        return Math.sqrt(number) % 1 == 0;
    }
    

    マイクロ最適化が必要ない場合、この答えはシンプルさと保守性の点で優れています。負の数値を取得する場合は、おそらく数値引数でMath.sqrt()引数としてMath.abs()を使用する必要があります。

    私の3.6Ghz Intel i7-4790 CPUでは、このアルゴリズムを0〜10,000,000で実行すると、計算ごとに平均35〜37ナノ秒かかりました。私は10回の連続実行を行い、1,000万のsqrt計算のそれぞれに費やされた平均時間を出力しました。合計実行はそれぞれ、600ミリ秒強で完了しました。

    実行する計算の数が少ない場合、以前の計算には少し時間がかかります。

    ここに分割統治ソリューションがあります。

    自然数の平方根( number )が自然数( solution )の場合、 solution number の桁数に基づいて:

    • number の数字は1桁です: solution in range = 1-4
    • number は2桁です: solution の範囲= 3〜10
    • number は3桁です:範囲内の solution = 10-40
    • number は4桁です: solution の範囲= 30〜100
    • number は5桁です: solution 範囲= 100〜400

    繰り返しに注意してください

    この範囲をバイナリ検索アプローチで使用して、次の solution があるかどうかを確認できます。

    number == solution * solution
    

    ここにコードがあります

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

    そして、これを使用する方法の例を示します。

    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"
    

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

    Carmacメソッドに関しては、もう一度繰り返すだけでかなり簡単になり、精度の桁数が2倍になるはずです。結局のところ、それは非常に優れた最初の推測を持つ、非常に短縮された反復法-ニュートン法です。

    現在のベストに関して、2つのマイクロ最適化が表示されます:

    • mod255を使用したチェック後のチェックと0の移動
    • 通常の(75%)ケースのすべてのチェックをスキップするように、4の累乗を再配置します。

    つまり:

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

    さらに良いのは単純かもしれません

    while ((n & 0x03L) == 0) n >>= 2;
    

    明らかに、各チェックポイントでいくつの数字がカリングされるかを知ることは興味深いでしょう-私はむしろチェックが本当に独立していることを疑い、物事を難しくしています。

    &quot;「長い値が完全な正方形である(つまり、その平方根が別の整数である)かどうかを判断する最速の方法を探しています。」

    答えは印象的ですが、簡単な確認ができませんでした:

    長い右側の最初の数字が集合(0,1,4,5,6,9)のメンバーかどうかを確認します。そうでない場合、「完全な正方形」になることはできません。

    eg。

    4567-完全な正方形にすることはできません。

    ライセンス: CC-BY-SA帰属
    所属していません StackOverflow
    scroll top