it-swarm.com.ru

Самый быстрый способ определить

Я ищу самый быстрый способ определить, является ли значение long идеальным квадратом (то есть его квадратный корень является другим целым числом): 

  1. Я сделал это простым способом, используя встроенную функцию Math.sqrt(), Но мне интересно, есть ли способ сделать это быстрее с помощью , Ограничивая себя только целочисленным доменом.
  2. Ведение справочной таблицы нецелесообразно (так как существует около 2+31,5 целые числа, площадь которых меньше 263).

Вот очень простой и понятный способ сделать это сейчас:

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

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

Примечание: я использую эту функцию во многих Project Euler задачах. Так что больше никому не придется поддерживать этот код. И этот вид микрооптимизации может реально изменить ситуацию, поскольку одна из задач состоит в том, чтобы выполнить каждый алгоритм менее чем за минуту, и в некоторых задачах эту функцию нужно будет вызывать миллионы раз.


Я пробовал разные решения проблемы:

  • После исчерпывающего тестирования я обнаружил, что добавление 0.5 к результату Math.sqrt () необязательно, по крайней мере, на моей машине.
  • быстрый обратный квадратный корень был быстрее, но он дал неверные результаты для n> = 410881. Однако, как предполагает BobbyShaftoe , мы можем использовать хак FISR для n <410881.
  • Метод Ньютона был немного медленнее, чем Math.sqrt(). Вероятно, это связано с тем, что Math.sqrt() использует что-то похожее на метод Ньютона, но реализовано в аппаратном обеспечении, поэтому оно намного быстрее, чем в Java. Кроме того, метод Ньютона все еще требовал использования двойных чисел.
  • Модифицированный метод Ньютона, который использовал несколько приемов так, чтобы была задействована только целочисленная математика, требовал некоторых хаков, чтобы избежать переполнения (я хочу, чтобы эта функция работала со всеми положительными 64-битными целыми числами со знаком), и он все еще был медленнее, чем Math.sqrt().
  • Бинарная отбивная была еще медленнее. Это имеет смысл, потому что двоичной отбивке в среднем потребуется 16 проходов, чтобы найти квадратный корень 64-битного числа.
  • Согласно тестам Джона, использование операторов or в C++ быстрее, чем использование switch, но в Java и C #, похоже, нет разницы между or и switch.
  • Я также попытался создать таблицу поиска (как частный статический массив из 64 логических значений). Тогда вместо оператора switch или or я бы просто сказал if(lookup[(int)(n&0x3F)]) { test } else return false;. К моему удивлению, это было (немного) медленнее. Это потому, что границы массивов проверяются в Java
1318
Kip

Я нашел метод, который работает на 35% быстрее, чем ваш код 6bit + Carmack + sqrt, по крайней мере, с моим процессором (x86) и языком программирования (C/C++). Ваши результаты могут отличаться, особенно потому, что я не знаю, как будет действовать фактор Java.

Мой подход тройной:

  1. Сначала отфильтруйте очевидные ответы. Это включает в себя отрицательные числа и глядя на последние 4 бита. (Я обнаружил, что просмотр последних шести не помог.) Я также отвечаю да для 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. Поскольку это произведение трех различных простых чисел, только около 1/8 из остатков по модулю 255 являются квадратами. Однако, по моему опыту, вызов оператора по модулю (%) стоит больше выгоды, которую можно получить, поэтому я использую битовые трюки с 255 = 2 ^ 8-1 для вычисления остатка. (Что бы там ни было, я не использую уловку чтения отдельных байтов из Слова, только поразрядно - и сдвиги.)
    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, «текущий» квадратный корень из x; каждый квадратный корень является точным по модулю все большей и большей степени 2, а именно t/2. В конце r и t/2-r будут квадратными корнями из x по модулю t/2. (Обратите внимание, что если 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. В последней части я смотрю на z = r - x * x и задаю t как наибольшую степень деления z на 2 с небольшим фокусом. Это позволяет мне пропустить t значений, которые не повлияли бы на значение r в любом случае. Предварительно вычисленное начальное значение в моем случае выбирает «наименьший положительный» квадратный корень по модулю 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;
}
649
A. Rex

Я довольно поздно на вечеринку, но я надеюсь дать лучший ответ; короче и (при условии, что мой эталон правильный) также намного быстрее .

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, поэтому нет затрат на доступ к массиву (проверка косвенности и границ). Для равномерно случайной long вероятность окончания здесь составляет 81,25%.

Второй тест ловит все числа, имеющие нечетное число двойок в их факторизации. Метод Long.numberOfTrailingZeros очень быстрый, так как он превращает JIT-ed в одну инструкцию i86.

После отбрасывания конечных нулей третий тест обрабатывает числа, заканчивающиеся на 011, 101 или 111 в двоичном виде, которые не являются идеальными квадратами. Он также заботится об отрицательных числах, а также обрабатывает 0.

Финальный тест возвращается к double арифметике. Поскольку double имеет только 53 бита мантиссы, Преобразование из long в double включает в себя округление для больших значений. Тем не менее, тест верен (если доказательство не верно).

Попытка внедрить идею mod255 не удалась.

317
maaartinus

Вам нужно будет сделать несколько тестов. Лучший алгоритм будет зависеть от распределения ваших входных данных.

Ваш алгоритм может быть почти оптимальным, но вы можете сделать быструю проверку, чтобы исключить некоторые возможности, прежде чем вызывать процедуру получения квадратного корня. Например, посмотрите на последнюю цифру вашего числа в шестнадцатеричном виде, выполнив побитовое «и». Совершенные квадраты могут заканчиваться только 0, 1, 4 или 9 в основании 16, так что для 75% ваших входных данных (при условии, что они распределены равномерно) вы можете избежать вызова квадратного корня в обмен на какое-то очень быстрое переключение битов.

Кип протестировал следующий код, реализующий шестнадцатеричный трюк. При тестировании чисел от 1 до 100 000 000 этот код выполнялся в два раза быстрее оригинала.

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, шестнадцатеричный трюк снова сделал код в два раза быстрее.

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

125
John D. Cook

Я думал об ужасных временах, которые я провел в курсе численного анализа.

И потом я помню, что эта функция кружила по сети из исходного кода 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!

Он написан на C++, но не должно быть слишком сложно повторно использовать ту же технику в Java, как только вы получите идею:

Первоначально я нашел его по адресу: http://www.codemaestro.com/reviews/9

Метод Ньютона, объясненный в википедии: http://en.wikipedia.org/wiki/Newton%27s_method

Вы можете перейти по ссылке для более подробного объяснения того, как это работает, но если вам все равно, то это примерно то, что я помню из чтения блога и прохождения курса численного анализа:

  • * (long*) &y - это в основном функция быстрого преобразования в long, поэтому целочисленные операции могут применяться к необработанным байтам.
  • строка 0x5f3759df - (i >> 1); - это предварительно рассчитанное начальное значение для функции аппроксимации.
  • * (float*) &i преобразует значение обратно в число с плавающей запятой.
  • строка y = y * ( threehalfs - ( x2 * y * y ) ) снова выполняет итерацию значения по функции.

Функция аппроксимации дает более точные значения, чем больше вы повторяете функцию по результату. В случае Quake, одна итерация "достаточно хороша", но если бы она была не для вас ... тогда вы могли бы добавить столько итераций, сколько вам нужно.

Это должно быть быстрее, потому что это уменьшает количество операций деления, выполняемых в простом квадратном корне, до простого деления на 2 (фактически операция умножения * 0.5F) и заменяет его несколькими фиксированными числами операций умножения.

48
chakrit

Я не уверен, будет ли это быстрее или даже точнее, но вы могли бы использовать алгоритм Джона Кармака - магический квадратный корень , чтобы быстрее решить квадратный корень. Вероятно, вы могли бы легко проверить это для всех возможных 32-битных целых чисел и убедиться, что вы действительно получили правильные результаты, так как это всего лишь приближение. Однако, теперь, когда я думаю об этом, использование двойных чисел также приближенно, так что я не уверен, каким образом это вступит в игру.

36
Kibbee

Если вы выполните двоичную отбивку, чтобы попытаться найти «правильный» квадратный корень, вы можете довольно легко определить, достаточно ли близко полученное значение, чтобы сказать:

(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 итерации.

32
Jon Skeet

Я провел собственный анализ нескольких алгоритмов в этой теме и получил новые результаты. Вы можете увидеть эти старые результаты в истории редактирования этого ответа, но они не точны, так как я допустил ошибку и потратил время на анализ нескольких алгоритмов, которые не являются близкими. Однако, извлекая уроки из нескольких разных ответов, у меня теперь есть два алгоритма, которые сокрушают «победителя» этой темы. Вот основная вещь, которую я делаю иначе, чем все остальные:

// 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. Тем не менее, это может добавить к времени выполнения, если многие из протестированных чисел имеют значительную степень двух факторов.

Алгоритмы ниже следующие:

  • Internet - опубликованный ответ Кипа
  • Durron - мой измененный ответ, использующий однопроходный ответ в качестве основы
  • DurronTwo - Мой измененный ответ с использованием двухпроходного ответа (@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

А вот пример времени выполнения, если он запускается только для первого миллиона длинных:

 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 является огромным победителем, потому что ему никогда не приходится делить на 4 много много раз в первом миллионном числе.

Вот Durron:

public final static boolean isPerfectSquareDurron(long n) {
    if(n < 0) return false;
    if(n == 0) return true;

    long x = n;
    // This is faster because a number is divisible by 16 only 6% of the time
    // and more than that a vanishingly small percentage.
    while((x & 0x3) == 0) x >>= 2;
    // This is effectively the same as the switch-case statement used in the original
    // answer. 
    if((x & 0x7) == 1) {

        long sqrt;
        if(x < 410881L)
        {
            int i;
            float x2, y;

            x2 = x * 0.5F;
            y  = x;
            i  = Float.floatToRawIntBits(y);
            i  = 0x5f3759df - ( i >> 1 );
            y  = Float.intBitsToFloat(i);
            y  = y * ( 1.5F - ( x2 * y * y ) );

            sqrt = (long)(1.0F/y);
        } else {
            sqrt = (long) Math.sqrt(x);
        }
        return sqrt*sqrt == x;
    }
    return false;
}

И DurronTwo

public final static boolean isPerfectSquareDurronTwo(long n) {
    if(n < 0) return false;
    // Needed to prevent infinite loop
    if(n == 0) return true;

    long x = n;
    while((x & 0x3) == 0) x >>= 2;
    if((x & 0x7) == 1) {
        long sqrt;
        if (x < 41529141369L) {
            int i;
            float x2, y;

            x2 = x * 0.5F;
            y = x;
            i = Float.floatToRawIntBits(y);
            //using the magic number from 
            //http://www.lomont.org/Math/Papers/2003/InvSqrt.pdf
            //since it more accurate
            i = 0x5f375a86 - (i >> 1);
            y = Float.intBitsToFloat(i);
            y = y * (1.5F - (x2 * y * y));
            y = y * (1.5F - (x2 * y * y)); //Newton iteration, more accurate
            sqrt = (long) ((1.0F/y) + 0.2);
        } else {
            //Carmack hack gives incorrect answer for n >= 41529141369.
            sqrt = (long) Math.sqrt(x);
        }
        return sqrt*sqrt == x;
    }
    return false;
}

И мой тестовый жгут: (Требуется Google Caliper 0.1-RC5)

public class SquareRootBenchmark {
    public static class Benchmark1 extends SimpleBenchmark {
        private static final int ARRAY_SIZE = 10000;
        long[] trials = new long[ARRAY_SIZE];

        @Override
        protected void setUp() throws Exception {
            Random r = new Random();
            for (int i = 0; i < ARRAY_SIZE; i++) {
                trials[i] = Math.abs(r.nextLong());
            }
        }


        public int timeInternet(int reps) {
            int trues = 0;
            for(int i = 0; i < reps; i++) {
                for(int j = 0; j < ARRAY_SIZE; j++) {
                    if(SquareRootAlgs.isPerfectSquareInternet(trials[j])) trues++;
                }
            }

            return trues;   
        }

        public int timeDurron(int reps) {
            int trues = 0;
            for(int i = 0; i < reps; i++) {
                for(int j = 0; j < ARRAY_SIZE; j++) {
                    if(SquareRootAlgs.isPerfectSquareDurron(trials[j])) trues++;
                }
            }

            return trues;   
        }

        public int timeDurronTwo(int reps) {
            int trues = 0;
            for(int i = 0; i < reps; i++) {
                for(int j = 0; j < ARRAY_SIZE; j++) {
                    if(SquareRootAlgs.isPerfectSquareDurronTwo(trials[j])) trues++;
                }
            }

            return trues;   
        }
    }

    public static void main(String... args) {
        Runner.main(Benchmark1.class, args);
    }
}

UPDATE: Я создал новый алгоритм, который быстрее в одних сценариях, медленнее в других, я получил разные тесты, основанные на разных входах. Если мы вычислим по модулю 0xFFFFFF = 3 x 3 x 5 x 7 x 13 x 17 x 241, мы можем исключить 97,82% чисел, которые не могут быть квадратами. Это может быть (вроде) сделано в одной строке, с 5 побитовыми операциями:

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

Результирующий индекс - это либо 1) остаток, 2) остаток + 0xFFFFFF, либо 3) остаток + 0x1FFFFFE. Конечно, нам нужна таблица поиска для остатков по модулю 0xFFFFFF, которая составляет около 3 МБ файла (в данном случае она хранится в виде десятичных чисел в тексте ascii, не оптимально, но явно улучшается с помощью ByteBuffer и т.д.). Но поскольку это предварительный расчет, не имеет большого значения. Вы можете найти файл здесь (или сгенерировать его самостоятельно): 

public final static boolean isPerfectSquareDurronThree(long n) {
    if(n < 0) return false;
    if(n == 0) return true;

    long x = n;
    while((x & 0x3) == 0) x >>= 2;
    if((x & 0x7) == 1) {
        if (!goodLookupSquares[(int) ((n & 0xFFFFFFl) + ((n >> 24) & 0xFFFFFFl) + (n >> 48))]) return false;
        long sqrt;
        if(x < 410881L)
        {
            int i;
            float x2, y;

            x2 = x * 0.5F;
            y  = x;
            i  = Float.floatToRawIntBits(y);
            i  = 0x5f3759df - ( i >> 1 );
            y  = Float.intBitsToFloat(i);
            y  = y * ( 1.5F - ( x2 * y * y ) );

            sqrt = (long)(1.0F/y);
        } else {
            sqrt = (long) Math.sqrt(x);
        }
        return sqrt*sqrt == x;
    }
    return false;
}

Я загружаю его в массив boolean следующим образом:

private static boolean[] goodLookupSquares = null;

public static void initGoodLookupSquares() throws Exception {
    Scanner s = new Scanner(new File("24residues_squares.txt"));

    goodLookupSquares = new boolean[0x1FFFFFE];

    while(s.hasNextLine()) {
        int residue = Integer.valueOf(s.nextLine());
        goodLookupSquares[residue] = true;
        goodLookupSquares[residue + 0xFFFFFF] = true;
        goodLookupSquares[residue + 0x1FFFFFE] = true;
    }

    s.close();
}

Пример выполнения. Он побил Durron (первая версия) в каждом испытании, которое я проводил.

 0% Scenario{vm=Java, trial=0, benchmark=Internet} 40665.77 ns; ?=566.71 ns @ 10 trials
33% Scenario{vm=Java, trial=0, benchmark=Durron} 38397.60 ns; ?=784.30 ns @ 10 trials
67% Scenario{vm=Java, trial=0, benchmark=DurronThree} 36171.46 ns; ?=693.02 ns @ 10 trials

  benchmark   us linear runtime
   Internet 40.7 ==============================
     Durron 38.4 ============================
DurronThree 36.2 ==========================

vm: Java
trial: 0
22
durron597

Должно быть намного быстрее использовать метод Ньютона для вычисления целочисленного квадратного корня , затем возвести в квадрат это число и проверить, как в текущем решении. Метод Ньютона является основой для решения Кармака, упомянутого в некоторых других ответах. Вы должны быть в состоянии получить более быстрый ответ, так как вас интересует только целочисленная часть корня, что позволяет быстрее остановить алгоритм аппроксимации.

Еще одна оптимизация, которую вы можете попробовать: если Digital Root числа не заканчивается на 1, 4, 7 или 9, то число не идеальный квадрат. Это можно использовать как быстрый способ устранения 60% ваших входных данных перед применением более медленного алгоритма квадратного корня.

16
Bill the Lizard

Я хочу, чтобы эта функция работала со всеми положительные 64-разрядные целые числа со знаком

Math.sqrt() работает с двойными значениями в качестве входных параметров, поэтому вы не получите точных результатов для целых чисел больше 2 ^ 53 .

14
mrzl

Просто для записи, другой подход заключается в использовании простого разложения. Если каждый фактор разложения четен, то число является идеальным квадратом. Итак, вы хотите посмотреть, можно ли разложить число как произведение квадратов простых чисел. Конечно, вам не нужно получать такое разложение, просто чтобы увидеть, существует ли оно.

Сначала создайте таблицу квадратов простых чисел, которые меньше, чем 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, то входное число было разложением квадрата простые числа. Если квадрат становится больше, чем само число, то этот квадрат или любые большие квадраты не могут его разделить, поэтому число не может быть разложением квадратов простых чисел.

Учитывая, что в настоящее время sqrt выполняется в аппаратном обеспечении и необходимость вычислять простые числа здесь, я думаю, что это решение намного медленнее. Но это должно дать лучшие результаты, чем решение с sqrt, которое не будет работать более 2 ^ 54, как говорит mrzl в своем ответе.

12
Cyrille Ka

Целочисленная задача заслуживает целочисленного решения. таким образом

Выполните бинарный поиск по (неотрицательным) целым числам, чтобы найти наибольшее целое число t такое, что t**2 <= n. Затем проверьте, является ли r**2 = n точно. Это занимает время O (log n). 

Если вы не знаете, как выполнить двоичный поиск натуральных чисел, потому что множество не ограничено, это легко. Вы начинаете с вычисления вашей возрастающей функции f (выше f(t) = t**2 - n) по степеням два. Когда вы видите, что это становится положительным, вы нашли верхнюю границу. Затем вы можете сделать стандартный бинарный поиск.

11
Colonel Panic

Было отмечено, что последние 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, и загрузка машины Word будет быстрее, чем загрузка одного бита.

10
Hugh Allen

Для производительности вам очень часто приходится идти на некоторые компромиссы. Другие выразили различные методы, однако вы заметили, что хак Кармака был быстрее до определенных значений N. Затем вы должны проверить «n», и если оно меньше, чем число N, используйте хак Кармака, иначе используйте какой-то другой описанный метод в ответах здесь.

9
BobbyShaftoe

Это самая быстрая реализация Java, которую я мог придумать, используя комбинацию методов, предложенных другими в этой теме.

  • Мод-256 тест
  • Неточный тест mod-3465 (избегает целочисленного деления за счет некоторых ложных срабатываний)
  • Квадратный корень с плавающей точкой, округлить и сравнить с входным значением

Я также экспериментировал с этими модификациями, но они не помогли производительности:

  • Дополнительный мод-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;
        }
    }

}
8
finnw

Следующее упрощение решения 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;

повлияет на производительность.

8
dfeuer

Вы должны избавиться от 2-степенной части N с самого начала.

2nd Edit Волшебное выражение для 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;

1-е редактирование:

Незначительное улучшение:

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

Конец первого редактирования

Теперь продолжайте как обычно. Таким образом, к тому времени, когда вы доберетесь до части с плавающей запятой, вы уже избавились от всех чисел, чья 2-степенная часть нечетна (примерно половина), и тогда вы будете считать только 1/8 того, что осталось. То есть вы запускаете часть с плавающей запятой на 6% чисел.

7
David Lehavi

Мне нравится идея использовать почти правильный метод для некоторых входных данных. Вот версия с более высоким «смещением». Код, кажется, работает и проходит мой простой тестовый пример.

Просто замените ваш:

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
Jonny Heggheim

Это доработка от десятичного к двоичному алгоритму старого калькулятора Марчанта (извините, у меня нет ссылки) в Ruby, адаптированном специально для этого вопроса:

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;
};
6
Brent.Longborough

Project Euler упоминается в тегах, и многие из проблем в нем требуют проверки номера >> 2 ^ 64. Большинство из упомянутых выше оптимизаций не работают легко, когда вы работаете с 80-байтовым буфером.

Я использовал Java BigInteger и слегка модифицированную версию метода Ньютона, которая лучше работает с целыми числами. Проблема заключалась в том, что точные квадраты n ^ 2 сходились к (n-1) вместо n, потому что n ^ 2-1 = (n-1) (n + 1), и окончательная ошибка была всего на один шаг ниже конечного делителя и алгоритм прекращен. Это было легко исправить, добавив один к исходному аргументу перед вычислением ошибки. (Добавьте два для кубических корней и т.д.)

Одним из приятных атрибутов этого алгоритма является то, что вы можете сразу сказать, является ли число идеальным квадратом - конечная ошибка (не коррекция) в методе Ньютона будет равна нулю. Простая модификация также позволяет вам быстро вычислить floor (sqrt (x)) вместо ближайшего целого числа. Это удобно с несколькими проблемами Эйлера.

6
bgiles

Вызов sqrt, как уже упоминалось, не совсем точен, но интересно и поучительно, что он не уносит другие ответы с точки зрения скорости. В конце концов, последовательность инструкций языка ассемблера для sqrt крошечная. У Intel есть аппаратная инструкция, которая не используется Java, я полагаю, потому что она не соответствует IEEE.

Так почему же это медленно? Потому что Java на самом деле вызывает подпрограмму C через JNI, и это на самом деле медленнее, чем вызов подпрограммы Java, которая сама по себе медленнее, чем встроенная. Это очень раздражает, и Java должна была придумать лучшее решение, то есть, при необходимости, создание вызовов библиотеки с плавающей запятой. Ну что ж.

Я подозреваю, что в C++ все сложные альтернативы потеряли бы скорость, но я не проверил их все .. То, что я сделал, и то, что люди Java найдут полезными, это простой взлом, расширение тестирования специального случая предложено А. Рекс. Используйте одно длинное значение в качестве битового массива, который не проверяется по границам. Таким образом, у вас есть 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 выполняется примерно на 1/3 времени на моей машине core2 duo. Я подозреваю, что дальнейшие изменения в том же направлении могут в среднем еще больше сократить время, но каждый раз, когда вы проверяете, вы тратите больше тестов на большее устранение, поэтому вы не можете идти слишком далеко на этом пути.

Конечно, вместо отдельного теста на отрицание вы можете проверить старшие 6 битов таким же образом.

Обратите внимание, что все, что я делаю, это устранение возможных квадратов, но когда у меня есть потенциальный случай, я должен вызвать исходный, встроенный isPerfectSquare.

Подпрограмма init2 вызывается один раз для инициализации статических значений pp1 и pp2 . Обратите внимание, что в моей реализации на C++ я использую long без знака, поэтому, поскольку вы подписаны, вам придется использовать> >> оператор.

Нет необходимости в проверке массива границ, но оптимизатор Java должен довольно быстро разобраться с этим, поэтому я не виню их за это.

6
hydrodog

Я проверил все возможные результаты, когда наблюдаются последние 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)
  • с имеет вид (2 << 2k + 2) - 1
  • d имеет вид (2 << 2k - 1) * 10

    Сначала он проверяет, имеет ли он квадратную невязку с модулями степени два, затем он проверяет на основе окончательного модуля, а затем использует Math.sqrt для выполнения окончательного теста. Я придумал идею из верхнего поста и попытался ее расширить. Я ценю любые комментарии или предложения.

    Обновление: Используя тест по модулю (modSq) и базе модулей 44352, мой тест выполняется в 96% времени по сравнению с тестом в обновлении OP для чисел до 1 000 000 000.

  • 5
    Fractaly

    Учитывая общую длину в битах (хотя я использовал здесь определенный тип), я попытался разработать упрощенный алгоритм, как показано ниже. Первоначально требуется простая и очевидная проверка для 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;  
    }  
    
    5
    nabam serbang

    Я не знаю, упоминалось ли это ранее. Но я нашел решение здесь :

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

    Если скорость вызывает беспокойство, почему бы не выделить из наиболее часто используемых наборов входных данных и их значений таблицу поиска, а затем выполнить любой оптимизированный магический алгоритм, который вы придумали для исключительных случаев?

    1
    Elijah

    Вот самый простой и краткий способ, хотя я не знаю, как он сравнивается с точки зрения циклов процессора. Это прекрасно работает, если вы хотите знать, является ли корень целым числом. Если вам действительно важно, является ли оно целым числом, вы также можете понять это. Вот простая (и чистая) функция:

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

    Если вам не нужна микрооптимизация, этот ответ лучше с точки зрения простоты и удобства обслуживания. Если вы будете получать отрицательные числа, возможно, вы захотите использовать Math.abs () для аргумента числа в качестве аргумента Math.sqrt ().

    На моем 3,6 ГГц процессоре Intel i7-4790 запуск этого алгоритма на 0–10 000 000 занял в среднем 35–37 наносекунд на расчёт. Я выполнил 10 последовательных прогонов, напечатав среднее время, затрачиваемое на каждый из десяти миллионов квадратных расчетов. Каждый полный прогон занимал чуть более 600 мсек.

    Если вы выполняете меньшее количество вычислений, более ранние вычисления занимают немного больше времени.

    1
    Steve Storck

    Должна быть возможность упаковать 'не может быть идеальным квадратом, если последние X цифр N' гораздо эффективнее, чем это! Я буду использовать 32-битные числа Java и получу достаточно данных для проверки последних 16 битов числа - это 2048 шестнадцатеричных значений типа int.

    ...

    Хорошо. Либо я столкнулся с некоторой теорией чисел, которая немного выше меня, либо в моем коде есть ошибка. В любом случае вот код:

    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
    paulmurray

    Может быть лучшим алгоритмом для этой проблемы является алгоритм быстрого целочисленного квадратного корня https://stackoverflow.com/a/51585204/5191852

    Там @Kde утверждает, что трех итераций метода Ньютона будет достаточно для точности ± 1 для 32-битных целых чисел. Конечно, для 64-разрядных целых чисел требуется больше итераций, может быть 6 или 7. 

    0
    Viktor

    Что касается метода Carmac, кажется, что было бы довольно легко просто повторить еще раз, что должно удвоить число цифр точности. В конце концов, это чрезвычайно укороченный итерационный метод - метод Ньютона, с очень хорошим первым предположением.

    Что касается вашего текущего лучшего, я вижу две микрооптимизации:

    • переместить чек против 0 после проверки, используя mod255
    • измените деление на четыре, чтобы пропустить все проверки для обычного (75%) случая. 

    То есть:

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

    Очевидно, было бы интересно узнать, сколько номеров отбраковано на каждой контрольной точке - я скорее сомневаюсь, что проверки действительно независимы, что усложняет задачу.

    0
    Ben

    Метод Ньютона с целочисленной арифметикой

    Если вы хотите избежать нецелых операций, вы можете использовать метод ниже. Он в основном использует метод Ньютона, модифицированный для целочисленной арифметики.

    /**
     * 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. Однако его производительность может быть улучшена с помощью механизмов фильтрации, описанных в некоторых других публикациях.

    0
    aventurin

    Вот решение «разделяй и властвуй».  

    Если корень квадратный из натурального числа (number) является натуральным числом (solution), вы можете легко определить диапазон для solution на основе количества цифр number:

    • 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, для которого:

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

    Если вам нужна скорость, учитывая, что ваши целые числа имеют конечный размер, я подозреваю, что самый быстрый способ заключался бы в (а) разбиении параметров по размеру (например, на категории по наибольшему установленному биту), а затем проверке значения по массиву совершенных квадратов в этом диапазоне. 

    0
    Celestial M Weasel

    Не уверен, что это самый быстрый способ, но на это я наткнулся (давным-давно в старшей школе), когда мне было скучно и я играл с калькулятором во время урока математики. В то время я был очень удивлен, что это работает ...

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

    «Я ищу самый быстрый способ определить, является ли длинное значение идеальным квадратом (то есть его квадратный корень является другим целым числом)». 

    Ответы впечатляют, но я не смог увидеть простую проверку:

    проверьте, находится ли первое число справа от него, входящего в набор (0,1,4,5,6,9). Если это не так, то это не может быть «идеальный квадрат».

    например.

    4567 - не может быть идеальным квадратом.

    0
    dstibbe

    Вычисление квадратных корней по методу Ньютона ужасно быстро ... при условии, что начальное значение разумно. Однако разумного начального значения не существует, и на практике мы заканчиваем разделение на две части и ведение журнала (2 ^ 64).
    Чтобы быть действительно быстрым, нам нужен быстрый способ получить разумное начальное значение, а это значит, что нам нужно погрузиться в машинный язык. Если процессор предоставляет в Pentium такую ​​инструкцию, как POPCNT, которая подсчитывает начальные нули, мы можем использовать ее, чтобы получить начальное значение с половиной значащих бит. С осторожностью мы можем найти фиксированное количество шагов Ньютона, которое всегда будет достаточно. (Таким образом, отпадает необходимость в цикле и очень быстром выполнении.)

    Второе решение заключается в использовании метода с плавающей запятой, который может иметь быстрое вычисление sqrt (как, например, сопроцессор i87). Даже экскурсия через exp () и log () может быть быстрее, чем Ньютон, вырождающийся в двоичный поиск. В этом есть один сложный аспект, зависящий от процессора анализ того, что и если впоследствии необходимо усовершенствовать.

    Третье решение решает немного другую проблему, но стоит упомянуть, потому что ситуация описана в этом вопросе. Если вы хотите вычислить большое количество квадратных корней для чисел, которые немного отличаются, вы можете использовать итерацию Ньютона, если вы никогда не инициализируете начальное значение, а просто оставляете его там, где остановились предыдущие вычисления. Я использовал это с успехом по крайней мере в одной проблеме Эйлера. 

    0
    Albert van der Horst