Я придумал метод, который работает на ~ 35% быстрее, чем ваш 6-битный код + Carmack + sqrt, по крайней мере, с моим процессором (x86) и языком программирования (C / C ++). Ваши результаты могут отличаться, особенно потому, что я не знаю, как будет действовать фактор Java.
Мой подход состоит из трех частей:
- Во-первых, отфильтруйте очевидные ответы. Это включает отрицательные числа и последние 4 бита. (Я обнаружил, что просмотр последних шести не помог.) Я также отвечаю «да» на 0. (Читая приведенный ниже код, обратите внимание, что я ввел
int64 x
.)
if( x < 0 || (x&2) || ((x & 7) == 5) || ((x & 11) == 8) )
return false;
if( x == 0 )
return true;
- Затем проверьте, является ли это квадратом по модулю 255 = 3 * 5 * 17. Поскольку это произведение трех различных простых чисел, только около 1/8 остатков по модулю 255 являются квадратами. Однако, по моему опыту, вызов оператора по модулю (%) стоит дороже, чем получаемая выгода, поэтому я использую битовые трюки с 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
- Наконец, попробуйте вычислить квадратный корень, используя метод, аналогичный лемме Хензеля. (Я не думаю, что это применимо напрямую, но оно работает с некоторыми изменениями.) Перед тем, как сделать это, я делю все степени двойки с помощью двоичного поиска:
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 по модулю t / 2. (Обратите внимание, что если r является квадратным корнем из x, то также и -r. Это верно даже по модулю чисел, но будьте осторожны, по модулю некоторых чисел вещи могут иметь даже более двух квадратных корней; в частности, это включает степени двойки. ) Поскольку наш фактический квадратный корень меньше 2 ^ 32, в этот момент мы можем просто проверить, являются ли r или t / 2-r действительными квадратными корнями. В моем фактическом коде я использую следующий модифицированный цикл:
int64 r, t, z;
r = start[(x >> 3) & 1023];
do {
z = x - r * r;
if( z == 0 )
return true;
if( z < 0 )
return false;
t = z & (-z);
r += (z & t) >> 1;
if( r > (t >> 1) )
r = t - r;
} while( t <= (1LL << 33) );
Ускорение здесь достигается тремя способами: предварительно вычисленное начальное значение (эквивалентное ~ 10 итерациям цикла), более ранний выход из цикла и пропуск некоторых значений t. В последней части я смотрю на z = r - x * x
и устанавливаю t как наибольшую степень двойки, делящую z, с помощью небольшого трюка. Это позволяет мне пропустить значения 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;
}
Поскольку Integer и Long на самом деле не имеют определенной длины (в большинстве языков C-ish, как выглядит ваш код), лучше сказать, что для 32-битного целого числа есть 2 ** 16 идеальных квадратов .
Это код Java, где int == 32 бита и long == 64 бита, и оба подписаны.
Что быстрее: «long tst = (long) Math.sqrt (n); return tst * tst == n;» (что у вас есть) или «double tst = Math.sqrt (n); return tst == (double) Math.round (tst);» ?
Какую JVM вы использовали для тестирования? По моему опыту, производительность алгоритма зависит от JVM.
@Shreevasta: Я провел несколько тестов на больших значениях (больше 2 ^ 53), и ваш метод дает несколько ложных срабатываний. Первый встречается для n = 9007199326062755, который не является точным квадратом, но возвращается как единица.
В вашем коде есть ошибка: sqrt = (long) Math.sqrt (n); вернуть sqrt * sqrt == n; Math.sqrt (n) возвращает представление с плавающей запятой, например Math.sqrt (9) может вернуть 2.99999999999, если вам не повезло, и когда вы разыграете его с (long), вы легко можете получить слишком маленькое число.
Вы можете использовать битовые уловки, чтобы проверить последние 6 бит: if ((x & 2) || ((x & 7) == 5) || ((x & 11) == 8) || ((x & 31) = = 20) || ((x & 47) == 24)) return false;
Пожалуйста, не называйте это «взломом Джона Кармака». Он этого не придумал.
@martinus: для значений ниже 2 ^ 53 двойное представление является точным, поэтому ошибки округления не будет. Я также проверил каждый идеальный квадрат больше 2 ^ 53, а также +/- 1 от каждого идеального квадрата, и ошибка округления никогда не приводит к неправильному ответу.
Я считаю, что современные JVM могут пропускать проверки индекса массива в заданном месте, если можно будет сделать вывод, что они всегда будут там действительны. С какой JVM проводилось тестирование?
«Взлом Джона Кармака» должен быть применим для большего диапазона, если нужно выполнить дополнительную итерацию, закомментированную в исходном коде Quake (т.е. число достаточно велико).
@ Thorbjørn Ravn Andersen: я использовал j2se 6.0 windows jvm, скачанный с сайта sun. Я также попытался раскомментировать дополнительную итерацию, и IIRC это действительно стало как-то менее точным.
Сделайте свой quickfail быстрее и сделайте // Quickfail if (n <0 || ((n & 2)! = 0) || ((n & 7) == 5) || ((n & 11) == 8)) return ложный; если (n == 0) вернуть истину; наоборот: // Quickfail if (n == 0) return true; if (n <0 || ((n & 2)! = 0) || ((n & 7) == 5) || ((n & 11) == 8)) return false;
@dstibbe: это будет быстрее, только если вход равен 0. для 75% других входов (даже не считая отрицательных чисел) он будет быстрее, чем он записан, а для остальных 25% разницы не будет.
@mamama - Возможно, но это ему приписывают. Генри Форд не изобрел машину, братья Райт не изобрели самолет, и Галлелео не был первым, кто выяснил, что Земля вращается вокруг Солнца ... мир состоит из украденных изобретений (и любовь).
@Kip, микробенчмарк для алгоритма может быть отключен из-за относительно большой таблицы. Когда вся таблица находится в кеше (жесткие циклы), промахов в кеше не будет. Логическое значение [] следует заменить длинными константами (или массивом), и оно получит больше.
Вы можете получить небольшое увеличение скорости в «quickfail», используя что-то вроде
((1<<(n&15))|65004) != 0
вместо трех отдельных проверок.Почему вы добавляете 0,5?
@KorayTugay, чтобы округлить число. Меня беспокоило то, что Math.sqrt может вернуть значение, которое немного отличается из-за ошибки округления. Скажем, если
math.sqrt(100)
вернул9.999999999999999
вместо10
. Однако я не уверен, есть ли случаи, когда это действительно происходит.@Kip ознакомьтесь с моим ответом, я получил значительный прирост производительности по вашему опубликованному ответу.
@Kip Я отредактировал свой пост, чтобы у него был третий алгоритм, который иногда работает лучше, чем мой предыдущий ответ.
Удивленный тем, что не увидел такого популярного вопроса, кто-то указывал, что вы можете быть точными на более высоком уровне
n
, используя так называемый «взлом Кармака», выполнив еще одну итерацию (итерации). N.B. Результат не Кармака, а Ньютона / Рафсона, я полагаю, что взлом «магического числа» был бы более справедливым приписыванием Кармаку.Не уверен, что это то, что вам нужно, но это занимает около 1 миллисекунды. (JavaScript, а не Java.)
return math.sqrt(x).split(".").length > 1
Я опаздываю на вечеринку, но я считаю, что вы можете выжать еще больше производительности из своего окончательного фрагмента кода, используя следующие методы: coderhelper.com/questions/15621083/…
@ user3932000 Я сделал это, потому что это может привести к небольшой оптимизации компилятора. Вот обсуждение получше: coderhelper.com/questions/5547663/…
Я никогда не сталкивался с проблемой PE, которую нельзя было бы вычислить менее чем за минуту в ruby . Так что аргумент о том, что производительность имеет значение для этого, немного невероятен.
Мне любопытно, как эта мудрость сохраняется перед лицом таких инноваций, как SSE SQRTPS? felixcloutier.com/x86/SQRTPS.html,
Каков пример проблемы, в которой это имеет значение? Вместо того, чтобы вычислять отдельные квадратные корни для длинного списка, гораздо лучше пройтись по списку полных квадратов и таким образом отфильтровать список.
Для очень больших чисел существует быстрый рандомизированный алгоритм: petr-mitrichev.blogspot.com/2017/12/a-quadratic-week.html
@RobertFraser Галилео Галилей не был первым, кто искалечил свое имя.
@RobertFraser: Да, и это великая несправедливость, с которой вы ничего не можете поделать. Это никогда не способ оправдать свое плохое поведение.
Я бы предложил использовать алгоритм целочисленного квадратного корня coderhelper.com/questions/1100090
Итак, когда вы говорите «быстрее», каков базовый уровень? Насколько быстро у вас версия?
@RobertFraser Я никогда не слышал, чтобы кто-нибудь утверждал, что Галилей изобрел гелиоцентрическую модель. Его всегда приписывают Копернику. Вы это имели в виду?