A maneira mais rápida de determinar se a raiz quadrada de um número inteiro é um número inteiro


1454

Estou procurando a maneira mais rápida de determinar se um longvalor é um quadrado perfeito (ou seja, sua raiz quadrada é outro número inteiro):

  1. Fiz isso da maneira mais fácil, usando a Math.sqrt() função interna, mas estou me perguntando se existe uma maneira de fazê-lo mais rapidamente, restringindo-se ao domínio somente número inteiro.
  2. Manter uma tabela de pesquisa é impraticável (já que existem cerca de 2 31,5 inteiros cujo quadrado é menor que 2 63 ).

Aqui está a maneira muito simples e direta de fazer agora:

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

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

Nota: Estou usando esta função em muitos problemas do Project Euler . Portanto, ninguém mais terá que manter esse código. E esse tipo de micro-otimização pode realmente fazer a diferença, já que parte do desafio é executar todos os algoritmos em menos de um minuto, e essa função precisará ser chamada milhões de vezes em alguns problemas.


Eu tentei as diferentes soluções para o problema:

  • Após testes exaustivos, descobri que 0.5não é necessário adicionar ao resultado Math.sqrt (), pelo menos não na minha máquina.
  • A raiz quadrada inversa rápida foi mais rápida, mas apresentou resultados incorretos para n> = 410881. No entanto, como sugerido por BobbyShaftoe , podemos usar o hack FISR para n <410881.
  • O método de Newton era um pouco mais lento que Math.sqrt(). Provavelmente porque Math.sqrt()usa algo semelhante ao Método de Newton, mas implementado no hardware, por isso é muito mais rápido do que em Java. Além disso, o método de Newton ainda exigia o uso de duplos.
  • Um método de Newton modificado, que usava alguns truques para envolver apenas matemática inteira, exigia alguns hacks para evitar o estouro (eu quero que essa função funcione com todos os inteiros positivos assinados em 64 bits) e ainda era mais lenta que isso Math.sqrt().
  • Costeleta binária foi ainda mais lenta. Isso faz sentido, porque o chop binário exigirá, em média, 16 passagens para encontrar a raiz quadrada de um número de 64 bits.
  • De acordo com os testes de John, o uso de orinstruções é mais rápido em C ++ do que o de a switch, mas em Java e C # parece não haver diferença entre ore switch.
  • Eu também tentei fazer uma tabela de pesquisa (como uma matriz estática privada de 64 valores booleanos). Então, em vez de um switch ou uma ordeclaração, eu diria apenas if(lookup[(int)(n&0x3F)]) { test } else return false;. Para minha surpresa, isso foi (apenas um pouco) mais lento. Isso ocorre porque os limites da matriz são verificados em Java .

21
Este é o código Java, onde int == 32 bits e long == 64 bits, e ambos são assinados.
214 Kip

14
@ Shreevasta: Eu fiz alguns testes em valores grandes (maiores que 2 ^ 53), e seu método fornece alguns falsos positivos. O primeiro encontrado é para n = 9007199326062755, que não é um quadrado perfeito, mas é retornado como um.
Kip

37
Por favor, não chame isso de "John Carmack hack". Ele não veio com isso.
User9282 11/03/09

84
@mamama - Talvez, mas é atribuído a ele. Henry Ford não inventou o carro, a Wright Bros. não inventou o avião, e Galleleo não foi o primeiro a descobrir que a Terra girava em torno do sol ... o mundo é composto de invenções roubadas (e amor).
Robert Fraser

4
Você pode obter um pequeno aumento de velocidade no 'quickfail' usando algo como ((1<<(n&15))|65004) != 0, em vez de ter três verificações separadas.
Nabb

Respostas:


736

Eu descobri um método que funciona ~ 35% mais rápido que o seu código 6bits + Carmack + sqrt, pelo menos com minha CPU (x86) e linguagem de programação (C / C ++). Seus resultados podem variar, principalmente porque eu não sei como o fator Java se desenvolverá.

Minha abordagem é tríplice:

  1. Primeiro, filtre as respostas óbvias. Isso inclui números negativos e a observação dos últimos 4 bits. (Eu achei que olhar para os últimos seis não ajudou.) Também respondo sim por 0. (Ao ler o código abaixo, observe que minha entrada é int64 x.)
    if( x < 0 || (x&2) || ((x & 7) == 5) || ((x & 11) == 8) )
        return false;
    if( x == 0 )
        return true;
  2. Em seguida, verifique se é um módulo quadrado 255 = 3 * 5 * 17. Como esse é um produto de três primos distintos, apenas cerca de 1/8 dos resíduos mod 255 são quadrados. No entanto, na minha experiência, chamar o operador do módulo (%) custa mais do que o benefício que obtemos, então eu uso truques de bits envolvendo 255 = 2 ^ 8-1 para calcular o resíduo. (Para melhor ou para pior, não estou usando o truque de ler bytes individuais de uma palavra, apenas bit a bit).
    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.
    
    Para realmente verificar se o resíduo é quadrado, procuro a resposta em uma tabela pré-computada.
    if( bad255[y] )
        return false;
    // However, I just use a table of size 512
    
  3. Por fim, tente calcular a raiz quadrada usando um método semelhante ao lema de Hensel . (Não acho que seja aplicável diretamente, mas funciona com algumas modificações.) Antes de fazer isso, divido todos os poderes de 2 com uma pesquisa binária:
    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;
    Neste ponto, para que nosso número seja um quadrado, ele deve ser 1 mod 8.
    if((x & 7) != 1)
        return false;
    A estrutura básica do lema de Hensel é a seguinte. (Nota: código não testado; se não funcionar, tente t = 2 ou 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.
    A idéia é que, a cada iteração, você adicione um bit em r, a raiz quadrada "atual" de x; cada raiz quadrada é um módulo preciso de uma potência cada vez maior de 2, ou seja, t / 2. No final, r e t / 2-r serão raízes quadradas de x módulo t / 2. (Observe que se r é uma raiz quadrada de x, então também é -r. Isso é verdade, mesmo números de módulo, mas cuidado, alguns módulos podem ter mais de 2 raízes quadradas; notavelmente, isso inclui potências de 2. ) Como nossa raiz quadrada real é menor que 2 ^ 32, nesse momento podemos verificar se r ou t / 2-r são raízes quadradas reais. No meu código real, eu uso o seguinte loop modificado:
    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) );
    A aceleração aqui é obtida de três maneiras: valor inicial pré-calculado (equivalente a ~ 10 iterações do loop), saída anterior do loop e pular alguns valores t. Para a última parte, eu olho z = r - x * xe defino t como a maior potência de 2, dividindo z com um pequeno truque. Isso me permite pular valores t que não afetariam o valor de r de qualquer maneira. O valor inicial pré-calculado no meu caso escolhe o módulo 8192 de raiz quadrada "menor positivo".

Mesmo que esse código não funcione mais rápido, espero que você aproveite algumas das idéias que ele contém. Segue código completo e testado, incluindo as tabelas pré-computadas.

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
Uau! Vou tentar converter isso para Java e fazer uma comparação, bem como uma verificação de precisão nos resultados. Vou deixar você saber o que eu acho.
214 Kip

79
Uau, isso é lindo. Eu já tinha visto Hensel levantando antes (calculando raízes de polinômios modulo a prime), mas nem percebi que o lema poderia ser cuidadosamente reduzido por todo o caminho para calcular raízes quadradas de números; este é ... edificante :)
ShreevatsaR

3
@ nightcracker Isso não acontece. 9 < 0 => false, 9&2 => 0, 9&7 == 5 => false, 9&11 == 8 => false.
Primo

53
Maartinus postou uma solução 2x mais rápida (e muito mais curta) lá embaixo, um pouco mais tarde, que não parece estar recebendo muito amor.
Jason C

3
Parece que muitas vantagens de velocidade nas diferentes soluções são obtidas filtrando os quadrados óbvios. Alguém avaliou a situação de filtrar através da solução Maartinus e depois usar a função sqrt como uma função embutida?
user1914292

377

Estou muito atrasado para a festa, mas espero dar uma resposta melhor; mais curto e (supondo que minha referência esteja correta) também muito mais rápido .

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

O primeiro teste captura a maioria dos não quadrados rapidamente. Ele usa uma tabela de 64 itens compactada em um longo período, para que não haja custo de acesso ao array (verificação indireta e de limites). Para uma uniformidade aleatória long, há uma probabilidade de 81,25% de terminar aqui.

O segundo teste captura todos os números com um número ímpar de dois em sua fatoração. O método Long.numberOfTrailingZerosé muito rápido, pois é inserido no JIT em uma única instrução i86.

Depois de eliminar os zeros à direita, o terceiro teste lida com números que terminam com 011, 101 ou 111 em binário, que não são quadrados perfeitos. Ele também se importa com números negativos e também lida com 0.

O teste final volta à doublearitmética. Como doublepossui apenas mantissa de 53 bits, a conversão de longpara doubleinclui arredondamento para grandes valores. No entanto, o teste está correto (a menos que a prova esteja errada).

Tentar incorporar a ideia mod255 não teve sucesso.


3
Esse mascaramento implícito do valor da mudança é um pouco ... mal. Você tem alguma idéia de por que isso está na especificação Java?
Dfeuer

6
@ Dfeuer Acho que há duas razões: 1. Mudar por mais não faz sentido. 2. É como se o HW funcionasse e qualquer pessoa que usasse operações bit a bit estivesse interessada em desempenho, portanto, fazer qualquer outra coisa estaria errado. - O goodMaskteste faz isso, mas antes do turno certo. Então você teria que repetir, mas dessa maneira é mais simples e o AFAIK é um pouco mais rápido e igualmente bom.
Maaartinus

3
@dfeuer Para o benchmark, é importante dar a resposta o mais rápido possível, e a própria contagem de zero à direita não dá resposta; é apenas uma etapa preparatória. i86 / amd64 fazê-lo. Nenhuma idéia sobre as pequenas CPUs nos celulares, mas, na pior das hipóteses, o Java precisa gerar uma instrução AND para elas, o que certamente é mais simples do que o contrário.
Maaartinus

2
@Sebastian Um teste provavelmente melhor: if ((x & (7 | Integer.MIN_VALUE)) != 1) return x == 0;.
maaartinus

4
"Como o dobro tem apenas mantissa de 56 bits" -> eu diria que provavelmente tem um de 53 bits . Também
chux - Restabelece Monica 23/03

132

Você terá que fazer alguns testes comparativos. O melhor algoritmo dependerá da distribuição de suas entradas.

Seu algoritmo pode ser quase ideal, mas você pode fazer uma verificação rápida para descartar algumas possibilidades antes de chamar sua rotina de raiz quadrada. Por exemplo, observe o último dígito do seu número em hexadecimal, digitando "e". Os quadrados perfeitos só podem terminar em 0, 1, 4 ou 9 na base 16. Portanto, para 75% de suas entradas (supondo que elas estejam distribuídas uniformemente), você pode evitar uma chamada para a raiz quadrada em troca de alguns ajustes muito rápidos.

Kip comparou o código a seguir implementando o truque hexadecimal. Ao testar os números de 1 a 100.000.000, esse código foi executado duas vezes mais rápido que o original.

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

Quando testei o código análogo no C ++, ele realmente ficou mais lento que o original. No entanto, quando eliminei a instrução switch, o truque hexadecimal mais uma vez torna o código duas vezes mais rápido.

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

Eliminar a instrução switch teve pouco efeito no código C #.


que é muito inteligente ... não teria pensado nisso
Warren

Ponto agradável sobre os bits finais. Eu tentaria combinar esse teste com algumas das outras observações aqui.
PeterAllenWebb

3
Excelente solução. Quer saber como você conseguiu isso? É um princípio bastante estabelecido ou apenas algo que você descobriu? : D
Jeel Shah 07/12/11

3
@LarsH Não há necessidade de adicionar 0,5; veja minha solução para obter um link para a prova.
Maaartinus

2
@JerryGoyal Depende do compilador e dos valores dos casos. Em um compilador perfeito, um comutador é sempre pelo menos tão rápido quanto o resto. Mas os compiladores não são perfeitos, por isso é melhor experimentá-lo, como John fez.
fishinear próximo de 10/11

52

Eu estava pensando nos momentos horríveis que passei no curso de Análise Numérica.

E então me lembro, havia essa função circulando a rede do código-fonte do 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;
}

Que basicamente calcula uma raiz quadrada, usando a função de aproximação de Newton (não consigo lembrar o nome exato).

Deve ser utilizável e até mais rápido, é de um dos jogos fenomenais do software de identificação!

Está escrito em C ++, mas não deve ser muito difícil reutilizar a mesma técnica em Java depois de ter a idéia:

Eu o encontrei originalmente em: http://www.codemaestro.com/reviews/9

O método de Newton explicado na wikipedia: http://en.wikipedia.org/wiki/Newton%27s_method

Você pode seguir o link para obter mais explicações sobre como ele funciona, mas se você não se importa muito, é isso que eu lembro da leitura do blog e do curso de Análise Numérica:

  • a * (long*) &yé basicamente uma função rapidamente convertido-para-longa para que as operações de inteiros podem ser aplicados sobre os bytes brutos.
  • a 0x5f3759df - (i >> 1);linha é um valor inicial pré-calculado para a função de aproximação.
  • o * (float*) &iconverte o valor novamente em ponto flutuante.
  • a y = y * ( threehalfs - ( x2 * y * y ) )linha basicamente itera o valor sobre a função novamente.

A função de aproximação fornece valores mais precisos quanto mais você iterar a função sobre o resultado. No caso de Quake, uma iteração é "boa o suficiente", mas se não fosse por você ... você poderia adicionar a iteração necessária.

Isso deve ser mais rápido, pois reduz o número de operações de divisão realizadas no quadrado ingênuo de raiz para uma simples divisão por 2 (na verdade, uma * 0.5Foperação de multiplicação) e substitui-a por um número fixo de operações de multiplicação.


9
Note-se que isso retorna 1 / sqrt (número), não sqrt (número). Eu fiz alguns testes, e essa falha a partir de n = 410881: a fórmula mágica John Carmack retornos 642.00104, quando a raiz quadrada real é 641.
Kip

11
Você pode ver o artigo de Chris Lomonts sobre raízes quadradas inversas rápidas: lomont.org/Math/Papers/2003/InvSqrt.pdf Ele usa a mesma técnica que aqui, mas com um número mágico diferente. O artigo explica por que o número mágico foi escolhido.

4
Além disso, beyond3d.com/content/articles/8 e beyond3d.com/content/articles/15 lançam alguma luz sobre as origens desse método. É frequentemente atribuído a John Carmack, mas parece que o código original foi (possivelmente) escrito por Gary Tarolli, Greg Walsh e provavelmente outros.

3
Além disso, você não pode digitar floats e ints em Java.
Antimony

10
@Antimony quem diz? FloatToIntBits e IntToFloatBits existem desde o java 1.0.2.
CorsiKa

38

Não tenho certeza se seria mais rápido, ou mesmo preciso, mas você poderia usar o algoritmo Magical Square Root , de John Carmack , para resolver a raiz quadrada mais rapidamente. Provavelmente, você poderia facilmente testar isso para todos os números inteiros de 32 bits possíveis e validar se realmente obteve os resultados corretos, pois isso é apenas uma aproximação. No entanto, agora que penso nisso, o uso de duplas também se aproxima, então não tenho certeza de como isso entraria em jogo.


10
Eu acredito que o truque de Carmack é bastante inútil nos dias de hoje. A instrução sqrt integrada é muito mais rápida do que costumava ser; portanto, é melhor executar apenas uma raiz quadrada regular e testar se o resultado é um int. Como sempre, avalie-o.
jalf

4
Isto quebra a partir de n = 410881, a fórmula mágica John Carmack retornos 642.00104, quando a raiz quadrada real é 641.
Kip

11
Recentemente, usei o truque de Carmack em um jogo Java e foi muito eficaz, acelerando em torno de 40%, por isso ainda é útil, pelo menos em Java.
finnw

3
@Robert Fraser Sim + 40% na taxa de quadros geral. O jogo tinha um sistema de física de partículas que ocupava quase todos os ciclos de CPU disponíveis, dominados pela função de raiz quadrada e pela função de arredondar para o número inteiro mais próximo (que eu também
otimizei

5
O link está quebrado.
Pixar

36

Se você fizer um corte binário para tentar encontrar a raiz quadrada "certa", poderá detectar com facilidade se o valor obtido é próximo o suficiente para informar:

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

Então, tendo calculado n^2, as opções são:

  • n^2 = target: pronto, retorne verdadeiro
  • n^2 + 2n + 1 > target > n^2 : você está perto, mas não é perfeito: return false
  • n^2 - 2n + 1 < target < n^2 : idem
  • target < n^2 - 2n + 1 : costeleta binária em uma parte inferior n
  • target > n^2 + 2n + 1 : costeleta binária em uma maior n

(Desculpe, isso usa ncomo seu palpite atual e targetcomo parâmetro. Peça desculpas pela confusão!)

Não sei se isso será mais rápido ou não, mas vale a pena tentar.

EDIT: O chop binário não precisa incluir todo o intervalo de números inteiros; (2^x)^2 = 2^(2x)portanto, depois de encontrar o bit mais definido no seu destino (o que pode ser feito com um truque de manipulação de bits; esqueço exatamente como) você pode obter rapidamente uma variedade de respostas em potencial. Lembre-se, uma ingestão binária de binário ainda vai levar apenas 31 ou 32 iterações.


Meu dinheiro está nesse tipo de abordagem. Evite chamar sqrt (), pois está calculando uma raiz quadrada completa e você só precisa dos primeiros dígitos.
PeterAllenWebb

3
Por outro lado, se o ponto flutuante estiver sendo feito em uma unidade FP dedicada, ele pode estar usando todos os tipos de truques divertidos. Eu não gostaria de apostar nele sem uma referência :) (I pode experimentá-lo esta noite, embora em C #, só para ver ...)
Jon Skeet

8
Os sqrts de hardware são realmente muito rápidos atualmente.
Adam Rosenfield

24

Fiz minha própria análise de vários algoritmos neste segmento e obtive alguns novos resultados. Você pode ver esses resultados antigos no histórico de edições desta resposta, mas eles não são precisos, como cometi um erro, e perdi tempo analisando vários algoritmos que não estão próximos. No entanto, tirando lições de várias respostas diferentes, agora tenho dois algoritmos que esmagam o "vencedor" desse segmento. Aqui está a coisa principal que faço de maneira diferente de todos os outros:

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

No entanto, essa linha simples, que na maioria das vezes adiciona uma ou duas instruções muito rápidas, simplifica muito a switch-caseinstrução em uma instrução if. No entanto, isso pode aumentar o tempo de execução se muitos dos números testados tiverem um poder significativo de dois fatores.

Os algoritmos abaixo são os seguintes:

  • Internet - resposta postada de Kip
  • Durron - Minha resposta modificada usando a resposta de uma passagem como base
  • DurronTwo - Minha resposta modificada usando a resposta em duas passagens (de @JohnnyHeggheim), com algumas outras pequenas modificações.

Aqui está um exemplo de tempo de execução se os números forem gerados usando 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

E aqui está um exemplo de tempo de execução, se for executado apenas no primeiro milhão de longos:

 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

Como você pode ver, DurronTwoé melhor para entradas grandes, porque ele usa o truque de mágica com muita frequência, mas é derrotado em comparação com o primeiro algoritmo e Math.sqrtporque os números são muito menores. Enquanto isso, o mais simples Durroné um grande vencedor, porque nunca precisa se dividir por 4 muitas e muitas vezes no primeiro milhão de números.

Aqui está 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;
}

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

E meu chicote de referência: (Requer o Google caliper 0.1-rc5)

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

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


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

            return trues;   
        }

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

            return trues;   
        }

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

            return trues;   
        }
    }

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

ATUALIZAÇÃO: Criei um novo algoritmo que é mais rápido em alguns cenários, mais lento em outros, obtive benchmarks diferentes com base em entradas diferentes. Se calcularmos o módulo 0xFFFFFF = 3 x 3 x 5 x 7 x 13 x 17 x 241, podemos eliminar 97,82% dos números que não podem ser quadrados. Isso pode ser feito (mais ou menos) em uma linha, com 5 operações bit a bit:

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

O índice resultante é 1) o resíduo, 2) o resíduo + 0xFFFFFFou 3) o resíduo + 0x1FFFFFE. Obviamente, precisamos ter uma tabela de pesquisa para o módulo de resíduos 0xFFFFFF, que é sobre um arquivo de 3mb (nesse caso, armazenado como números decimais de texto ascii, não ideal, mas claramente improvável com um ByteBuffere assim por diante. t importa tanto. Você pode encontrar o arquivo aqui (ou gerar it yourself):

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

Eu carrego-o em uma booleanmatriz como esta:

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

Exemplo de tempo de execução. Ele bateu Durron(versão um) em todos os testes que eu executei.

 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
Uma tabela de pesquisa gigante não parece uma boa ideia. Uma falta de cache é mais lenta (~ 100 a 150 ciclos) do que a instrução sq86 de hardware x86 (~ 20 ciclos). Em termos de taxa de transferência, você pode manter muitas falhas de cache pendentes, mas ainda está despejando outros dados úteis. Uma enorme tabela de pesquisa só valeria a pena se fosse MUITO mais rápida do que qualquer outra opção, e essa função foi o principal fator no desempenho de todo o programa.
Peter Cordes

1
@SwissFrank: verificação perfeita é a única coisa que seu programa faz? Uma tabela de pesquisa pode ficar bem em uma marca de microbench que a chama repetidamente em um loop apertado, mas em um programa real que possui outros dados em seu conjunto de trabalho, isso não é bom.
Peter Cordes

1
Um bitmap de 0x1FFFFFE bits leva 4 mega- bytes se armazenado como um bitmap compactado. Um cache L3 atingido em um desktop Intel moderno tem> 40 ciclos de latência, e pior em um grande Xeon; mais do que o sqrt do hardware + a latência mul. Se armazenado como um mapa de bytes com 1 byte por valor, é cerca de 32 MB; maior que o cache L3 de qualquer coisa, exceto um Xeon com muitos núcleos, em que todos os núcleos compartilham um cache enorme. Portanto, se os dados de suas entradas tiverem uma distribuição aleatória uniforme em uma faixa suficientemente grande de entradas, você terá muitas falhas de cache L2, mesmo em um loop apertado. (privado L2 por núcleo em Intel é de apenas 256k, com latência ciclo de ~ 12.)
Peter Cordes

1
@SwissFrank: Ah, se tudo o que você está fazendo é verificação de raiz, existe um potencial nisso com um bitmap para obter ocorrências L3. Eu estava olhando para a latência, mas muitas falhas podem estar em andamento ao mesmo tempo, portanto a taxa de transferência é potencialmente boa. OTOH, sqrtpstaxa de transferência SIMD ou mesmo sqrtpd(precisão dupla) não são muito ruins no Skylake, mas não são muito melhores do que a latência em CPUs antigas. De qualquer forma, 7-cpu.com/cpu/Haswell.html possui alguns bons números experimentais e páginas para outras CPUs. De Agner Nevoeiro guia microarch pdf tem alguns números de latência de cache para Intel e AMD uarches: agner.org/optimize
Peter Cordes

1
Usar o x86 SIMD a partir de Java é um problema e, quando você adiciona o custo da conversão int-> fp e fp-> int, é plausível que um bitmap possa ser melhor. Você precisa de doubleprecisão para evitar arredondar algum número inteiro fora do intervalo + -2 ^ 24 (para que um número inteiro de 32 bits possa ficar fora dele) e sqrtpdé mais lento que o sqrtpsprocessamento e apenas a metade do número de elementos por instrução (por vetor SIMD) .
Peter Cordes

18

Deve ser muito mais rápido usar o método de Newton para calcular a Raiz quadrada inteira , depois quadrar esse número e verificar como você faz na sua solução atual. O método de Newton é a base da solução Carmack mencionada em algumas outras respostas. Você deve conseguir uma resposta mais rápida, pois está interessado apenas na parte inteira da raiz, permitindo interromper o algoritmo de aproximação mais cedo.

Outra otimização que você pode tentar: Se a Raiz Digital de um número não terminar em 1, 4, 7 ou 9, o número não será um quadrado perfeito. Isso pode ser usado como uma maneira rápida de eliminar 60% de suas entradas antes de aplicar o algoritmo de raiz quadrada mais lento.


1
A raiz digital é estritamente computacionalmente equivalente ao módulo, por isso deve ser considerado juntamente com outros métodos de módulo aqui, como mod 16 e mod 255.
Christian Oudard

1
Você tem certeza de que a raiz digital é equivalente ao módulo? Parece ser algo totalmente diferente, conforme explicado no link. Observe que a lista é 1,4,7,9 e não 1,4,5,9.
Fractaly 04/12/11

1
A raiz digital no sistema decimal é equivalente ao uso do módulo 9 (poço dr (n) = 1 + ((n-1) mod 9); portanto, uma pequena mudança também). Os números 0,1,4,5,9 são para o módulo 16 e 0, 1, 4, 7 são para o módulo 9 - que corresponde a 1, 4, 7, 9 para a raiz digital.
Hans Olsson

16

Quero que esta função funcione com todos os números inteiros positivos de 64 bits

Math.sqrt()funciona com dobras como parâmetros de entrada, para que você não obtenha resultados precisos para números inteiros maiores que 2 ^ 53 .


5
Na verdade, testei a resposta em todos os quadrados perfeitos maiores que 2 ^ 53, bem como em todos os números de 5 abaixo de cada quadrado perfeito a 5 acima de cada quadrado perfeito, e obtenho o resultado correto. (o erro de arredondamento é corrigida quando eu arredondar a resposta sqrt a um tempo, então praça que valor e comparar)
Kip

2
@Kip: Acho que já provei que funciona .
maaartinus 08/09

Os resultados não são perfeitamente precisos, mas mais precisos do que você imagina. Se assumirmos pelo menos 15 dígitos precisos após a conversão para o dobro e depois da raiz quadrada, isso é suficiente, porque não precisamos mais do que 11: 10 dígitos para a raiz quadrada de 32 bits e menos de 1 para uma casa decimal, porque o +0,5 arredonda para o mais próximo.
precisa saber é o seguinte

3
Math.sqrt () não é totalmente preciso, mas não precisa. No primeiro post, tst é um número inteiro próximo a sqrt (N). Se N não for um quadrado, então tst * tst! = N, não importa qual seja o valor de tst. Se N for um quadrado perfeito, então sqrt (N) <2 ^ 32, e enquanto sqrt (N) for calculado com um erro <0,5, estamos bem.
Gnasher729

13

Apenas para constar, outra abordagem é usar a decomposição principal. Se todos os fatores da decomposição forem pares, o número será um quadrado perfeito. Então, o que você quer é ver se um número pode ser decomposto como um produto de quadrados de números primos. Obviamente, você não precisa obter essa decomposição, apenas para ver se ela existe.

Primeiro construa uma tabela de quadrados de números primos menores que 2 ^ 32. Isso é muito menor que uma tabela de todos os números inteiros até esse limite.

Uma solução seria assim:

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

Eu acho que é um pouco enigmático. O que ele faz é verificar a cada passo que o quadrado de um número primo divide o número de entrada. Se isso acontecer, ele divide o número pelo quadrado o máximo possível, para remover esse quadrado da decomposição principal. Se, por esse processo, chegamos a 1, o número de entrada foi uma decomposição do quadrado dos números primos. Se o quadrado se tornar maior que o número em si, não há como esse quadrado, ou qualquer quadrado maior, poder dividi-lo; portanto, o número não pode ser uma decomposição de quadrados de números primos.

Dado o sqrt de hoje em dia feito em hardware e a necessidade de calcular números primos aqui, acho que essa solução é muito mais lenta. Mas deve dar melhores resultados do que a solução com sqrt, que não funcionará acima de 2 ^ 54, como diz mrzl em sua resposta.


1
A divisão inteira é mais lenta que o FP sqrt no hardware atual. Essa ideia não tem chance. >. <Mesmo em 2008, o sqrtsdrendimento do Core2 é de um por 6-58c. É idivum por 12-36 motos. (latências semelhantes às taxas de transferência: nenhuma unidade é canalizada).
22615 Peter Cordes

O sqrt não precisa ser perfeitamente preciso. É por isso que você verifica ao quadrado o número inteiro e fazendo uma comparação de números inteiros para decidir se o número inteiro de entrada tinha um número inteiro exato sqrt.
22615 Peter Cordes

11

Tem sido apontado que os últimos ddígitos de um quadrado perfeito só podem assumir certos valores. Os últimos ddígitos (na base b) de um número nsão os mesmos que o restante quando né dividido por bd, ie. em notação C n % pow(b, d).

Isso pode ser generalizado para qualquer módulo m, ie. n % mpode ser usado para excluir uma porcentagem de números de quadrados perfeitos. O módulo que você está usando atualmente é 64, o que permite 12, ou seja. 19% dos restantes, como quadrados possíveis. Com um pouco de codificação, encontrei o módulo 110880, que permite apenas 2016, ou seja. 1,8% dos restantes como quadrados possíveis. Portanto, dependendo do custo de uma operação de módulo (por exemplo, divisão) e de uma pesquisa de tabela versus uma raiz quadrada em sua máquina, o uso desse módulo pode ser mais rápido.

A propósito, se o Java tem uma maneira de armazenar uma matriz compactada de bits para a tabela de pesquisa, não a utilize. 110880 Palavras de 32 bits não são muita RAM hoje em dia e buscar uma palavra de máquina será mais rápida do que buscar um único bit.


Agradável. Você resolveu isso algebricamente ou por tentativa e erro? Eu posso ver por que é tão eficaz - muitas colisões entre quadrados perfeitos, por exemplo, 333 ^ 2% 110880 == 3 ^ 2, 334 ^ 2% 110880 == 26 ^ 2, 338 ^ 2% 110880 == 58 ^ 2 .. .
finnw

IIRC foi força bruta, mas observe que 110880 = 2 ^ 5 * 3 ^ 2 * 5 * 7 * 11, o que fornece 6 * 3 * 2 * 2 * 2 - 2 = 1 = 143 divisores adequados.
Hugh Allen

Descobri que, devido às limitações da pesquisa, o 44352 funciona melhor, com uma taxa de aprovação de 2,6%. Pelo menos na minha implementação.
Fractaly 04/12/11

1
A divisão inteira ( idiv) é igual ou pior em custo ao FP sqrt ( sqrtsd) no hardware x86 atual. Além disso, discordo completamente de evitar campos de bits. A taxa de acertos do cache será muito melhor com um campo de bits, e testar um pouco em um campo de bits é apenas uma ou duas instruções mais simples do que testar um byte inteiro. (Para pequenas mesas que se encaixam no cache, mesmo quando não bitfields, um array de bytes seria melhor, e não de 32 bits ints x86 tem acesso de byte único com igual velocidade para dword de 32 bits..)
Peter Cordes

11

Um problema inteiro merece uma solução inteira. portanto

Faça uma pesquisa binária nos números inteiros (não negativos) para encontrar o maior número inteiro t tal que t**2 <= n. Em seguida, teste se r**2 = nexatamente. Isso leva tempo O (log n).

Se você não souber pesquisar binariamente os números inteiros positivos porque o conjunto é ilimitado, é fácil. Você começa calculando sua função crescente f (acima f(t) = t**2 - n) com potências de dois. Quando você vê que fica positivo, você encontra um limite superior. Então você pode fazer a pesquisa binária padrão.


Na verdade, o tempo seria pelo menos O((log n)^2)porque a multiplicação não é constante, mas na verdade tem um limite inferior O(log n), o que se torna aparente ao trabalhar com grandes números de multi-precisão. Mas o escopo deste wiki parece ser de 64 bits, então talvez seja nbd.

10

A seguinte simplificação da solução de maaartinus parece reduzir alguns pontos percentuais do tempo de execução, mas não sou bom o suficiente em benchmarking para produzir um benchmark em que possa confiar:

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

Vale a pena verificar como omitir o primeiro teste,

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

afetaria o desempenho.


2
Os resultados estão aqui . A remoção do primeiro teste é ruim, pois resolve a maioria dos casos de maneira bem barata. A fonte está na minha resposta (atualizada).
Maaartinus

9

Para desempenho, muitas vezes você precisa fazer alguns compromissos. Outros expressaram vários métodos, no entanto, você notou que o hack de Carmack era mais rápido até certos valores de N. Então, você deve verificar o "n" e, se for menor que esse número N, use o hack de Carmack, caso contrário, use outro método descrito nas respostas aqui.


Também incorporei sua sugestão na solução. Além disso, boa alça. :)
Kip

8

Esta é a implementação Java mais rápida que eu pude criar, usando uma combinação de técnicas sugeridas por outras pessoas neste segmento.

  • Teste Mod-256
  • Teste mod-3465 inexato (evita divisão inteira ao custo de alguns falsos positivos)
  • Raiz quadrada de ponto flutuante, arredondar e comparar com o valor de entrada

Também experimentei essas modificações, mas elas não ajudaram no desempenho:

  • Teste adicional mod-255
  • Dividindo o valor de entrada por potências de 4
  • Raiz quadrada inversa rápida (para trabalhar com altos valores de N, são necessárias 3 iterações, o suficiente para torná-la mais lenta que a função de raiz quadrada do hardware.)

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

Você deve se livrar da parte de 2 potências de N desde o início.

2ª Edição A expressão mágica para m abaixo deve ser

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

e não como escrito

Fim da 2ª edição

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

1ª Edição:

Pequena melhoria:

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

Fim da 1ª edição

Agora continue como de costume. Dessa forma, no momento em que você chega à parte do ponto flutuante, você já se livra de todos os números cuja parte de 2 potências é ímpar (cerca da metade) e então considera apenas 1/8 do que resta. Ou seja, você executa a parte do ponto flutuante em 6% dos números.


7

O Projeto Euler é mencionado nas tags e muitos dos problemas nele requerem verificação de números >> 2^64. A maioria das otimizações mencionadas acima não funciona facilmente quando você está trabalhando com um buffer de 80 bytes.

Eu usei o java BigInteger e uma versão ligeiramente modificada do método de Newton, que funciona melhor com números inteiros. O problema era que os quadrados exatos n^2convergiam para em (n-1)vez de nporque n^2-1 = (n-1)(n+1)e o erro final estava apenas um passo abaixo do divisor final e o algoritmo foi encerrado. Foi fácil corrigir isso adicionando um ao argumento original antes de calcular o erro. (Adicione dois para raízes de cubo, etc.)

Um bom atributo desse algoritmo é que você pode dizer imediatamente se o número é um quadrado perfeito - o erro final (não a correção) no método de Newton será zero. Uma modificação simples também permite calcular rapidamente em floor(sqrt(x))vez do número inteiro mais próximo. Isso é útil com vários problemas de Euler.


1
Eu estava pensando a mesma coisa sobre esses algoritmos que não se traduzem bem em buffers de precisão múltipla. Então pensei em colocar isso aqui ... Na verdade, encontrei um teste probabilístico de quadrado com melhor complexidade assintótica para grandes números ..... onde aplicações da teoria dos números não se encontram de maneira incomum. Não estou familiarizado com o Projeto Euler ... parece interessante.

6

Isso é um retrabalho de decimal para binário do antigo algoritmo da calculadora Marchant (desculpe, não tenho uma referência), em Ruby, adaptado especificamente para esta pergunta:

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

Aqui está uma descrição de algo semelhante (por favor, não vote em mim por estilo de codificação / cheiros ou O / O desajeitado - é o algoritmo que conta, e C ++ não é minha língua materna). Nesse caso, estamos procurando por resíduo == 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 número de iterações parece O (ln n), onde n é o comprimento de bits de v, então duvido que isso economize muito para v maior. O sqrt de ponto flutuante é lento, talvez 100-200 ciclos, mas a matemática inteira não é livre também. Uma dúzia de iterações com 15 ciclos cada, e seria uma lavagem. Ainda assim, +1 por ser interessante.
Tadmas

Na verdade, acredito que as adições e subtrações podem ser feitas pelo XOR.
Brent.Longborough

Esse foi um comentário idiota - apenas a adição pode ser feita por um XOR; a subtração é aritmética.
Brent.Longborough

1
Existe realmente alguma diferença substancial entre o tempo de execução do XOR e a adição?
Tadmas 01/02/09

1
@ Tadmas: provavelmente não o suficiente para quebrar a regra "otimizar mais tarde". (:-)
Brent.Longborough

6

A chamada sqrt não é perfeitamente precisa, como foi mencionado, mas é interessante e instrutivo que ela não exagere nas outras respostas em termos de velocidade. Afinal, a sequência de instruções em linguagem assembly para um sqrt é pequena. A Intel tem uma instrução de hardware, que não é usada pelo Java, acredito, porque não está em conformidade com o IEEE.

Então, por que é lento? Como o Java está realmente chamando uma rotina C por meio da JNI, é mais lento fazê-lo do que chamar uma sub-rotina Java, que é mais lenta do que fazê-la em linha. Isso é muito chato, e o Java deveria ter encontrado uma solução melhor, ou seja, criar chamadas de biblioteca de ponto flutuante, se necessário. Ah bem.

No C ++, suspeito que todas as alternativas complexas perderiam velocidade, mas não as verifiquei todas. O que eu fiz e o que o pessoal de Java achará útil é um hack simples, uma extensão do teste de caso especial sugerido por A. Rex. Use um único valor longo como uma matriz de bits, que não seja verificada nos limites. Dessa forma, você tem uma pesquisa booleana de 64 bits.

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

A rotina isPerfectSquare5 é executada em cerca de 1/3 do tempo na minha máquina core2 duo. Eu suspeito que novos ajustes nas mesmas linhas poderiam reduzir o tempo ainda mais, em média, mas toda vez que você verifica, você está trocando mais testes por mais eliminações, para que você não possa ir muito mais longe nessa estrada.

Certamente, em vez de ter um teste separado para negativo, você pode verificar os 6 bits mais altos da mesma maneira.

Observe que tudo o que estou fazendo é eliminar possíveis quadrados, mas quando tenho um caso em potencial, preciso chamar o original, inPerfectSquare.

A rotina init2 é chamada uma vez para inicializar os valores estáticos de pp1 e pp2. Observe que, na minha implementação em C ++, estou usando a assinatura não assinada por muito tempo, portanto, como você foi assinado, você teria que usar o operador >>>.

Não há necessidade intrínseca de limitar a verificação da matriz, mas o otimizador de Java precisa descobrir essas coisas rapidamente, para que eu não as culpe por isso.


3
Aposto que você está errado duas vezes. 1. O sqrt da Intel está em conformidade com o IEEE. As únicas instruções não conformes são as instruções goniométricas para argumentos de variação. 2. Java usa intrínsecas para Math.sqrt, sem JNI .
maaartinus 08/09

1
Você não se esqueceu de usar pp2? Entendo que pp1é usado para testar os seis bits menos significativos, mas não acredito que testar os próximos seis bits faça algum sentido.
Maaartinus

6

Eu gosto da ideia de usar um método quase correto em algumas das entradas. Aqui está uma versão com um "deslocamento" mais alto. O código parece funcionar e passa no meu caso de teste simples.

Basta substituir o seu:

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

código com este:

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

Considerando o tamanho geral dos bits (embora eu tenha usado um tipo específico aqui), tentei projetar algo simplista como abaixo. A verificação simples e óbvia de 0,1,2 ou <0 é necessária inicialmente. A seguir, é simples, no sentido de que ele não tenta usar nenhuma função matemática existente. A maior parte do operador pode ser substituída por operadores bit a bit. Ainda não testei com nenhum dado de benchmark. Eu não sou especialista em matemática ou design de algoritmos de computador em particular, eu adoraria vê-lo apontando um problema. Eu sei que há muitas chances de melhoria lá.

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: Algum problema com o meu navegador.
Nabam serbang

1
Você precisa de um pouco de recuo.
Steve Kuo

5

Eu verifiquei todos os resultados possíveis quando os últimos n bits de um quadrado são observados. Ao examinar sucessivamente mais bits, até 5/6 das entradas podem ser eliminadas. Na verdade, eu projetei isso para implementar o algoritmo de fatoração de Fermat, e é muito rápido lá.

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

O último bit de pseudocódigo pode ser usado para estender os testes e eliminar mais valores. Os testes acima são para k = 0, 1, 2, 3

  • a é da forma (3 << 2k) - 1
  • b é da forma (2 << 2k)
  • c é da forma (2 << 2k + 2) - 1
  • d é da forma (2 << 2k - 1) * 10

    Primeiro ele testa se tem um resíduo quadrado com módulos de potência de dois, depois testa com base em um módulo final e depois usa o Math.sqrt para fazer um teste final. Eu tive a ideia do post principal e tentei estendê-la. Agradeço quaisquer comentários ou sugestões.

    Atualização: Usando o teste por um módulo (modSq) e uma base de módulo 44352, meu teste é executado em 96% do tempo daquele na atualização do OP para números de até 1.000.000.000.


  • 2

    Aqui está uma solução de dividir e conquistar.

    Se a raiz quadrada de um número natural ( number) for um número natural ( solution), você poderá determinar facilmente um intervalo com solutionbase no número de dígitos de number:

    • numbertem 1 dígito: solutionno intervalo = 1 - 4
    • numbertem 2 dígitos: solutionno intervalo = 3 - 10
    • numbertem 3 dígitos: solutionno intervalo = 10 - 40
    • numbertem 4 dígitos: solutionno intervalo = 30 - 100
    • numbertem 5 dígitos: solutionno intervalo = 100 - 400

    Observe a repetição?

    Você pode usar esse intervalo em uma abordagem de pesquisa binária para verificar se existe um solutionpara o qual:

    number == solution * solution

    Aqui está o código

    Aqui está a minha classe 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;
        }
    
    }

    E aqui está um exemplo de como usá-lo.

    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
    Adoro o conceito, mas gostaria de apontar educadamente uma falha importante: os números estão na base 2 binária. A conversão da base 2 para a base 10 toStringé uma operação incrivelmente cara em comparação com os operadores bit a bit. Portanto, para satisfazer o objetivo da pergunta - desempenho - você deve usar operadores bit a bit em vez de 10 strings de base. Mais uma vez, eu realmente gosto do seu conceito. Não obstante, sua implementação (como está agora) é de longe a mais lenta dentre todas as soluções possíveis postadas para a pergunta.
    Jack Giffin 23/01

    1

    Se a velocidade é uma preocupação, por que não particionar o conjunto mais comum de entradas e seus valores em uma tabela de pesquisa e, em seguida, fazer o algoritmo mágico otimizado que você criar para casos excepcionais?


    O problema é que não existe um "conjunto de entradas comumente usado" - normalmente estou percorrendo uma lista, portanto não utilizarei as mesmas entradas duas vezes.
    Kip

    1

    Deveria ser possível empacotar o 'não pode ser um quadrado perfeito se os últimos dígitos do X forem N' com muito mais eficiência do que isso! Usarei ints de 32 bits em java e produzirei dados suficientes para verificar os últimos 16 bits do número - são 2048 valores int hexadecimais.

    ...

    Está bem. Ou eu me deparei com uma teoria dos números que está um pouco além de mim ou há um erro no meu código. De qualquer forma, aqui está o código:

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

    e aqui estão os resultados:

    (ed: elided por fraco desempenho em prettify.js; veja o histórico de revisões para ver.)


    1

    Método de Newton com aritmética inteira

    Se você deseja evitar operações não inteiras, use o método abaixo. Ele basicamente usa o Método de Newton modificado para aritmética inteira.

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

    Esta implementação não pode competir com soluções que usam Math.sqrt. No entanto, seu desempenho pode ser aprimorado usando os mecanismos de filtragem descritos em algumas das outras postagens.


    1

    Calcular raízes quadradas pelo método de Newton é terrivelmente rápido ... desde que o valor inicial seja razoável. No entanto, não existe um valor inicial razoável e, na prática, terminamos com o comportamento de bissecção e log (2 ^ 64).
    Para ser realmente rápido, precisamos de uma maneira rápida de obter um valor inicial razoável, e isso significa que precisamos descer para a linguagem de máquina. Se um processador fornece uma instrução como POPCNT no Pentium, que conta os zeros iniciais, podemos usar isso para ter um valor inicial com metade dos bits significativos. Com cuidado, podemos encontrar um número fixo de etapas de Newton que sempre serão suficientes. (Por isso, precedendo a necessidade de fazer um loop e ter uma execução muito rápida.)

    Uma segunda solução está passando pelo recurso de ponto flutuante, que pode ter um cálculo rápido de sqrt (como o coprocessador i87). Mesmo uma excursão via exp () e log () pode ser mais rápida do que Newton degenerou em uma pesquisa binária. Há um aspecto complicado nisso: uma análise dependente do processador do que e se é necessário o refinamento posterior.

    Uma terceira solução resolve um problema ligeiramente diferente, mas vale a pena mencionar porque a situação é descrita na pergunta. Se você deseja calcular muitas raízes quadradas para números que diferem um pouco, você pode usar a iteração de Newton, se nunca reinicializar o valor inicial, mas apenas deixá-lo onde o cálculo anterior parou. Eu usei isso com sucesso em pelo menos um problema de Euler.


    Obter uma boa estimativa não é muito difícil. Você pode usar o número de dígitos do número para estimar um limite inferior e superior para a solução. Veja também minha resposta, onde proponho uma solução de dividir e conquistar.
    MWB

    Qual é a diferença entre POPCNT e contando o número de dígitos? Exceto que você pode executar POPCNT em um nanossegundo.
    Albert van der Horst

    1

    Raiz quadrada de um número, dado que o número é um quadrado perfeito.

    A complexidade é log (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

    Se você deseja velocidade, considerando que seus números inteiros são de tamanho finito, suspeito que a maneira mais rápida envolveria (a) particionar os parâmetros por tamanho (por exemplo, em categorias pelo maior conjunto de bits) e depois verificar o valor em uma matriz de quadrados perfeitos dentro desse intervalo.


    2
    Existem 2 ^ 32 quadrados perfeitos no intervalo de um longo. Essa mesa seria enorme. Além disso, a vantagem de calcular o valor em um acesso à memória pode ser enorme.
    PeterAllenWebb

    Oh não, não existem, existem 2 ^ 16. 2 ^ 32 é 2 ^ 16 ao quadrado. Existem 2 ^ 16.
    Celestial M Weasel

    3
    sim, mas o alcance de um comprimento é de 64 bits, não de 32 bits. sqrt (2 ^ 64) = 2 ^ 32. (i estou ignorando o bit de sinal para fazer a matemática um pouco mais fácil ... há realmente (longas) (2 ^ 31,5) = 3037000499 quadrados perfeitos)
    Kip

    0

    Com relação ao método Carmac, parece que seria bastante fácil iterar mais uma vez, o que deve dobrar o número de dígitos de precisão. Afinal, é um método iterativo extremamente truncado - o de Newton, com um bom primeiro palpite.

    Em relação ao seu melhor atual, vejo duas micro-otimizações:

    • mova o cheque vs. 0 após o cheque usando mod255
    • reorganize as potências divisórias de quatro para pular todas as verificações do caso usual (75%).

    Ou seja:

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

    Melhor ainda pode ser um simples

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

    Obviamente, seria interessante saber quantos números são selecionados em cada ponto de verificação - duvido que os cheques sejam verdadeiramente independentes, o que torna as coisas complicadas.

    Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
    Licensed under cc by-sa 3.0 with attribution required.