整数の平方根が整数かどうかを判断する最も速い方法


1454

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

  1. 私は組み込みMath.sqrt() 関数を使用して簡単な方法で実行しましたが、整数のみのドメインに制限することでより速く実行する方法があるかどうか疑問に思っています。
  2. ルックアップテーブルを維持することは非現実的です(二乗が2 63未満の整数が約2 31.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パスを必要とするため、これは理にかなっています。
  • ジョンのテストによると、使用してorステートメントは、より高速なC ++で使用するよりもあるswitchが、JavaやC#での間に違いはないようであるorswitch
  • (64のブール値のプライベート静的配列として)ルックアップテーブルを作成してみました。次に、switchやorstatementの代わりに、とだけ言いif(lookup[(int)(n&0x3F)]) { test } else return false;ます。驚いたことに、これは(ほんの少し)遅くなりました。これは、配列の境界がJavaでチェックされるためです。

21
これはJavaコードであり、int == 32ビットおよびlong == 64ビットであり、両方が署名されています。
キップ

14
@Shreevasta:大きな値(2 ^ 53より大きい)でいくつかのテストを行いましたが、あなたのメソッドはいくつかの誤検知を与えます。最初に遭遇するのはn = 9007199326062755で、これは完全な正方形ではありませんが、1として返されます。
キップ

37
「ジョンカーマックハック」と呼ばないでください。彼はそれを思いつきませんでした。
user9282 2009年

84
@mamama-たぶん、それは彼に起因している。ヘンリーフォードは車を発明しなかった、ライトブラザーズは飛行機を発明しなかった、そしてガレレオは太陽を中心に回転する地球を理解した最初のものではなかった...世界は盗まれた発明で構成されている(そして愛)。
Robert Fraser、

4
((1<<(n&15))|65004) != 03つの個別のチェックを使用する代わりに、のようなものを使用することにより、「quickfail」のわずかな速度向上が得られる場合があります。
Nabb

回答:


736

私は、少なくとも私のCPU(x86)とプログラミング言語(C / C ++)で、6bits + 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.
    アイデアは、各反復で、rに1ビットを追加することです。これは、xの「現在の」平方根です。各平方根は2の累乗、つまりt / 2を法として正確です。最後に、rとt / 2-rは、x / 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;
}

5
うわー!これをJavaに変換して比較し、結果の精度チェックを行います。私が見つけたものをお知らせします。
Kip

79
わあ、これは美しい。私は以前にヘンゼルが(素数を法とする多項式の根を計算する)リフトするのを見たことがありますが、補題を数値の平方根を計算するために注意深く下げることができることに気づきませんでした。これは...高揚する:)
ShreevatsaR

3
@nightcrackerありません。9 < 0 => false9&2 => 09&7 == 5 => false9&11 == 8 => false
primo

53
Maartinusは、2倍高速の(そしてはるかに短い)ソリューションを以下に投稿しましたが、少し遅れて、あまり愛されていないようです。
Jason C

3
明白な二乗をフィルタリングすることにより、さまざまなソリューションにおける速度の利点の多くが得られるようです。Maartinusのソリューションを介してフィルターをかけ、組み込み関数であるsqrt関数を使用する状況をベンチマークしましたか?
user1914292 2014年

377

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

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

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

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

末尾のゼロを削除した後、3番目のテストは、2進数で011、101、または111で終わる数値を処理します。これらは完全な正方形ではありません。また、負の数を考慮し、0も処理します。

最後のテストはdouble算術に戻ります。double唯一の53ビット仮数を有する、からの変換longにはdouble大きな値の丸め含みます。それにもかかわらず、テストは正しいです(証明が間違っていない限り)。

mod255のアイデアを取り入れようとしても成功しませんでした。


3
シフト値の暗黙のマスキングは少し悪です。それがJava仕様に含まれる理由が何かわかりますか?
dfeuer 14

6
@dfeuer理由は2つあると思います。1.シフトするのは意味がありません。2.これはHWが機能するようなもので、ビット単位の演算を使用する人は誰でもパフォーマンスに関心があるため、他のことをすると何も問題が発生します。-goodMaskテストはそれをしないが、それはそれをしないの前に右シフト。だからあなたはそれを繰り返す必要がありますが、このようにそれはより簡単で、私の知る限り少し速くそして同等に良いです。
maaartinus 2014

3
@dfeuerベンチマークでは、できるだけ早く答えを出すことが重要であり、末尾のゼロカウント自体は答えを出しません。これは準備段階にすぎません。i86 / amd64はそれを行います。モバイルの小さなCPUについてはわかりませんが、最悪の場合、JavaはそれらのAND命令を生成する必要があります。これは、他の方法よりも確かに簡単です。
maaartinus 14

2
@セバスチャンAおそらくより良いテスト:if ((x & (7 | Integer.MIN_VALUE)) != 1) return x == 0;
maaartinus

4
「ダブルのみ56ビットの仮数を持っているように、」 - >私はそれが可能性が高い持っていると言うだろう53ビットの 1を。 また、
chux -復活モニカ

132

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

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


それはかなり賢いです...それについては考えていなかったでしょう
ウォーレン

末尾のビットについての良い点。そのテストを他のいくつかの発言とここで組み合わせてみます。
PeterAllenWebb 2008年

3
素晴らしいソリューション。どのようにしてそれを思いついたのですか?かなり確立された原則ですか、それともあなたが考え出しただけですか?:D
Jeel Shah

3
@LarsH 0.5を追加する必要はありません。証明へのリンクについては、私のソリューションを参照してください。
maaartinus 14年

2
@JerryGoyalコンパイラとケースの値によって異なります。完璧なコンパイラでは、スイッチは常に少なくともif-elseと同じくらい高速です。しかし、コンパイラーは完全ではないので、Johnがしたように、それを試すことが最善です。
2017年

52

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

そして、覚えています。この関数は、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乗算演算)に減らし、代わりにいくつかの固定数の乗算演算で置き換えるため、より高速になるはずです。


9
これはsqrt(number)ではなく1 / sqrt(number)を返すことに注意してください。私はいくつかのテストをやった、これは、n = 410881で始まる失敗:ジョン・カーマック魔法式の戻りは642.00104、実際の平方根が641のとき
キップ

11
高速逆平方根に関するChris Lomontsの論文を見ることができます:lomont.org/Math/Papers/2003/InvSqrt.pdfこれは、ここと同じ手法を使用していますが、マジック番号が異なります。この論文では、マジックナンバーが選ばれた理由を説明しています。

4
また、beyond3d.com / content / articles / 8beyond3d.com/content/articles/15は、このメソッドの起源を明らかにしています。ジョン・カーマックが原因であることが多いですが、元のコードは(おそらく)ゲイリー・タロリ、グレッグ・ウォルシュ、そしておそらく他の人々によって書かれたようです。

3
また、Javaではfloatとintをタイププンできません。
アンチモン

10
@Antimony誰が言ったの?FloatToIntBitsIntToFloatBitsは、Java 1.0.2以降で使用されています。
corsiKa

38

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


10
カーマックのトリックは最近かなり無意味だと思います。組み込みのsqrt命令は、以前よりもはるかに高速であるため、通常の平方根を実行し、結果がintかどうかをテストすることをお勧めします。いつものように、それをベンチマークします。
2008年

4
実際の平方根が641の場合、John Carmackのマジック式は642.00104を返します。
キップ

11
私は最近、JavaゲームでCarmackのトリックを使用しましたが、それは非常に効果的であり、約40%のスピードアップを実現しました。
1

3
@Robert Fraserはいフレームレート全体で+ 40%。ゲームには、利用可能なほぼすべてのCPUサイクルを占めるパーティクルフィジックスシステムがあり、平方根関数と最も近い整数への丸め関数(同様のビットツイストリングハックを使用して最適化しました)が
支配的でした

5
リンクが壊れています。
Pixar

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回の繰り返ししかかかりません。


私のお金はこのようなアプローチにあります。sqrt()は完全な平方根を計算するため、最初の数桁しか必要ないため、呼び出しは避けてください。
PeterAllenWebb 2008年

3
一方、浮動小数点が専用のFPユニットで実行されている場合は、あらゆる種類の楽しいトリックが使用されている可能性があります。私はベンチマークなしでそれに賭けたくありません:)(私は今夜C#で試してみるかもしれませんが、見たいだけです...)
Jon Skeet

8
最近、ハードウェアsqrtはかなり高速です。
Adam Rosenfield、

24

私はこのスレッドでいくつかのアルゴリズムの独自の分析を実行し、いくつかの新しい結果を思いつきました。これらの古い結果はこの回答の編集履歴で確認できますが、私が間違えたため、正確ではありません。しかし、いくつかの異なる答えから教訓を引き出して、このスレッドの「勝者」を打ち砕く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の投稿された回答
  • デュロンワンパス回答をベースとして使用した私の変更された回答
  • 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万longでのみ実行される場合のサンプルランタイムを次に示します。

 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キャリパー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);
    }
}

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

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

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

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

3
巨大なルックアップテーブルは良いアイデアのようには思えません。キャッシュミスは、x86ハードウェアsqrt命令(約20サイクル)よりも遅い(約100から150サイクル)。スループットに関しては、多くの未解決のキャッシュミスを維持できますが、それでも他の有用なデータを追い出します。巨大なルックアップテーブルは、他のどのオプションよりもLOTで高速である場合にのみ価値があり、この関数はプログラム全体のパフォーマンスの主要な要因でした。
Peter Cordes 2015

1
@SwissFrank:プログラムが行う唯一のことは、完全二乗チェックですか?ルックアップテーブルは、タイトループで繰り返し呼び出すマイクロベンチマークでは見栄えがよくなりますが、ワーキングセットに他のデータがある実際のプログラムでは、見栄えがよくありません。
Peter Cordes 2018年

1
0x1FFFFFEビットのビットマップは、パックされたビットマップとして保存された場合、4 メガバイトかかります。最近のIntelデスクトップでのL3キャッシュヒットは、40サイクルを超えるレイテンシであり、大規模なXeonではさらに悪い。ハードウェアsqrt + mulレイテンシよりも長くなります。値ごとに1バイトのバイトマップとして格納されている場合、約32 MBです。すべてのコアが1つの巨大なキャッシュを共有するメニーコアXeon以外のL3キャッシュよりも大きい。したがって、入力データが十分に広い範囲の入力にわたって均一なランダム分布を持つ場合、タイトなループでも多くのL2キャッシュミスが発生します。(IntelのプライベートコアごとのL2は256kのみで、レイテンシは〜12です。)
Peter Cordes 2018年

1
@SwissFrank:ああ、あなたがしているのがルートチェックだけなら、ビットマップを使用してL3ヒットを取得できる可能性があります。レイテンシを検討していましたが、一度に多くのミスが発生している可能性があるため、スループットは潜在的に良好です。OTOH、SIMDのsqrtpsスループット、さらにはsqrtpd(倍精度)はSkylakeではそれほど悪くはありませんが、古いCPUでのレイテンシーほど優れていません。とにかく7-cpu.com/cpu/Haswell.htmlには、いくつかの素晴らしい実験的な数値と他のCPUのページがあります。Agner Fogのmicroarchガイドpdfには、IntelおよびAMDのキャッシュレイテンシの数値がいくつかあります:agner.org/optimize
Peter Cordes

1
Javaからx86 SIMDを使用することは問題であり、int-> fpおよびfp-> int変換のコストを追加するまでに、ビットマップの方が優れている可能性があります。double+ -2 ^ 24の範囲外の整数の丸めを回避するために精度が必要です(32ビットの整数はその範囲外になる可能性があります)。また、命令ごとに(SIMDベクトルごとに)半分の要素のみを処理するsqrtpdよりも低速です。sqrtps
Peter Cordes

18

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

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


1
数字根はを法と厳密に計算的に等価であるので、このようなMOD 16やMOD 255など、ここで他の剰余法、一緒に考慮されるべきである
クリスチャンOudard

1
デジタルルートがモジュロに相当することを確認しますか?リンクで説明されているように、まったく違うもののようです。リストは1,4,5,9ではなく1,4,7,9であることに注意してください。
Fractaly

1
10進法のデジタルルートは、モジュロ9を使用することと同じです(dr(n)= 1 +((n-1)mod 9);したがって、わずかにシフトします)。数値0、1、4、5、9は16を法とするもので、0、1、4、7は9を法とするものです。これらはデジタルルートの1、4、7、9に対応します。
Hans Olsson、

16

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

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


5
2 ^ 53より大きいすべての完全な正方形と、各完全な正方形の下5から各完全な正方形の上5までのすべての数値で実際に答えをテストしましたが、正しい結果が得られました。(丸め誤差は、sqrt回答をlongに丸め、その値を二乗して比較すると修正されます)
Kip

2
@Kip:私はそれが機能することを証明した思います。
maaartinus 2013

結果は完全に正確ではありませんが、想像以上に正確です。2倍に変換した後、平方根の後に少なくとも15桁の正確な数字があると仮定すると、11ビット以下で十分です。32ビット平方根は10桁、小数点以下の桁数は1未満なので、 +0.5は最も近い値に丸めます。
mwfearnley 14

3
Math.sqrt()は完全に正確ではありませんが、そうである必要はありません。最初の投稿では、tstはsqrt(N)に近い整数です。Nが正方形でない場合、tstの値に関係なく、tst * tst!= Nになります。Nが完全な正方形の場合、sqrt(N)<2 ^ 32であり、sqrt(N)が誤差<0.5で計算される限り問題ありません。
gnasher729 2014年

13

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

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


1
現在のハードウェアでは、整数除算はFP sqrtよりも低速です。このアイデアにはチャンスがありません。>。<2008年でさえ、Core2のsqrtsdスループットは6-58cあたり1です。ITSはidiv12-36cyclesにつき一つです。(スループットに似たレイテンシ:どちらのユニットもパイプライン化されていません)。
Peter Cordes

sqrtは完全に正確である必要はありません。そのため、結果を整数2乗して整数比較を行い、入力整数に正確な整数sqrtが含まれているかどうかを確認します。
Peter Cordes

11

d完全な四角形の最後の桁は特定の値しか取り得ないことが指摘されています。最後のd桁(塩基でb数)がn残りのと同じであるnことにより、分割されbd、すなわち、。C表記でn % pow(b, d)

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

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


いいね。これを代数的に、または試行錯誤によって解決しましたか?なぜそれが非常に効果的であるかがわかります-完全な正方形の間の多くの衝突、例えば333 ^ 2%110880 == 3 ^ 2、334 ^ 2%110880 == 26 ^ 2、338 ^ 2%110880 == 58 ^ 2 .. 。
finnw

IIRCは力ずくでしたが、110880 = 2 ^ 5 * 3 ^ 2 * 5 * 7 * 11であることに注意してください。これにより、6 * 3 * 2 * 2 * 2-1 = 143の適切な除数が得られます。
ヒューアレン、

ルックアップの制限により、通過率2.6%で44352がより適切に機能することがわかりました。少なくとも私の実装では。
Fractaly

1
整数除算(idiv)のコストはsqrtsd、現在のx86ハードウェアでのFP sqrt ()と同等かそれより悪いです。また、ビットフィールドを回避することには完全に同意しません。キャッシュヒット率はビットフィールドを使用すると数トン良くなり、ビットフィールドでビットをテストすることは、バイト全体をテストするよりも1つまたは2つだけ簡単な命令です。(非ビットフィールドとしてもキャッシュに収まる小さなテーブルの場合、32ビット整数ではなく、バイト配列が最適です。x86はシングルバイトアクセスで32ビットdwordと同等の速度です。)
Peter Cordes

11

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

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

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


実際には、少なくともO((log n)^2)乗算は定数時間ではなく、実際にはの下限があるため、時間がかかりO(log n)ます。これは、大きな多精度数を処理するときに明らかになります。しかし、このWikiの範囲は64ビットのようですので、おそらくnbdです。

10

以下の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;

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


2
結果はこちら。最初のテストを削除することは、ほとんどの場合かなり安価に解決されるため、悪いことです。ソースは私の答えにあります(更新)。
maaartinus 14

9

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


私もあなたの提案を解決策に取り入れました。また、素敵なハンドル。:)
キップ

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

}

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%で実行します。


7

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

私はJava BigIntegerと、整数でよりうまく機能するNewtonのメソッドのわずかに変更されたバージョンを使用しました。問題は、正確な四角はということであったn^2に収束(n-1)代わりのnためn^2-1 = (n-1)(n+1)、最終的な誤差は、単に1つのステップの最終除数を下回ったとアルゴリズムは終了しました。エラーを計算する前に元の引数に1を追加することで簡単に修正できました。(立方根などに2つ追加)

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


1
私はこれらのアルゴリズムについて同じことを多精度バッファにうまく変換できないと考えていました。だから私はこれをここに置くと思った...私は実際に巨大な数のためのより良い漸近的な複雑さを持つ確率的二乗テストを見つけ .....数論アプリケーションが珍しく自分自身を見つけることはない。プロジェクトオイラーに慣れていないけど...

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

反復回数はO(ln n)に見えます。ここで、nはvのビット長です。これにより、vが大きくなっても大幅に節約できるとは思いません。どちらか無料。それぞれが15サイクルの12回の反復で、洗浄されます。それでも、面白かったことの+1。
Tadmas、2009年

実は、XORで足し算と引き算ができると思います。
ブレントロングボロー、2009年

XORで追加できるのは追加のコメントだけです。減算は算術です。
Brent.Longborough、2009年

1
とにかく、XORの実行時間と加算の間に実質的な違いはありますか?
Tadmas、2009年

1
@Tadmas:「後で最適化する」ルールを破るにはおそらく不十分です。(:-)
ブレント。ロングボロー2009年

6

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

なぜそれが遅いのですか?Javaは実際にはJNIを介してCルーチンを呼び出しており、Javaサブルーチンを呼び出すよりも実際には遅いため、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 duoマシンで約1/3の時間で実行されます。同じ線に沿ってさらに微調整すると、平均して時間をさらに短縮できると思いますが、チェックするたびに、より多くのテストをトレードオフしてより多くの除去を行うので、その道をそれほど遠くに進むことができません。

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

私がしているすべてのことは可能な四角形を排除することですが、潜在的なケースがある場合、元のインライン化されたisPerfectSquareを呼び出さなければならないことに注意してください。

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

配列の境界チェックを本質的に行う必要はありませんが、Javaのオプティマイザはこれをすぐに理解する必要があるので、私はそれらを非難しません。


3
私はあなたが二度間違っていると思います。1. Intel sqrtはIEEEに準拠しています。唯一の不適合な指示は、言語引数の角度測定指示です。2. JavaはMath.sqrtの組み込みを使用し、JNIは使用しません
maaartinus 2013

1
使うのを忘れませんでしたpp2か?pp1最下位6ビットのテストに使用されていることは理解していますが、次の6ビットのテストが意味をなさないと思います。
maaartinus

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

6

一般的なビット長を考慮して(ここでは特定のタイプを使用しました)、以下のように単純なアルゴを設計しようとしました。最初は、0、1、2、または<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;  
}  

@Kip:ブラウザに問題があります。
nabam serbang 2010

1
インデントが必要です。
Steve Kuo

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%で実行されます。


  • 2

    これは、分割統治ソリューションです。

    自然数の平方根(number)が自然数(solution)の場合solution、の桁数に基づいての範囲を簡単に決定できますnumber

    • number1桁:solution範囲= 1-4
    • number2桁:solution範囲= 3-10
    • number3桁:solution範囲= 10から40
    • number4桁:solution範囲= 30〜100
    • number5桁: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"

    2
    コンセプトは気に入っていますが、大きな欠点を丁寧に指摘したいと思います。数値は2進数の2進数です。base 2をbase 10に変換することtoStringは、ビットごとの演算子に比べて非常にコストのかかる操作です。したがって、質問の目的(パフォーマンス)を満たすためには、ベース10の文字列ではなくビット演算子を使用する必要があります。繰り返しますが、私はあなたのコンセプトが本当に好きです。それにもかかわらず、あなたの実装(現在のところ)は、質問に対して投稿されたすべての可能な解決策の中で最も遅いです。
    ジャックギフィン

    1

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


    問題は、「一般的に使用される入力のセット」がないことです。通常はリストを反復処理するため、同じ入力を2回使用しません。
    Kip

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


    1

    整数演算を使用するニュートン法

    整数以外の演算を避けたい場合は、以下の方法を使用できます。基本的には整数演算用に変更されたニュートン法を使用します。

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

    この実装は、を使用するソリューションと競合することはできませんMath.sqrt。ただし、他のいくつかの投稿で説明されているフィルタリングメカニズムを使用すると、パフォーマンスを向上させることができます。


    1

    ニュートン法による平方根の計算は、開始値が妥当であれば、途方もなく高速です... ただし、妥当な開始値はなく、実際には、2分割とlog(2 ^ 64)の動作で終わります。
    本当に速くなるには、妥当な初期値に到達するための高速な方法が必要です。つまり、機械語に降格する必要があります。プロセッサがPentiumのPOPCNTのような命令を提供する場合、先行ゼロをカウントし、それを使用して、有効ビットの半分の開始値を持つことができます。注意して、常に十分な数のニュートンステップを見つけることができます。(したがって、ループする必要があり、非常に高速に実行する必要があります。)

    2番目の解決策は、浮動小数点機能を使用する方法です。浮動小数点機能を使用すると、sqrt計算が高速になります(i87コプロセッサーなど)。exp()およびlog()によるエクスカーションであっても、バイナリ検索に縮退したNewtonよりも高速な場合があります。これにはトリッキーな側面があります。それは、何をどのように処理するかをプロセッサに依存する分析であり、その後の改良が必要です。

    3番目の解決策は少し異なる問題を解決しますが、状況は質問に記載されているため、言及する価値があります。わずかに異なる数の非常に多くの平方根を計算する場合は、開始値を再初期化せずに、前の計算が終わったところにそのままにしておく場合は、ニュートン反復法を使用できます。これを使用して、少なくとも1つのオイラー問題で成功しました。


    適切な見積もりを取得することはそれほど難しくありません。数値の桁数を使用して、解の下限と上限を推定できます。分割統治ソリューションを提案する私の回答も参照してください。
    MWB 2018

    POPCNTと桁数のカウントの違いは何ですか?あなたが1ナノ秒でPOPCNTを行うことができることを除いて。
    アルバートファンデルホルスト

    1

    数が完全な平方である場合、その数の平方根。

    複雑さはlog(n)です

    /**
     * Calculate square root if the given number is a perfect square.
     * 
     * Approach: Sum of n odd numbers is equals to the square root of n*n, given 
     * that n is a perfect square.
     *
     * @param number
     * @return squareRoot
     */
    
    public static int calculateSquareRoot(int number) {
    
        int sum=1;
        int count =1;
        int squareRoot=1;
        while(sum<number) {
            count+=2;
            sum+=count;
            squareRoot++;
        }
        return squareRoot;
    }

    0

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


    2
    longの範囲に2 ^ 32の完全な正方形があります。このテーブルは巨大になります。また、メモリアクセスよりも値を計算することの利点は非常に大きい可能性があります。
    PeterAllenWebb 2008年

    ああ、ありません。2^ 16あります。2 ^ 32は2 ^ 16の2乗です。2 ^ 16があります。
    Celestial M Weasel

    3
    はい、ただしlongの範囲は32ビットではなく64ビットです。sqrt(2 ^ 64)= 2 ^ 32。(私は計算を少し簡単にするために符号ビットを無視しています...実際には(long)(2 ^ 31.5)= 3037000499完全な四角形があります)
    Kip

    0

    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;

    明らかに、各チェックポイントでいくつの数が間引かれるのかを知ることは興味深いでしょう。チェックが本当に独立しているのではないかと疑っています。

    弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
    Licensed under cc by-sa 3.0 with attribution required.