关于Java:确定整数平方根是否为整数的最快方法

Fastest way to determine if an integer's square root is an integer

我正在寻找最快的方法来确定long值是否是一个完美的平方(即它的平方根是另一个整数):

  • 通过使用内置的Math.sqrt(),我很容易做到了。功能,但我想知道是否有一种方法可以通过将自己限制为仅整数域。
  • 维护查阅表格是不切实际的(因为231.5个整数,其平方小于263)。
  • 下面是我现在做的非常简单和直接的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    public final static boolean isPerfectSquare(long n)
    {
      if (n < 0)
        return false;

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

    注意:我在许多项目Euler问题中使用此函数。所以没有人需要维护这个代码。这种微观优化实际上可以起到一定的作用,因为其中一个挑战是在不到一分钟的时间内完成每一个算法,而这个函数在某些问题中需要被调用数百万次。

    我试过用不同的方法来解决这个问题:

    • 经过详尽的测试,我发现在math.sqrt()的结果中添加0.5是不必要的,至少在我的机器上是不必要的。
    • 快速反比平方根速度较快,但对n>=410881给出的结果不正确。但是,正如Bobbyshaftoe所建议的,对于n<410881,我们可以使用fisr hack。
    • 牛顿的方法比Math.sqrt()慢得多。这可能是因为EDCOX1的1使用类似于牛顿的方法,但是在硬件中实现,所以它比Java要快得多。此外,牛顿的方法仍然需要使用双数。
    • 一个改进的牛顿方法,它使用了一些技巧,所以只涉及整数数学,需要一些黑客来避免溢出(我希望这个函数能处理所有64位带符号的正整数),它仍然比Math.sqrt()慢。
    • 二元斩波更慢。这是有意义的,因为二进制杂碎平均需要16遍才能找到64位数字的平方根。
    • 根据约翰的测试,使用EDCOX1,6个语句比使用EDCOX1,7个词在C++中更快,但是在爪哇和C中,EDCX1 6和EDCX1 7之间没有区别。
    • 我还尝试创建一个查找表(作为64个布尔值的私有静态数组)。然后,我不说switch或or语句,只说if(lookup[(int)(n&0x3F)]) { test } else return false;。令我惊讶的是,这(只是稍微)慢了一点。这是因为数组边界是用Java检查的。


    我想出了一个比你的6位+ CARMAK+SRT代码快35%的方法,至少用我的CPU(x86)和编程语言(C/C++)。您的结果可能会有所不同,特别是因为我不知道Java因素将如何发挥作用。

    我的方法有三个方面:

  • 首先,筛选出明显的答案。这包括负数和最后4位。(我发现看最后六个没什么帮助。)我也对0回答是。(在阅读下面的代码时,请注意我的输入是int64 x。)
    1
    2
    3
    4
    if( x < 0 || (x&2) || ((x & 7) == 5) || ((x & 11) == 8) )
        return false;
    if( x == 0 )
        return true;
  • 接下来,检查它是否是平方模255=3*5*17。因为这是三个不同素数的乘积,所以只有大约1/8的255型残基是正方形。但是,根据我的经验,调用modulo operator的成本比调用modulo operator得到的好处要高,所以我使用涉及255=2^8-1的位技巧来计算余数。(不管是好是坏,我没有使用从单词中读取单个字节的技巧,只使用位和移位。)
    1
    2
    3
    4
    5
    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.

    代码>为了实际检查余数是否为正方形,我在预先计算的表中查找答案。

    1
    2
    3
    if( bad255[y] )
        return false;
    // However, I just use a table of size 512
  • 最后,尝试使用类似于汉塞尔引理的方法计算平方根。(我不认为它直接适用,但它经过了一些修改。)在此之前,我用二进制搜索来划分2的所有幂:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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。

    1
    2
    if((x & 7) != 1)
        return false;

    汉塞尔引理的基本结构如下。(注意:未测试的代码;如果它不起作用,请尝试t=2或8。)

    1
    2
    3
    4
    5
    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上加一个位,即x的"当前"平方根;每个平方根都是精确的模,2的更大的幂,即t/2。最后,r和t/2-r是x模t/2的平方根。(注意,如果r是x的平方根,那么-r也是。这是真的偶数模,但是要注意,对一些数模,甚至可以有2个以上的平方根;特别是,这包括2的幂。)因为我们的实际平方根小于2^32,在这一点上,我们实际上可以检查r或t/2-r是否是实平方根。在我的实际代码中,我使用以下修改后的循环:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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值。在最后一部分,我研究了z = r - x * x,并将t设置为2的最大功率,用一个小技巧将z除尽。这样我就可以跳过t值,而t值无论如何都不会影响r的值。在我的例子中,预先计算的起始值选择了"最小的正"平方根模8192。

  • 即使这段代码对您来说不能更快地工作,我希望您喜欢它包含的一些想法。完整的测试代码如下,包括预计算表。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    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;
    }


    ></P><P>我参加聚会已经很晚了,但我希望能提供一个更好的答案;时间更短,而且(假设我的基准是正确的)也更快。</P></p>
<div class=

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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%的概率在这里结束。

    第二个测试捕获所有在因子分解中具有奇数two的数字。方法Long.numberOfTrailingZeros非常快,因为它被JIT化为单个i86指令。

    在去掉尾随的零之后,第三个测试处理以011、101或111结尾的二进制数字,这些数字不是完美的平方。它还关心负数,还处理0。

    最后的测试返回到double算术。由于double只有53位尾数,从longdouble的转换包括大值的舍入。然而,测试是正确的(除非证明是错误的)。

    尝试合并mod255的想法没有成功。


    你必须做一些基准测试。最佳算法将取决于输入的分布。

    您的算法可能几乎是最优的,但您可能希望在调用平方根例程之前进行快速检查以排除某些可能性。例如,用十六进制表示数字的最后一个数字时,要注意"和"。"完美平方"只能以0、1、4或9结尾,以16为基数,因此对于75%的输入(假设它们是均匀分布的),可以避免调用平方根,以换取一些非常快的位旋转。

    kip对实现十六进制技巧的以下代码进行了基准测试。当测试数字1到100000000时,此代码的运行速度是原始代码的两倍。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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语句时,hex技巧再次使代码的速度提高了两倍。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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代码没有什么影响。


    我在想我在数值分析课上度过的那些可怕的时光。

    然后我记得,有一个函数围绕着‘地震源代码的网络’旋转:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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软件的游戏!

    它是用C++编写的,但是一旦你明白了,在Java中重用同样的技术就不难了。

    我最初在:http://www.codemaestro.com/reviews/9找到它

    牛顿的方法在维基百科上解释:http://en.wikipedia.org/wiki/newton%27s_方法

    你可以通过链接了解更多关于它如何工作的解释,但是如果你不太在意,那么这大概就是我从阅读博客和参加数值分析课程中所记得的:

    • * (long*) &y基本上是一个快速转换为长函数的函数,因此整数运算可以应用于原始字节。
    • 0x5f3759df - (i >> 1);线是近似函数的一个预先计算的种子值。
    • * (float*) &i将值转换回浮点值。
    • y = y * ( threehalfs - ( x2 * y * y ) )行基本上再次迭代函数的值。

    近似函数给出的值越精确,对结果的迭代次数越多。在地震的情况下,一次迭代"足够好了",但如果不是你的话……然后您可以根据需要添加尽可能多的迭代。

    这应该更快,因为它将原始平方根中的除法运算减少到一个简单的被2除的除法运算(实际上是一个* 0.5F乘法运算),而代之以一些固定数目的乘法运算。


    我不确定它是否更快,甚至更准确,但是你可以使用约翰·卡马克的神奇平方根算法更快地求解平方根。您可以很容易地对所有可能的32位整数进行测试,并验证您实际上得到了正确的结果,因为它只是一个近似值。然而,现在我想起来了,使用双打也是近似的,所以我不确定这会如何发挥作用。


    如果你做了一个二元切分,试图找到"正确"的平方根,你可以相当容易地检测出你得到的值是否足够接近,以告诉:

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

    因此,在计算了n^2之后,选项如下:

    • n^2 = target:完成,返回真
    • 你很接近,但并不完美:返回错误
    • 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),所以一旦你在目标中找到了最上面的一个集合位(这可以通过一个小的旋转技巧来完成,我完全忘了怎么做),你就可以快速得到一系列潜在的答案。记住,一个简单的二进制CHOP仍然只需要31或32次迭代。


    我对这个线程中的几个算法进行了自己的分析,并得出了一些新的结果。你可以在这个答案的编辑历史中看到那些旧的结果,但是它们不准确,因为我犯了一个错误,并且浪费时间分析了几个不接近的算法。然而,从几个不同的答案中汲取经验教训,我现在有了两种算法来粉碎这个线程的"赢家"。以下是我与其他人不同之处的核心:

    1
    2
    3
    4
    5
    6
    // 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;

    然而,这一简单的行(大多数情况下会添加一两条非常快的指令)大大简化了switch-case语句成为一条if语句。但是,如果许多被测试的数字具有两个因素的显著威力,那么它可以添加到运行时中。

    以下算法如下:

    • 互联网-kip发布的答案
    • 杜伦-我修改的答案,以一次通过的答案为基础。
    • DurronTwo——我修改过的答案,使用了两遍答案(由@johnnyheggheim提供),还有一些其他的细微修改。

    如果这些数字是使用Math.abs(java.util.Random.nextLong())生成的,这里是一个运行时示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     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

    这里有一个示例运行时,如果它只运行在前百万个long上:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    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;
    }

    我的基准测试工具:(需要谷歌卡尺0.1-rc5)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    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文本十进制数,不是最佳的,但可以用ByteBuffer等明显改进)。但因为这是预分解,所以没什么关系。您可以在此处找到该文件(或自己生成):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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数组中,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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();
    }

    运行时示例。在我参加的每一次试验中,它都击败了EDOCX1(第四版)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     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


    使用牛顿的方法来计算整数平方根,然后平方这个数字并进行检查,这应该要快得多,就像在当前的解决方案中那样。牛顿方法是其他一些答案中提到的卡马克解的基础。您应该能够得到更快的答案,因为您只对根的整数部分感兴趣,这允许您更快地停止近似算法。

    另一个你可以尝试的优化:如果数字的根不以1、4、7或9这个数字不是一个完美的平方。这可以作为在应用较慢的平方根算法之前消除60%输入的快速方法。


    I want this function to work with all
    positive 64-bit signed integers

    Math.sqrt()使用double作为输入参数,因此对于大于2^53的整数,将无法获得准确的结果。


    就记录而言,另一种方法是使用素数分解。如果分解的每一个因子都是偶数,那么这个数就是一个完美的平方。所以你想知道一个数是否可以分解成质数平方的乘积。当然,您不需要获得这样的分解,只需要看看它是否存在。

    首先建立一个小于2^32的素数平方表。这远小于达到此限制的所有整数的表。

    解决方案如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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,那么输入的数字就是质数平方的分解。如果平方比数字本身大,那么这个平方或任何更大的平方都无法将其分割,因此数字不能是质数平方的分解。

    现在的sqrt是在硬件上完成的,需要在这里计算素数,我想这个解决方案要慢得多。但是,正如Mrzl在他的回答中所说的那样,它应该比使用sqrt的解决方案得到更好的结果,而sqrt的解决方案不能超过2^54。


    整数问题需要一个整数解。因此

    对(非负)整数进行二进制搜索,以找到最大整数t,使t**2 <= n。然后测试r**2 = n是否准确。这需要时间o(log n)。

    如果你不知道如何用二进制搜索正整数,因为集合是无界的,那么很容易。首先计算两次幂的递增函数f(在f(t) = t**2 - n之上)。当你看到它变为正时,你发现了一个上界。然后您可以进行标准的二进制搜索。


    有人指出,一个完全平方的最后一个d位数只能具有一定的值。数字n的最后一个d位数(以b为底)与当n除以bd时的余数相同,即用C表示法n % pow(b, d)

    这可以推广到任何模量m,也就是说,n % m可以用来排除某些百分比的数字是完全平方。你目前使用的模数是64,这允许12,即19%的余数,作为可能的平方。通过一点编码,我找到了模110880,它只允许2016年,即1.8%的余数作为可能的平方。因此,根据模运算(即除法)和表查找相对于机器上平方根的成本,使用这个模可能更快。

    顺便说一下,如果Java有一种方法来存储查找表的填充数组,就不要使用它。目前,110880个32位字的RAM并不多,提取机器字要比提取单个字快。


    为了表现,你经常要做一些比较。其他人表达了不同的方法,但是,你注意到Carmack的hack的速度快到了n的某些值。然后,你应该检查"n",如果它小于n,使用Carmack的hack,否则使用这里答案中描述的其他方法。


    以下对Maaartinus解决方案的简化似乎使运行时减少了几个百分点,但我在基准测试方面还不够好,无法生成我可以信任的基准测试:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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;
    }

    有必要检查一下省略第一个测试的方式,

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

    会影响性能。


    这是我能想出的最快的Java实现,它使用了这个线程中其他人提出的技术的组合。

    • MOD256试验
    • 不精确的mod-3465测试(避免整数除法,代价是一些误报)
    • 浮点平方根,圆,与输入值比较

    我还尝试了这些修改,但它们对性能没有帮助:

    • 附加mod-255测试
    • 将输入值除以4的幂
    • 快速反比平方根(要适用于n的高值,需要3次迭代,足以使其慢于硬件平方根函数。)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    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-1 modulo 264 */
        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;
            }
        }

    }

    你应该从一开始就去掉n的2次方部分。

    第二编辑下面M的神奇表达应该是

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

    不是书面的

    第二年底编辑

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

    第一编辑:

    轻微改善:

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

    第一年底编辑

    现在像往常一样继续。这样,当你到达浮点部分时,你已经去掉了所有2次方部分是奇数(大约一半)的数字,然后你只考虑剩下的1/8。也就是说,在6%的数字上运行浮点部分。


    标签中提到了Project Euler,其中的许多问题都需要检查编号>>2^64。当您使用80字节的缓冲区时,上面提到的大多数优化都不容易工作。

    我使用了JavaBigTigand和牛顿的方法的一个稍微修改的版本,一个更好地使用整数的方法。问题是精确平方n^2收敛到(n-1)而不是n,因为n^2-1=(n-1)(n+1),最终误差仅比最终除数低一步,算法终止。在计算错误之前,通过在原始参数中添加一个参数,很容易解决这个问题。(对立方根等加两个)

    这个算法的一个很好的属性是,您可以立即判断数字是否是一个完美的平方-牛顿方法中的最终误差(而非校正)将为零。简单的修改还可以让您快速计算楼层(sqrt(x)),而不是最接近的整数。这对于几个Euler问题很方便。


    这是一个从十进制到二进制的旧马尚计算器算法(对不起,我没有参考),在Ruby中,专门为这个问题改编:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    #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 <<"
    "
    ;
        };
        return 0;
    };


    正如前面提到的,sqrt调用并不是完全准确的,但它并没有以速度消除其他答案,这很有趣,也很有指导意义。毕竟,sqrt的汇编语言指令序列很小。英特尔有一个硬件指令,它不被Java使用,因为它不符合IEEE。

    那为什么慢呢?因为Java实际上是通过JNI调用C例程,它实际上比调用Java子程序要慢得多,Java子程序本身比内联做的要慢。这非常烦人,Java应该提出一个更好的解决方案,即在必要时建立浮点库调用。哦,好吧。

    在C++中,我怀疑所有复杂的替代品都会失去速度,但我没有检查它们。我所做的,以及Java人会发现什么有用,是一个简单的黑客攻击,是A. Rex提出的特殊情况测试的扩展。使用单个长值作为位数组,这不是边界检查。这样,就可以进行64位布尔查找。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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 <<"
    "
    ;  
    }


    inline bool isPerfectSquare5(UVLONG x) {
      return pp1 & (1 << (x & 0x3F)) ? isPerfectSquare(x) : false;
    }

    程序isPerfectSquare5在我的Core2 Duo机器上运行大约1/3的时间。我怀疑,在同一条线上进行进一步的调整平均可以进一步缩短时间,但每次检查时,您都在权衡更多的测试以获得更多的消除,因此您不能在这条路上走得太远。

    当然,您可以用同样的方法检查高6位,而不是对负进行单独的测试。

    请注意,我所做的就是消除可能的平方,但是当我有一个可能的情况时,我必须调用原始的内联isPerfectSquare。

    init2例程被调用一次以初始化pp1和pp2的静态值。请注意,在我的C++实现中,我使用的是未签名的long long,所以既然已经签名,就必须使用> >运算符。

    没有必要对数组进行边界检查,但是Java的优化器必须很快地把这些东西计算出来,所以我不为此责怪他们。


    我喜欢在一些输入上使用几乎正确的方法。这里有一个更高"偏移"的版本。代码似乎可以工作并通过我的简单测试用例。

    只需替换:

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

    使用此代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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);
    }

    当观察到一个平方的最后n位时,我检查了所有可能的结果。通过连续检查更多的位,可以消除最多5/6的输入。实际上,我设计这个来实现费马的因式分解算法,它在那里非常快。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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

    它首先测试它是否有一个二次幂模的平方残差,然后根据一个最终的模进行测试,然后使用math.sqrt进行最终测试。我从最上面的帖子里提出了这个想法,并试图扩展到它上面。我很感激你的评论和建议。

    更新:使用一个模(modsq)和一个44352的模基进行测试,我的测试运行在操作更新中的96%的时间内,最多更新100000000个数字。



    考虑到一般的位长度(尽管我在这里使用了特定的类型),我尝试设计如下简单算法。最初需要简单而明显地检查0、1、2或<0。以下是简单的意义上,它不试图使用任何现有的数学函数。大多数运算符可以用位运算符替换。不过,我还没有用任何基准数据进行测试。我既不是数学专家,也不是计算机算法设计专家,我很想看到你指出问题所在。我知道那里有很多改进的机会。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    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("
     Perfect square for %d"
    , c2);  
        else  
            printf("
     Not perfect but nearest to :%d :"
    , c2);  
        return 0;  
    }


    我不知道这之前有没有提到过。但我在这里找到了一个解决方案:

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

    这是最简单和最简洁的方法,尽管我不知道它如何在CPU周期方面进行比较。如果你只想知道根是否是一个整数的话,这个方法非常有效。如果你真的关心它是不是一个整数,你也可以计算出来。下面是一个简单(纯)函数:

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

    如果您不需要微观优化,这个答案在简单性和可维护性方面会更好。如果您得到的是负数,那么您可能需要在number参数上使用math.abs()作为math.sqrt()参数。

    在我的3.6GHz Intel i7-4790 CPU上,在0-10000000上运行此算法平均每次计算花费35-37纳秒。我做了10次连续运行,打印了1000万sqrt计算的平均时间。每次总运行只需600毫秒多一点就可以完成。

    如果您执行的计算数量较少,则早期的计算会花费更长的时间。


    如果最后的x位数是n,那么包装"不能是一个完美的正方形"应该是可能的,比这个效率高得多!我将使用Java 32位INT,并产生足够的数据来检查数字的最后16位,即2048个十六进制int值。

    好啊。要么我遇到了一些我无法理解的数字理论,要么我的代码中有一个错误。在任何情况下,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    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中的性能不佳被排除;查看修订历史以查看。)


    如果速度是一个问题,为什么不将最常用的一组输入及其值分割到一个查找表中,然后针对特殊情况执行您所提出的任何优化魔术算法呢?


    "我正在寻找最快的方法来确定一个长值是否是一个完美的平方(即它的平方根是另一个整数)。"

    答案令人印象深刻,但我没有看到简单的检查:

    检查long it右边的第一个数字是否为集合的成员(0,1,4,5,6,9)。如果不是,那么它就不可能是一个"完美的正方形"。

    如。

    不能是一个完美的正方形。


    如果你想要速度,考虑到整数的大小是有限的,我猜想最快的方法是(a)按大小(例如按最大位集划分为类别),然后根据该范围内的一组完美平方来检查该值。


    对于这个问题,最好的算法可能是一个快速整数平方根算法https://stackoverflow.com/a/5158524/5191852

    在@kde中,牛顿方法的三次迭代足以使32位整数的精度达到±1。当然,64位整数需要更多的迭代,可能是6或7。


    对于carmac方法,似乎只需重复一次就很容易了,这应该是精度位数的两倍。毕竟,这是一个非常截断的迭代方法——牛顿的方法,有一个非常好的第一猜测。

    关于您目前的最佳状态,我看到两个微优化:

    • 使用mod255在检查后将检查相对于0移动
    • 重新排列四的除法幂,跳过通常(75%)情况下的所有检查。

    即:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 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;
    }

    更好的可能是一个简单的

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

    很明显,知道每个检查点有多少个数字被剔除是很有趣的——我更怀疑这些检查是真正独立的,这使得事情变得很棘手。


    牛顿整数算法

    如果希望避免非整数运算,可以使用下面的方法。它基本上使用牛顿的方法修改整数算术。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /**
     * Test if the given number is a perfect square.
     * @param n Must be greater than 0 and less
     *    than Long.MAX_VALUE.
     * @return <wyn>true</wyn> if n is a perfect
     *    square, or <wyn>false</wyn> 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的解决方案竞争。但是,它的性能可以通过使用其他一些帖子中描述的过滤机制来改进。


    用牛顿法计算平方根的速度是惊人的快…前提是起始值合理。然而,没有合理的起始值,在实践中,我们以平分和对数(2^64)行为结束。要真正快速,我们需要一个快速的方法来获得一个合理的起始值,这意味着我们需要下降到机器语言。如果处理器提供像奔腾中的popcnt这样的指令,它对前导零进行计数,我们可以用它得到一个起始值,其有效位为一半。小心地,我们可以找到一个固定数量的牛顿步骤,它总是足够的。(因此,之前需要循环并具有非常快的执行速度。)

    第二个解决方案是通过浮点工具,它可能具有快速的sqrt计算(如i87协处理器),甚至通过exp()和log()的偏移也可能比牛顿退化为二进制搜索更快。这有一个棘手的方面,一个依赖于处理器的分析是什么,如果随后的精化是必要的。

    第三种解决方案解决了一个稍有不同的问题,但很值得一提,因为问题中描述了这种情况。如果要为稍微不同的数字计算大量的平方根,可以使用牛顿迭代法,如果从未重新初始化起始值,但只需将其保留在上次计算的位置。我在至少一个Euler问题中成功地使用了这个方法。


    这是一个分而治之的解决方案。

    如果自然数(number的平方根是自然数(solution的平方根),则可以根据number的位数轻松确定solution的范围:

    • number有1个数字:solution在1-4范围内
    • number有2个数字:solution在范围内=3-10
    • number有3个数字:solution在范围内=10-40
    • number有4个数字:solution在范围内=30-100
    • number有5个数字:solution在100-400范围内

    注意到重复了吗?

    您可以在二进制搜索方法中使用此范围来查看是否存在solution,其中:

    1
    number == solution * solution

    这是密码

    这是我班的学生

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    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;
        }

    }

    下面是一个如何使用它的例子。

    1
    2
    3
    4
    5
    6
    7
    8
    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"

    不知道这是不是最快的方法,但这是我在数学课上无聊和玩计算器时偶然发现的(很久以前在高中)。当时,我真的很惊讶这是有效的…

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public static boolean isIntRoot(int number) {
        return isIntRootHelper(number, 1);
    }

    private static boolean isIntRootHelper(int number, int index) {
        if (number == index) {
            return true;
        }
        if (number < index) {
            return false;
        }
        else {
            return isIntRootHelper(number - 2 * index, index + 1);
        }
    }


    不知道最快,但最简单的方法是以普通的方式取平方根,将结果乘以它本身,看看它是否与原始值匹配。

    既然我们在这里讨论整数,那么fasted可能涉及一个集合,您可以在其中进行查找。