Самый быстрый способ определить, является ли квадратный корень целого числа целым числом

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

  1. я сделал это простым способом, используя встроенную математику.функция sqrt() функция, но мне интересно, есть ли способ сделать это быстрее ограничение себя только целочисленным доменом.
  2. поддержание таблица impratical (так как есть о 231.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;
}

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


новое решение опубликовано А. Рекс оказалось еще быстрее. При выполнении первого миллиарда целых чисел решение требовало только 34% времени, которое использовалось в исходном решении. В то время как John Carmack hack немного лучше для небольших значений n, преимущество сравненное к этому решению довольно небольшое.

вот решение A. Rex, преобразованное в Java:

private final static boolean isPerfectSquare(long n)
{
  // Quickfail
  if( n < 0 || ((n&2) != 0) || ((n & 7) == 5) || ((n & 11) == 8) )
    return false;
  if( n == 0 )
    return true;

  // Check mod 255 = 3 * 5 * 17, for fun
  long y = n;
  y = (y & 0xffffffffL) + (y >> 32);
  y = (y & 0xffffL) + (y >> 16);
  y = (y & 0xffL) + ((y >> 8) & 0xffL) + (y >> 16);
  if( bad255[(int)y] )
      return false;

  // Divide out powers of 4 using binary search
  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;

  if((n & 0x7L) != 1)
      return false;

  // Compute sqrt using something like Hensel's lemma
  long r, t, z;
  r = start[(int)((n >> 3) & 0x3ffL)];
  do {
    z = n - 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 <= (1L << 33) );
  return false;
}

private static boolean[] bad255 =
{
   false,false,true ,true ,false,true ,true ,true ,true ,false,true ,true ,true ,
   true ,true ,false,false,true ,true ,false,true ,false,true ,true ,true ,false,
   true ,true ,true ,true ,false,true ,true ,true ,false,true ,false,true ,true ,
   true ,true ,true ,true ,true ,true ,true ,true ,true ,true ,false,true ,false,
   true ,true ,true ,false,true ,true ,true ,true ,false,true ,true ,true ,false,
   true ,false,true ,true ,false,false,true ,true ,true ,true ,true ,false,true ,
   true ,true ,true ,false,true ,true ,false,false,true ,true ,true ,true ,true ,
   true ,true ,true ,false,true ,true ,true ,true ,true ,false,true ,true ,true ,
   true ,true ,false,true ,true ,true ,true ,false,true ,true ,true ,false,true ,
   true ,true ,true ,false,false,true ,true ,true ,true ,true ,true ,true ,true ,
   true ,true ,true ,true ,true ,false,false,true ,true ,true ,true ,true ,true ,
   true ,false,false,true ,true ,true ,true ,true ,false,true ,true ,false,true ,
   true ,true ,true ,true ,true ,true ,true ,true ,true ,true ,false,true ,true ,
   false,true ,false,true ,true ,false,true ,true ,true ,true ,true ,true ,true ,
   true ,true ,true ,true ,false,true ,true ,false,true ,true ,true ,true ,true ,
   false,false,true ,true ,true ,true ,true ,true ,true ,false,false,true ,true ,
   true ,true ,true ,true ,true ,true ,true ,true ,true ,true ,true ,false,false,
   true ,true ,true ,true ,false,true ,true ,true ,false,true ,true ,true ,true ,
   false,true ,true ,true ,true ,true ,false,true ,true ,true ,true ,true ,false,
   true ,true ,true ,true ,true ,true ,true ,true ,false,false,true ,true ,false,
   true ,true ,true ,true ,false,true ,true ,true ,true ,true ,false,false,true ,
   true ,false,true ,false,true ,true ,true ,false,true ,true ,true ,true ,false,
   true ,true ,true ,false,true ,false,true ,true ,true ,true ,true ,true ,true ,
   true ,true ,true ,true ,true ,false,true ,false,true ,true ,true ,false,true ,
   true ,true ,true ,false,true ,true ,true ,false,true ,false,true ,true ,false,
   false,true ,true ,true ,true ,true ,false,true ,true ,true ,true ,false,true ,
   true ,false,false,true ,true ,true ,true ,true ,true ,true ,true ,false,true ,
   true ,true ,true ,true ,false,true ,true ,true ,true ,true ,false,true ,true ,
   true ,true ,false,true ,true ,true ,false,true ,true ,true ,true ,false,false,
   true ,true ,true ,true ,true ,true ,true ,true ,true ,true ,true ,true ,true ,
   false,false,true ,true ,true ,true ,true ,true ,true ,false,false,true ,true ,
   true ,true ,true ,false,true ,true ,false,true ,true ,true ,true ,true ,true ,
   true ,true ,true ,true ,true ,false,true ,true ,false,true ,false,true ,true ,
   false,true ,true ,true ,true ,true ,true ,true ,true ,true ,true ,true ,false,
   true ,true ,false,true ,true ,true ,true ,true ,false,false,true ,true ,true ,
   true ,true ,true ,true ,false,false,true ,true ,true ,true ,true ,true ,true ,
   true ,true ,true ,true ,true ,true ,false,false,true ,true ,true ,true ,false,
   true ,true ,true ,false,true ,true ,true ,true ,false,true ,true ,true ,true ,
   true ,false,true ,true ,true ,true ,true ,false,true ,true ,true ,true ,true ,
   true ,true ,true ,false,false
};

private static int[] start =
{
  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
};

я пробовал различные решения, представленные ниже.

  • после тщательного тестирования, я обнаружил, что добавление 0.5 к результату математики.sqrt () не нужен, по крайней мере, не на моей машине.
  • на Джон Кармак взломать был быстрее, но он дал неправильные результаты, начиная с n=410881. Однако, как было предложено BobbyShaftoe, мы можем использовать Кармак hack для n
  • метод Ньютона был намного медленнее, чем Math.sqrt(). Это, вероятно, потому что Math.sqrt() использует что-то похожее на метод Ньютона, но реализовано в аппаратном обеспечении, так что это намного быстрее, чем в Java. Кроме того, метод Ньютона все еще требовал использования двойников.
  • модифицированный метод Ньютона, который использовал несколько трюков, так что была задействована только целочисленная математика, требовал некоторых хаков, чтобы избежать переполнения (я хочу, чтобы эта функция работала со всеми положительными 64-битными целыми числами со знаком), и это было еще медленнее, чем Math.sqrt().
  • Binary chop был еще медленнее. Это имеет смысл, потому что двоичный Чоп в среднем потребует 16 проходов, чтобы найти квадратный корень из 64-битного числа.

одно предложение, которое показало улучшения, было сделано Джон Д. Кук. Вы можете заметить, что последняя шестнадцатеричная цифра (т. е. последние 4 бита) идеального квадрата должна быть 0, 1, 4 или 9. Это значит что 75% из чисел можно немедленно исключить как возможные квадраты. Реализация этого решения привела к сокращению на 50% выполнения.

работая по предложению Джона, я исследовал свойства последнего n биты идеального квадрата. Анализируя последние 6 бит, я нашла, что только 12 из 64 значений за последние 6 бит. Это означает, что 81% значений можно исключить без использования математики. Реализация этого решения дала дополнительное сокращение времени выполнения на 8% (по сравнению с моим исходным алгоритм.) Анализ более 6 бит приводит к списку возможных конечных битов, который слишком велик, чтобы быть практичным.

вот код, который я использовал, который работает в 42% времени, требуемого оригинальным алгоритмом (на основе запуска первых 100 миллионов целых чисел). Для значений n чем 410881, оно бежит в только 29% из времени требуемого оригиналом алгоритм.

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

  switch((int)(n & 0x3F))
  {
  case 0x00: case 0x01: case 0x04: case 0x09: case 0x10: case 0x11:
  case 0x19: case 0x21: case 0x24: case 0x29: case 0x31: case 0x39:
    long sqrt;
    if(n < 410881L)
    {
      //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);
      i  = 0x5f3759df - ( i >> 1 );
      y  = Float.intBitsToFloat(i);
      y  = y * ( 1.5F - ( x2 * y * y ) );

      sqrt = (long)(1.0F/y);
    }
    else
    {
      //Carmack hack gives incorrect answer for n >= 410881.
      sqrt = (long)Math.sqrt(n);
    }
    return sqrt*sqrt == n;

  default:
    return false;
  }
}

Примечания:

  • согласно тестам Джона, используя or операторы быстрее в C++, чем с помощью switch, но в Java и C#, похоже, нет никакой разницы между or и switch.
  • я также попытался сделать таблицу подстановки (как частный статический массив из 64 логических значений). Тогда вместо switch или or заявление, я бы просто сказал if(lookup[(int)(n&0x3F)]) { test } else return false;. К моему удивлению, это было (только немного) замедлившийся. я не уверен, почему. это так границы массива проверяются в Java.

30 ответов


я выяснил метод, который работает ~35% быстрее, чем ваш код 6bits+Carmack+sqrt, по крайней мере, с моим CPU (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 остатков mod 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 как наибольшую мощность 2 деление z с небольшим трюком. Это позволяет мне пропустить значения 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;
}

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

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

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

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

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

попытка включить идею mod255 не увенчалась успехом.


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

Ваш алгоритм может быть почти оптимальным, но вы можете сделать быструю проверку, чтобы исключить некоторые возможности, прежде чем звонить корень квадратный рутины. Например, посмотрите на последнюю цифру вашего числа в hex, сделав немного "и"."Идеальные квадраты могут заканчиваться только на 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#.


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

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

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

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

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

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


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


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

(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 для параметра. Прошу прощения за путаницу!)

я не знаю, будет ли это быстрее или нет, но стоит попробовать.

EDIT: двоичный ЧОП не должен принимать весь диапазон целых чисел, либо (2^x)^2 = 2^(2x), поэтому, как только вы нашли верхний бит набора в своей цели (что можно сделать с помощью трюка с битами; я забыл, как именно) вы можете быстро получить ряд потенциальных ответов. Имейте в виду, что наивный двоичный ЧОП по-прежнему будет занимать до 31 или 32 итераций.


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

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

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

  • интернет - Кип опубликовал ответ
  • 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 суппорт 0.1-rc5)

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

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


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

            return trues;   
        }

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

            return trues;   
        }

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

            return trues;   
        }
    }

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

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

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

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

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


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

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


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

сначала постройте таблицу квадратов простых чисел, которые ниже 2^32. Это намного меньше, чем таблица всех целых чисел до этот предел.

решение было бы таким:

boolean isPerfectSquare(long number)
{
    if (number < 0) return false;
    if (number < 2) return true;

    for (int i = 0; ; i++)
    {
        long square = squareTable[i];
        if (square > number) return false;
        while (number % square == 0)
        {
            number /= square;
        }
        if (number == 1) return true;
    }
}

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

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


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

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

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


было указано, что последний d цифры идеального квадрата могут принимать только определенные значения. Последний d цифры (в базу b) в то же самое, что и остальные, когда n делится на bd, ie. в языке C n % pow(b, d).

это можно обобщить на любой модуль m, ie. n % m можно использовать, чтобы исключить некоторый процент чисел из идеальных квадратов. Модуль вы используете в настоящее время составляет 64, что позволяет 12, т. е.. 19% остатков, как можно больше квадратов. С небольшим кодированием я нашел модуль 110880, который позволяет только 2016, т. е. 1,8% остатков как возможные квадраты. Поэтому в зависимости от стоимости операции модуля (т. е. деление) и поиск таблицы по сравнению с квадратным корнем на вашем компьютере, используя этот модуль может быть быстрее.

кстати, если Java имеет способ хранить упакованный массив битов для таблицы поиска, не используйте его. 110880 32-битные слова не так много ОЗУ эти дни и получение машинного слова будут быстрее, чем получение одного бита.


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


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

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

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

  • дополнительный тест mod-255
  • деление входного значения на степени 4
  • быстрый обратный квадратный корень (для работы с высокими значениями N требуется 3 итерации, достаточно, чтобы сделать его медленнее, чем аппаратная функция квадратного корня.)

public class SquareTester {

    public static boolean isPerfectSquare(long n) {
        if (n < 0) {
            return false;
        } else {
            switch ((byte) n) {
            case -128: case -127: case -124: case -119: case -112:
            case -111: case -103: case  -95: case  -92: case  -87:
            case  -79: case  -71: case  -64: case  -63: case  -60:
            case  -55: case  -47: case  -39: case  -31: case  -28:
            case  -23: case  -15: case   -7: case    0: case    1:
            case    4: case    9: case   16: case   17: case   25:
            case   33: case   36: case   41: case   49: case   57:
            case   64: case   65: case   68: case   73: case   81:
            case   89: case   97: case  100: case  105: case  113:
            case  121:
                long i = (n * INV3465) >>> 52;
                if (! good3465[(int) i]) {
                    return false;
                } else {
                    long r = round(Math.sqrt(n));
                    return r*r == n; 
                }
            default:
                return false;
            }
        }
    }

    private static int round(double x) {
        return (int) Double.doubleToRawLongBits(x + (double) (1L << 52));
    }

    /** 3465<sup>-1</sup> modulo 2<sup>64</sup> */
    private static final long INV3465 = 0x8ffed161732e78b9L;

    private static final boolean[] good3465 =
        new boolean[0x1000];

    static {
        for (int r = 0; r < 3465; ++ r) {
            int i = (int) ((r * r * INV3465) >>> 52);
            good3465[i] = good3465[i+1] = true;
        }
    }

}

следующее упрощение решения maaartinus, похоже, сбривает несколько процентных пунктов от времени выполнения, но я недостаточно хорош в бенчмаркинге, чтобы создать тест, которому я могу доверять:

long goodMask; // 0xC840C04048404040 computed below
{
    for (int i=0; i<64; ++i) goodMask |= Long.MIN_VALUE >>> (i*i);
}

public boolean isSquare(long x) {
    // This tests if the 6 least significant bits are right.
    // Moving the to be tested bit to the highest position saves us masking.
    if (goodMask << x >= 0) return false;
    // Remove an even number of trailing zeros, leaving at most one.
    x >>= (Long.numberOfTrailingZeros(x) & (-2);
    // Repeat the test on the 6 least significant remaining bits.
    if (goodMask << x >= 0 | x <= 0) return x == 0;
    // Do it in the classical way.
    // The correctness is not trivial as the conversion from long to double is lossy!
    final long tst = (long) Math.sqrt(x);
    return tst * tst == x;
}

стоило бы проверить, как опустить первый тест,

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

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


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

2-е изд Магическое выражение для m ниже должно быть

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

а не как написано

конец 2-го редактирования

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

1-я правка:

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

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

конец 1-го редактирования

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


это переделка из десятичного в двоичный старого алгоритма калькулятора Марчанта (извините, у меня нет ссылки), в 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;
};

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

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

в C++ я подозреваю, что все сложные альтернативы потеряют скорость, но я не проверил их все. То, что я сделал, и то, что люди Java найдут полезным, - это простой хак, расширение специального тестирования, предложенного A. Rex. Использовать одно длинное значение в качестве битового массива, который не проверен. Таким образом, у вас есть 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 времени сердечником2 машина дуэта. Я подозреваю, что дальнейшие настройки по тем же линиям могут сократить время в среднем дальше, но каждый раз, когда вы проверяете, Вы меняете больше тестирования на большее устранение, поэтому вы не можете идти слишком далеко по этой дороге.

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

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

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

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


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

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

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


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

просто заменить:

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

Я проверил все возможные результаты, когда последние n бит квадрата наблюдается. Путем последовательно рассматривать больше битов, до 5/6th входных сигналов можно исключить. Я фактически разработал это для реализации алгоритма факторизации ферма, и он очень быстрый.

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
  • b имеет вид (2
  • c имеет вид (2
  • d имеет вид (2 сначала он проверяет, имеет ли он квадратный остаток с модулями мощности двух, затем он проверяет на основе конечного модуля, затем использует математику.sqrt, чтобы сделать окончательный тест. Я придумал эту идею с верхнего столба и попытался расширить ее. Я ценю любые замечания или предложения.

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


  • учитывая общую длину бита (хотя я использовал конкретный тип здесь), я попытался создать упрощенный algo, как показано ниже. Простая и очевидная проверка для 0,1,2 или

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

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


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

    ...

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

    public static void main(String[] args) {
        final int BITS = 16;
    
        BitSet foo = new BitSet();
    
        for(int i = 0; i< (1<<BITS); i++) {
            int sq = (i*i);
            sq = sq & ((1<<BITS)-1);
            foo.set(sq);
        }
    
        System.out.println("int[] mayBeASquare = {");
    
        for(int i = 0; i< 1<<(BITS-5); i++) {
            int kk = 0;
            for(int j = 0; j<32; j++) {
                if(foo.get((i << 5) | j)) {
                    kk |= 1<<j;
                }
            }
            System.out.print("0x" + Integer.toHexString(kk) + ", ");
            if(i%8 == 7) System.out.println();
        }
        System.out.println("};");
    }
    

    и вот результаты:

    (ed: elided для плохой работы в prettify.js; просмотр истории ревизий для просмотра.)


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

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

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

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

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


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


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

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

    • переместите чек против 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,1,4,5,6,9). Если это не так, то он не может быть 'лучше' .

    например.

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


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

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

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

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