Найшвидший спосіб визначити, чи є квадратний корінь цілого числа цілим числом


1453

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

  1. Я зробив це простим способом, використовуючи вбудовану Math.sqrt() функцію, але мені цікаво, чи є спосіб зробити це швидше, обмежившись лише доменним числом.
  2. Підтримувати таблицю пошуку недоцільно (оскільки існує близько 2 31,5 цілих чисел, площа яких менша за 2 63 ).

Ось дуже простий і простий спосіб, як я це роблю зараз:

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

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

Примітка. Я використовую цю функцію в багатьох проблемах Project Euler . Тож нікому більше ніколи не доведеться підтримувати цей код. І такий тип мікрооптимізації може насправді змінити значення, оскільки частина завдання полягає в тому, щоб виконати кожен алгоритм менше ніж за хвилину, і цю функцію потрібно буде викликати мільйони разів у деяких проблемах.


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

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

21
Це код Java, де int == 32 біта і long == 64 біта, і обидва підписані.
Кіп

14
@Shreevasta: Я провів тестування на великі значення (більше 2 ^ 53), і ваш метод дає кілька помилкових позитивних результатів. Перший, що зустрічається, для n = 9007199326062755, який не є ідеальним квадратом, але повертається як один.
Кіп

37
Будь ласка, не називайте це "хаком Джона Кармака". Він цього не придумав.
user9282

84
@mamama - Можливо, але це приписується саме йому. Генрі Форд не вигадав машину, Райт Братс не винайшов літак, і Галлелео не був першим, хто зрозумів, що Земля обертається навколо сонця ... світ складається з викрадених винаходів (і любов).
Роберт Фрейзер

4
Ви можете отримати невелике збільшення швидкості в "швидкій файлі", використовуючи щось на зразок ((1<<(n&15))|65004) != 0, замість того, щоб три перевірки.
Nabb

Відповіді:


735

Я зрозумів метод, який працює на 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 мод 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 modulo 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;
}

5
Оце Так! Я спробую перетворити це на Java і зроблю порівняння, а також перевіряю точність результатів. Я дам вам знати, що я знаходжу.
Кіп

79
Ого, це прекрасно. Раніше я бачив, як Гензель піднімався (обчислював коріння поліномів modulo a prime), але навіть не зрозумів, що лему можна обережно знизити до кінця для обчислення квадратних коренів чисел; це ... підняття :)
ShreevatsaR

3
@nightcracker Це не так. 9 < 0 => false, 9&2 => 0, 9&7 == 5 => false, 9&11 == 8 => false.
примо

53
Maartinus розмістив внизу трохи швидше (і набагато коротше) рішення, трохи пізніше, яке, здається, не отримує великої любові.
Джейсон C

3
Здається, що велика перевага швидкості в різних рішеннях отримується фільтруванням очевидних квадратів. Хтось орієнтував ситуацію фільтрації через рішення Maartinus, а потім просто за допомогою функції sqrt, оскільки це вбудована функція?
користувач1914292

377

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

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

public boolean isSquare(long x) {
    // This tests if the 6 least significant bits are right.
    // Moving the to be tested bit to the highest position saves us masking.
    if (goodMask << x >= 0) return false;
    final int numberOfTrailingZeros = Long.numberOfTrailingZeros(x);
    // Each square ends with an even number of zeros.
    if ((numberOfTrailingZeros & 1) != 0) return false;
    x >>= numberOfTrailingZeros;
    // Now x is either 0 or odd.
    // In binary each odd square ends with 001.
    // Postpone the sign test until now; handle zero in the branch.
    if ((x&7) != 1 | x <= 0) return x == 0;
    // Do it in the classical way.
    // The correctness is not trivial as the conversion from long to double is lossy!
    final long tst = (long) Math.sqrt(x);
    return tst * tst == x;
}

Перший тест швидко наздоганяє більшість неквадратів. Він використовує 64-позиційну таблицю, упаковану в довгий час, тому немає вартості доступу до масиву (непрямі та межі перевірки). Для рівномірно випадкових long, є 81,25% ймовірність закінчення тут.

Другий тест фіксує всі числа, що мають непарну кількість подвійних при їх факторизації. Метод Long.numberOfTrailingZerosдуже швидкий, оскільки він отримує JIT-ed в єдину інструкцію i86.

Після скидання проміжних нулів третій тест обробляє числа, що закінчуються на 011, 101 або 111 у двійкових, які не є ідеальними квадратами. Він також піклується про негативні числа, а також обробляє 0.

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

Спроба включити ідею mod255 виявилася невдалою.


3
Це неявне маскування значення зсуву трохи ... зло. Чи маєте ви якесь уявлення, чому це в специфікації Java?
dfeuer

6
@dfeuer Я думаю, що є дві причини: 1. Переміщення на більше не має сенсу. 2. Це як HW працює, і кожен, хто використовує побитові операції, зацікавлений у продуктивності, тому робити що-небудь ще було б неправильно. -goodMask тест це робить, але він робить це перш , ніж зрушення вправо. Тож вам доведеться повторити це, але таким чином простіше і AFAIK крихітно трохи швидше і однаково добре.
maaartinus

3
@dfeuer Для еталону важливо дати відповідь якнайшвидше, а сам кінцевий нульовий підрахунок не дає відповіді; це лише підготовчий крок. i86 / amd64 зробіть це. Поняття про маленькі процесори в мобільних телефонах не має, але в гіршому випадку Java повинна сформувати для них інструкцію І, що, безумовно, простіше, ніж навпаки.
maaartinus

2
@Sebastian , ймовірно , краще тест: if ((x & (7 | Integer.MIN_VALUE)) != 1) return x == 0;.
maaartinus

4
"Оскільки дубль має лише 56 біт мантіси" -> я б сказав, що він, швидше за все, має 53-бітний . Також
chux

132

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

Ваш алгоритм може бути майже оптимальним, але ви, можливо, захочете зробити швидку перевірку, щоб виключити деякі можливості, перш ніж викликати рутину квадратного кореня. Наприклад, подивіться останню цифру свого номера в шістнадцятковій формі, зробивши трохи "мудро" і "." Ідеальні квадрати можуть закінчуватися лише на 0, 1, 4 або 9 в базі 16, Тож для 75% ваших входів (якщо припустити, що вони рівномірно розподілені), ви можете уникнути дзвінка до квадратного кореня в обмін на дуже швидке скорочення біт.

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

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

Усунення оператора перемикання мало вплинуло на код C #.


це досить розумно ... не подумав би про це
warren

Приємний момент щодо кінцевих шматочків. Я б спробував поєднати цей тест з деякими іншими зауваженнями тут.
PeterAllenWebb

3
Прекрасне рішення. Цікаво, як ти це придумав? Це досить усталений принцип чи просто щось, що ви придумали? : D
Джел Шах

3
@LarsH Не потрібно додавати 0,5, дивіться моє рішення щодо посилання на доказ.
maaartinus

2
@JerryGoyal Це залежить від компілятора та значень справ. У ідеальному компіляторі перемикач завжди принаймні такий швидкий, як інакше. Але компілятори не є ідеальними, тому краще спробувати це, як це робив Джон.
fishinear

52

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

І тоді я пам’ятаю, була ця функція, що кружляла навколо мережі з вихідного коду Quake:

float Q_rsqrt( float number )
{
  long i;
  float x2, y;
  const float threehalfs = 1.5F;

  x2 = number * 0.5F;
  y  = number;
  i  = * ( long * ) &y;  // evil floating point bit level hacking
  i  = 0x5f3759df - ( i >> 1 ); // wtf?
  y  = * ( float * ) &i;
  y  = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
  // y  = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed

  #ifndef Q3_VM
  #ifdef __linux__
    assert( !isnan(y) ); // bk010122 - FPE?
  #endif
  #endif
  return y;
}

Який в основному обчислює квадратний корінь, використовуючи функцію наближення Ньютона (не пам'ятаю точну назву).

Це повинно бути корисним і може бути навіть швидшим, це з однієї з ігор феноменального програмного забезпечення id!

Це написано на C ++, але не повинно бути надто важким повторно використовувати ту саму техніку на Java, як тільки ви зрозумієте, що:

Я спочатку знайшов його за адресою: http://www.codemaestro.com/reviews/9

Метод Ньютона пояснили у вікіпедії: http://en.wikipedia.org/wiki/Newton%27s_method

Ви можете перейти за посиланням, щоб отримати додаткові пояснення того, як це працює, але якщо вам не дуже важливо, то це приблизно те, що я пам’ятаю, читаючи блог та пройшовши курс числового аналізу:

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

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

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


9
Слід зазначити, що це повертає 1 / sqrt (число), а не sqrt (число). Я зробив кілька тестувань, і це не вдалося, починаючи з n = 410881: магічна формула Джона Кармака повертається 642.00104, коли фактичний квадратний корінь дорівнює 641.
Кіп

11
Ви можете дивитись на папір Кріса Ломонта на швидких зворотних квадратних коренях: lomont.org/Math/Papers/2003/InvSqrt.pdf Тут використовується та сама техніка, що і тут, але з іншим магічним числом. У статті пояснюється, чому було обрано магічне число.

4
Крім того, за межами3d.com/ content/ articles/8 та izvan3d.com/content/articles/15 пролито трохи світла щодо витоків цього методу. Його часто приписують Джону Кармаку, але, схоже, оригінальний код був (можливо) написаний Гері Тароллі, Грегом Уолшем та, ймовірно, іншими.

3
Крім того, ви не можете вводити floats і ints в Java.
Сурма

10
@Антиман, хто каже? FloatToIntBits та IntToFloatBits існують з ява 1.0.2.
corsiKa

38

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


10
Я вважаю, що фокус Кармака в наші дні досить безглуздий. Вбудована інструкція sqrt набагато швидша, ніж раніше, тому вам може бути краще просто виконати звичайний квадратний корінь і протестувати, якщо результат є int. Як завжди, орієнтуйте його.
jalf

4
Ця перерва починається з n = 410881, магічна формула Джона Кармака повертається 642.00104, коли фактичний квадратний корінь дорівнює 641.
Кіп

11
Нещодавно я використовував хитрість Carmack в грі на Java, і це було дуже ефективно, даючи швидкість приблизно 40%, тому вона все ще корисна, принаймні на Java.
finnw

3
@Robert Fraser Так + 40% у загальній частоті кадрів. У грі була система фізики частинок, яка займала майже всі доступні цикли процесора, домінувала функція квадратного кореня та функція "круглого до найближчого цілого" (що я також оптимізував, використовуючи аналогічний бит-подвійний злом.)
finnw

5
Посилання розірвано.
Піксар

36

Якщо ви робите двійковий відбиток, щоб спробувати знайти «правильний» квадратний корінь, ви можете досить легко визначити, чи є у вас значення достатньо близьким, щоб сказати:

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

Отже, обчисливши n^2, варіанти:

  • n^2 = target: зроблено, повернути правду
  • n^2 + 2n + 1 > target > n^2 : ти близький, але це не ідеально: повернути помилково
  • 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 ітерації.


Мої гроші на такому підході. Уникайте виклику sqrt (), оскільки він обчислює повний квадратний корінь, і вам потрібно лише перші кілька цифр.
PeterAllenWebb

3
З іншого боку, якщо плаваюча точка робиться в спеціальному підрозділі FP, можливо, вона використовує всілякі забавні трюки. Я не хотів би робити ставку на це без еталону :) (я можу спробувати сьогодні ввечері, хоча в C #, просто щоб побачити ...)
Джон Скіт

8
Програмне забезпечення на сьогоднішній день фактично досить швидко.
Адам Розенфілд

24

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

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

Нижче наведені алгоритми:

  • Інтернет - Опублікована відповідь Кіпа
  • 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і т. Д. Але, оскільки це перерахунок, це не робить ' t так важливо. Ви можете знайти файл тут (або створити його самостійно):

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

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

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

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

Я завантажую його в такий booleanмасив:

private static boolean[] goodLookupSquares = null;

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

    goodLookupSquares = new boolean[0x1FFFFFE];

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

    s.close();
}

Приклад виконання. Він бив Durron(версія перша) у кожному пробі, який я проводив.

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

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

vm: java
trial: 0

3
Гігантська таблиця пошуку не здається гарною ідеєю. Пропуск кешу проходить повільніше (~ 100-150 циклів), ніж інструкція щодо апаратної sqrt x86 (~ 20 циклів). По мірі пропускної здатності ви можете пережити багато непогашених помилок кешу, але ви все одно вилучаєте інші корисні дані. Величезна таблиця пошуку коштувала б лише того, якби вона була ЛОТИШО швидшою, ніж будь-яка інша опція, і ця функція була головним фактором в роботі всієї вашої програми.
Пітер Кордес

1
@SwissFrank: це ідеальна квадратна перевірка єдиного, що робить ваша програма? Таблиця пошуку може виглядати добре в мікротехніці, яка викликає її неодноразово в тісному циклі, але в реальній програмі, яка має інші дані в своєму робочому наборі, це не добре.
Пітер Кордес

1
Растрове зображення з 0x1FFFFFE біт займає 4 мега - байт , якщо зберігається у вигляді упакованого растрового зображення. L3 кеш - хіт на сучасному робочому столі Intel має> 40 циклів затримки, і гірше на великий Xeon; довше апаратного sqrt + затримка муль. Якщо зберігається як байт- карта з 1 байтом на значення, це приблизно 32 Мб; більший, ніж кеш L3 нічого, крім багатоядерного Xeon, де всі ядра мають один величезний кеш. Отже, якщо ваші вхідні дані мають рівномірний випадковий розподіл по досить великому діапазону входів, ви отримаєте безліч пропусків кеш-пам'яті L2 навіть у тісному циклі. (приватний L-ядро L2 для Intel складає всього 256 кб, затримка циклу ~ 12).
Пітер Кордес

1
@SwissFrank: О, якщо все, що ви робите, - це перевірка кореня, то для цього можна отримати раціональну карту, щоб отримати хіти L3. Я дивився на затримку, але багато помилок можуть бути в польоті відразу, тому пропускна здатність потенційно хороша. Пропускна sqrtpsздатність OTOH, SIMD або навіть sqrtpd(подвійна точність) не дуже погана для Skylake, але не набагато краща, ніж затримка на старих процесорах. У будь-якому випадку 7-cpu.com/cpu/Haswell.html має кілька приємних експериментальних номерів та сторінки для інших процесорів. Посібник з мікроарха Agner Fog pdf має кілька затримок кеш-пам'яті для Intel та AMD uarches: agner.org/optimize
Пітер Кордес

1
Використання x86 SIMD від Java є проблемою, і до моменту додавання витрат на перетворення int-> fp та fp-> int, правдоподібно, що растрова карта може бути кращою. Вам потрібна doubleточність, щоб уникнути округлення деякого цілого числа за межами діапазону + -2 ^ 24 (тому 32-бітове ціле число може бути поза цим), і sqrtpdце повільніше sqrtps, а також лише обробка половини більшої кількості елементів за інструкцію (на вектор SIMD) .
Пітер Кордес

18

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

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


1
Цифровий корінь суворо обчислюється еквівалентно модулю, тому його слід розглядати разом з іншими методами модуля, такими як mod 16 та mod 255.
Крістіан Оудар,

1
Ви впевнені, що цифровий корінь еквівалентний модулю? Здається, це щось зовсім інше, як пояснено посиланням. Зверніть увагу, що список становить 1,4,7,9, а не 1,4,5,9.
Fractaly

1
Цифровий корінь у десятковій системі еквівалентний використанню модуля 9 (ну dr (n) = 1 + ((n-1) mod 9); тому невеликий зсув також). Числа 0,1,4,5,9 - для модуля 16, а 0, 1, 4, 7 - для модуля 9 - які відповідають 1, 4, 7, 9 для цифрового кореня.
Ганс Ольссон

16

Я хочу, щоб ця функція працювала з усіма позитивними 64-бітовими цілими числами

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


5
Я фактично перевірив відповідь на всі ідеальні квадрати, більші за 2 ^ 53, а також на всі числа від 5 нижче кожного ідеального квадрата до 5 вище кожного ідеального квадрата, і я отримую правильний результат. (помилка округлення виправляється, коли я заокруглю відповідь sqrt на довгий, а потім квадратний, який значення і порівняти)
Kip

2
@Kip: Напевно, я довів, що це працює .
maaartinus

Результати не є абсолютно точними, але більш точними, ніж ви могли подумати. Якщо припустити щонайменше 15 точних цифр після перетворення в подвійні та після квадратного кореня, то це багато, тому що нам потрібно не більше 11: 10 цифр для 32-бітного квадратного кореня і менше 1 для десяткового місця, оскільки +0,5 раундів до найближчого.
mwfearnley

3
Math.sqrt () не зовсім точний, але цього не потрібно. У першій публікації tst є цілим числом, близьким до sqrt (N). Якщо N не є квадратом, то tst * tst! = N, незалежно від значення tst. Якщо N - ідеальний квадрат, то sqrt (N) <2 ^ 32, і поки sqrt (N) обчислюється з помилкою <0,5, ми добре.
gnasher729

13

Для запису ще один підхід полягає у використанні основного розкладання. Якщо кожен фактор розкладу є парним, то число є ідеальним квадратом. Отже, ви хочете дізнатися, чи можна число розкласти як добуток квадратів простих чисел. Звичайно, вам не потрібно отримувати таке розкладання, аби тільки переконатися, чи існує воно.

Спочатку побудуйте таблицю квадратів простих чисел, менших за 2 ^ 32. Це набагато менше, ніж таблиця всіх цілих чисел до цієї межі.

Тоді рішення буде таким:

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

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

Я здогадуюсь, що це трохи криптично. Що робити, це перевірка на кожному кроці, що квадрат простого числа ділить вхідне число. Якщо це так, то він ділить число на квадрат, наскільки це можливо, щоб видалити цей квадрат з основного розкладання. Якщо цим процесом ми дійшли до 1, то вхідне число було розкладанням квадратних простих чисел. Якщо квадрат стає більшим від самого числа, то жодним чином цей квадрат чи будь-які великі квадрати не можуть розділити його, тому число не може бути розкладанням квадратів простих чисел.

Враховуючи нині sqrt, виконаний апаратно, і необхідність обчислення простих чисел тут, я думаю, що це рішення проходить повільніше. Але це повинно дати кращі результати, ніж рішення з sqrt, яке не буде працювати над 2 ^ 54, як говорить mrzl у своїй відповіді.


1
ціле ділення повільніше, ніж FP sqrt на поточному апаратному забезпеченні. Ця ідея не має шансів. Навіть у 2008 році sqrtsdпропускна здатність Core2 становить одна на 6-58c. Її idivодин на 12-36циклів. (затримки, схожі на пропускну здатність: жодна одиниця не конвеєрна).
Пітер Кордес

sqrt не потрібно бути абсолютно точним. Ось чому ви перевіряєте шляхом цілочислового зіставлення результату і роблячи цілочисельне порівняння, щоб вирішити, чи було вхідне ціле число точним цілим sqrt.
Пітер Кордес

11

Вказувалося, що останні dцифри досконалого квадрата можуть приймати лише певні значення. Останні dцифри (в базі b) числа nє такими ж, як і решта, коли nділиться на bd, тобто. в С нотації n % pow(b, d).

Це можна узагальнити до будь-якого модуля m, тобто.n % mможна використовувати, щоб виключити якийсь відсоток чисел від ідеальних квадратів. Модуль, який ви зараз використовуєте, становить 64, що дозволяє 12, тобто. 19% залишків, як можливих квадратів. Трохи кодуючи я знайшов модуль 110880, який дозволяє лише 2016 рік, тобто. 1,8% залишків як можливих квадратів. Отже, залежно від вартості операції з модулем (тобто поділу) та пошуку таблиці проти квадратного кореня на вашій машині, використання цього модуля може бути швидшим.

До речі, якщо у Java є спосіб зберігати запакований масив бітів для таблиці пошуку, не використовуйте її. 110880 32-розрядних слів не так багато оперативної пам’яті в ці дні, і отримання машинного слова буде швидше, ніж отримання одного біта.


Приємно. Ви працювали це алгебраїчно чи методом проб і помилок? Я бачу, чому це так ефективно - багато зіткнень між ідеальними квадратами, наприклад, 333 ^ 2% 110880 == 3 ^ 2, 334 ^ 2% 110880 == 26 ^ 2, 338 ^ 2% 110880 == 58 ^ 2. .
finnw

IIRC це була груба сила, але зауважте, що 110880 = 2 ^ 5 * 3 ^ 2 * 5 * 7 * 11, що дає 6 * 3 * 2 * 2 * 2 - 1 = 143 правильних дільників.
Х'ю Аллен

Я виявив, що через обмеження пошуку 44352 працює краще, із швидкістю проходження 2,6%. Принаймні в моєму виконанні.
Fractaly

1
Розділення цілого числа ( idiv) дорівнює або гірше за вартістю FP sqrt ( sqrtsd) на поточному пристрої x86. Крім того, повністю не згоден з тим, щоб уникати біт-полів. Швидкість удару кеша буде на тонну краще з бітфілдом, а тестування трохи на бітфілді - це лише одна-дві більш прості інструкції, ніж тестування цілого байта. (Для крихітних таблиць, які вміщуються в кеші навіть як небітові поля, найкраще буде байтовий масив, а не 32-бітові вставки. X86 має однобайтовий доступ з однаковою швидкістю до 32-бітового слова.)
Пітер Кордес,

11

Ціла задача заслуговує на ціле рішення. Таким чином

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

Якщо ви не знаєте, як двійковим пошуком натуральних чисел, тому що безліч без обмежень, це легко. Ви починаєте з обчислення вашої зростаючої функції f (вище f(t) = t**2 - n) на потужності двох. Побачивши позитив, ви знайшли верхню межу. Тоді ви можете виконати стандартний двійковий пошук.


Насправді час був би хоча б O((log n)^2)тому, що множення не є постійним часом, а насправді має нижню межу O(log n), що стає очевидним при роботі з великими багатоточними числами. Але сфера цієї вікі здається 64-бітною, тому, можливо, це nbd.

10

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

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

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

Варто було б перевірити, як пропустити перший тест,

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

вплинуло на продуктивність.


2
Результати тут . Видалити перший тест погано, оскільки він вирішує більшість випадків досить дешево. Джерело є в моїй відповіді (оновлено).
maaartinus

9

Для продуктивності вам дуже часто доводиться робити якісь компроміси. Інші висловлювали різні методи, проте ви зазначили, що злом Carmack був швидшим до певних значень N. Тоді вам слід перевірити "n", і якщо воно менше, ніж це число N, скористайтеся злому Carmack, інакше використовувати якийсь інший описаний метод у відповідях тут.


Я також включив вашу пропозицію в рішення. Також приємна ручка. :)
Кіп

8

Це найшвидша реалізація Java, яку я міг придумати, використовуючи комбінацію методів, запропонованих іншими в цій темі.

  • Тест Mod-256
  • Inexact mod-3465 тест (уникає цілого поділу ціною деяких помилкових позитивів)
  • Квадратний корінь з плаваючою комою, округлий і порівняйте зі значенням введення

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

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

public class SquareTester {

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

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

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

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

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

}

7

Ви повинні позбутися 2-сильної частини N вже з самого початку.

2-е редагування Магічний вираз для m нижче має бути

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

а не так, як написано

Кінець другої редакції

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% чисел.


7

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

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

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


1
Я думав те саме, що ці алгоритми не перекладають добре на багатоточні буфери. Тому я подумав, що я дотримуюся цього тут ... Я фактично знайшов тест на ймовірнісну квадратичність з кращою асимптотичною складністю для величезних чисел ..... де додатки теорії чисел нечасто опиняються. Не знайомий з Project Euler, хоча ... виглядає цікаво.

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

Кількість ітерацій виглядає O (ln n), де n - бітова довжина v, тому я сумніваюся, що це значно заощадить для більших v. Sqrt з плаваючою точкою повільний, можливо, 100-200 циклів, але ціла математика не є безкоштовно. Десяток ітерацій по 15 циклів кожен, і це було б миттям. Все-таки +1 за цікавість.
Тадмас

Власне, я вважаю, що додавання та віднімання може здійснювати XOR.
Brent.Longborough

Це був затятий коментар - XOR може зробити лише доповнення; віднімання є арифметичним.
Brent.Longborough

1
Чи дійсно є якась істотна різниця між часом виконання XOR та додаванням в будь-якому випадку?
Тадмас

1
@Tadmas: напевно, недостатньо, щоб порушити правило "оптимізувати пізніше". (:-)
Brent.Longborough

6

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

Звичайно, замість того, щоб мати окремий тест на мінус, ви можете перевірити високі 6 біт так само.

Зауважте, що все, що я роблю, - це усунення можливих квадратів, але коли у мене є потенційний випадок, я повинен викликати оригінальний, вбудований isPerfectSquare.

Програма init2 викликається один раз для ініціалізації статичних значень pp1 та pp2. Зауважте, що для моєї реалізації в C ++ я довго не підписуюсь, тому, оскільки ви підписалися, вам доведеться використовувати оператор >>>.

Немає внутрішньої необхідності перевіряти масив, але оптимізатор Java повинен вияснити цей матеріал досить швидко, тому я не звинувачую їх у цьому.


3
Б'юсь об заклад, що ви помиляєтесь двічі. 1. Intel sqrt відповідає IEEE. Єдині невідповідні інструкції - це гоніометричні вказівки щодо аргументів на ланге. 2. Java використовує властивості для Math.sqrt, без JNI .
maaartinus

1
Ви не забули скористатися pp2? Я розумію, що pp1використовується для тестування шести найменш значущих бітів, але я не вірю, що тестування наступних шести біт має сенс.
maaartinus

6

Мені подобається ідея використовувати майже правильний метод на деякому вході. Ось версія з більшим "зміщенням". Код, здається, працює і передає мій простий тестовий випадок.

Просто замініть:

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

код з цим:

if (n < 11043908100L) {
    //John Carmack hack, converted to Java.
    // See: http://www.codemaestro.com/reviews/9
    int i;
    float x2, y;

    x2 = n * 0.5F;
    y = n;
    i = Float.floatToRawIntBits(y);
    //using the magic number from 
    //http://www.lomont.org/Math/Papers/2003/InvSqrt.pdf
    //since it more accurate
    i = 0x5f375a86 - (i >> 1);
    y = Float.intBitsToFloat(i);
    y = y * (1.5F - (x2 * y * y));
    y = y * (1.5F - (x2 * y * y)); //Newton iteration, more accurate

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

6

Враховуючи загальну довжину бітів (хоча тут я використовував конкретний тип), я спробував розробити спрощений алго як нижче. Спочатку необхідна проста і очевидна перевірка на 0,1,2 або <0. Далі просто в сенсі, що він не намагається використовувати будь-які існуючі математичні функції. Більшу частину оператора можна замінити на бітові оператори. Я ще не перевіряв жодних даних про оцінку. Я не знаю ні спеціалістів з математики, ані з комп'ютерними алгоритмами, я хотів би бачити, як ви вказували на проблему. Я знаю, що є багато шансів на покращення.

int main()
{
    unsigned int c1=0 ,c2 = 0;  
    unsigned int x = 0;  
    unsigned int p = 0;  
    int k1 = 0;  
    scanf("%d",&p);  
    if(p % 2 == 0) {  
        x = p/2; 
    }  
    else {  
        x = (p/2) +1;  
    }  
    while(x) 
    {
        if((x*x) > p) {  
            c1 = x;  
            x = x/2; 
        }else {  
            c2 = x;  
            break;  
        }  
    }  
    if((p%2) != 0)  
        c2++;

    while(c2 < c1) 
    {  
        if((c2 * c2 ) == p) {  
            k1 = 1;  
            break;  
        }  
        c2++; 
    }  
    if(k1)  
        printf("\n Perfect square for %d", c2);  
    else  
        printf("\n Not perfect but nearest to :%d :", c2);  
    return 0;  
}  

@Kip: певна проблема з моїм браузером.
nabam serbang

1
Вам потрібні відступи.
Стів Куо

5

Я перевірив усі можливі результати, коли спостерігаються останні n бітів квадрата. Послідовно вивчивши більше бітів, можна усунути до 5/6-го вводу. Я фактично створив це для реалізації алгоритму факторизації Ферма, і це дуже швидко.

public static boolean isSquare(final long val) {
   if ((val & 2) == 2 || (val & 7) == 5) {
     return false;
   }
   if ((val & 11) == 8 || (val & 31) == 20) {
     return false;
   }

   if ((val & 47) == 32 || (val & 127) == 80) {
     return false;
   }

   if ((val & 191) == 128 || (val & 511) == 320) {
     return false;
   }

   // if((val & a == b) || (val & c == d){
   //   return false;
   // }

   if (!modSq[(int) (val % modSq.length)]) {
        return false;
   }

   final long root = (long) Math.sqrt(val);
   return root * root == val;
}

Останній біт псевдокоду можна використовувати для розширення тестів для усунення більшої кількості значень. Випробування, наведені вище, призначені для k = 0, 1, 2, 3

  • a має вигляд (3 << 2k) - 1
  • b має вигляд (2 << 2k)
  • c має вигляд (2 << 2k + 2) - 1
  • d має вигляд (2 << 2k - 1) * 10

    Він спочатку перевіряє, чи має квадратний залишок з модулями потужності два, потім він тестує на основі остаточного модуля, потім використовує Math.sqrt, щоб зробити остаточний тест. Я придумав ідею з верхнього поста і спробував її поширити. Я вдячний за будь-які коментарі чи пропозиції.

    Оновлення: Використовуючи тест за модулем, (modSq) та базою модулів 44352, мій тест виконується за 96% часу того, який було оновлено в ОП, для чисел до 1 000 000 000.


  • 2

    Ось рішення для розбиття і підкорення.

    Якщо квадратний корінь натурального числа ( 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"

    2
    Я люблю цю концепцію, але хотів би ввічливо вказати на основний недолік: цифри знаходяться в базі 2 двійкових. Перетворення бази 2 в базу 10 через toString- неймовірно дорога операція порівняно з побітовими операторами. Таким чином, для задоволення мети питання - продуктивності - ви повинні використовувати побітові оператори замість базових 10 рядків. Знову ж таки, мені дуже подобається ваша концепція. Незважаючи на те, ваша реалізація (як це зараз є) є найбільш повільною з усіх можливих рішень, розміщених у питанні.
    Джек Гіффін

    1

    Якщо швидкість викликає занепокоєння, чому б не розділити найчастіше використовуваний набір входів та їх значення в таблицю пошуку, а потім виконати будь-який оптимізований магічний алгоритм, який ви створили для виняткових випадків?


    Проблема полягає в тому, що не існує "загальновживаного набору входів" - зазвичай я повторюю список, тому я не буду використовувати однакові входи двічі.
    Кіп

    1

    Має бути можливість упакувати знак 'не може бути ідеальним квадратом, якщо останні X цифри мають N' набагато ефективніше, ніж це! Я буду використовувати 32-бітові inva 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: elided за низької продуктивності в prettify.js; перегляньте історію редагувань.)


    1

    Метод Ньютона з цілою арифметикою

    Якщо ви хочете уникнути не цілих операцій, ви можете скористатися наведеним нижче способом. В основному він використовує метод Ньютона, модифікований для цілої арифметики.

    /**
     * Test if the given number is a perfect square.
     * @param n Must be greater than 0 and less
     *    than Long.MAX_VALUE.
     * @return <code>true</code> if n is a perfect
     *    square, or <code>false</code> otherwise.
     */
    public static boolean isSquare(long n)
    {
        long x1 = n;
        long x2 = 1L;
    
        while (x1 > x2)
        {
            x1 = (x1 + x2) / 2L;
            x2 = n / x1;
        }
    
        return x1 == x2 && n % x1 == 0L;
    }

    Ця реалізація не може конкурувати з рішеннями, які використовують Math.sqrt. Однак його ефективність може бути покращена за допомогою механізмів фільтрації, описаних в деяких інших постах.


    1

    Обчислення квадратних коренів методом Ньютона жахливо швидко ... за умови розумного початкового значення. Однак розумного початкового значення немає, і ми на практиці закінчуємо поведінку бісекції та журналу (2 ^ 64).
    Щоб бути дійсно швидким, нам потрібен швидкий шлях, щоб досягти розумного стартового значення, а це означає, що нам потрібно перейти до машинної мови. Якщо процесор надає таку інструкцію, як POPCNT в Pentium, що рахує провідні нулі, ми можемо використовувати це, щоб мати початкове значення з половиною значущих бітів. З обережністю ми можемо знайти фіксовану кількість кроків Ньютона, яких завжди буде достатньо. (Таким чином, вищесказана необхідність циклу та дуже швидкого виконання.)

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

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


    Отримати хорошу оцінку не надто складно. Можна використовувати число цифр числа, щоб оцінити нижню і верхню межу рішення. Дивіться також мою відповідь, де я пропоную поділ і перемогти рішення.
    MWB

    Чим відрізняється POPCNT від підрахунку кількості цифр? За винятком того, що ви можете робити POPCNT за одну наносекунд.
    Альберт ван дер Хорст

    1

    Корінь квадратного числа, враховуючи, що число є ідеальним квадратом.

    Складність журналу (n)

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

    0

    Якщо ви хочете швидкості, враховуючи, що ваші цілі числа мають кінцевий розмір, я підозрюю, що найшвидший спосіб передбачає (а) розподіл параметрів за розміром (наприклад, на категорії за найбільшим набором бітів), а потім перевірку значення на масив ідеальних квадратів в межах цього діапазону.


    2
    У діапазоні довгих є 2 ^ 32 ідеальних квадрата. Цей стіл був би величезним. Також перевага обчислення значення в порівнянні з доступом до пам'яті може бути величезною.
    PeterAllenWebb

    О, ні, немає, є 2 ^ 16. 2 ^ 32 - це 2 ^ 16 у квадраті. Є 2 ^ 16.
    Небесний М Ласка

    3
    так, але діапазон довгого - 64 біт, а не 32 біт. sqrt (2 ^ 64) = 2 ^ 32. (я ігнорую бітовий знак, щоб трохи полегшити математику ... є насправді (довгі) (2 ^ 31,5) = 3037000499 досконалі квадрати)
    Kip

    0

    Щодо методу Кармака, то, схоже, було б досить просто просто повторити ще раз, що повинно подвоїти кількість цифр точності. Зрештою, це надзвичайно усічений ітеративний метод - Ньютон, з дуже хорошим першим здогадом.

    Що стосується ваших поточних найкращих, то я бачу дві мікрооптимізації:

    • перемістіть чек проти 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;

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

    Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
    Licensed under cc by-sa 3.0 with attribution required.