Bir tamsayının karekökünün bir tam sayı olup olmadığını belirlemenin en hızlı yolu


1454

Bir longdeğerin mükemmel bir kare olup olmadığını belirlemek için en hızlı yolu arıyorum (yani onun kare kökü başka bir tamsayı):

  1. Yerleşik Math.sqrt() işlevi kullanarak bunu kolay bir şekilde yaptım , ancak kendinizi yalnızca tamsayı alanıyla kısıtlayarak daha hızlı yapmanın bir yolu olup olmadığını merak ediyorum.
  2. Bir arama tablosunu korumak pratik değildir (çünkü karesi 2 63'ten küçük olan yaklaşık 2 31.5 tamsayı vardır ).

İşte şimdi bunu yapmanın çok basit ve anlaşılır yolu:

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

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

Not: Bu işlevi birçok Project Euler probleminde kullanıyorum. Bu yüzden hiç kimse bu kodu korumak zorunda kalmayacak. Ve bu tür bir mikro optimizasyon aslında bir fark yaratabilir, çünkü zorluğun bir kısmı her algoritmayı bir dakikadan daha kısa sürede yapmaktır ve bu fonksiyonun bazı problemlerde milyonlarca kez çağrılması gerekecektir.


Soruna farklı çözümler denedim:

  • Kapsamlı testlerden sonra 0.5, en azından makinemde Math.sqrt () sonucuna eklemenin gerekli olmadığını gördüm.
  • Kare kökü ters hızlı hızlı, ama yanlış sonuçlar verdi n> = 410881. Ancak önerdiği gibi BobbyShaftoe , biz n <410.881 için FISR kesmek kullanabilirsiniz.
  • Newton'un yöntemi bundan biraz daha yavaştı Math.sqrt(). Muhtemelen Math.sqrt()Newton Metoduna benzer bir şey kullanıyor, ancak donanımda uygulandığı için Java'dan çok daha hızlı. Ayrıca, Newton'un Metodu hala çiftlerin kullanılmasını gerektiriyordu.
  • Sadece tamsayı matematik dahil, birkaç hileler kullanılan değiştirilmiş Newton yöntemi, taşma önlemek için bazı kesmek gerekli (Ben bu işlevin tüm pozitif 64-bit işaretli tamsayılarla çalışmak istiyorum) ve hala daha yavaştı Math.sqrt().
  • İkili pirzola daha yavaştı. İkili pirzola 64 bitlik bir sayının kare kökünü bulmak için ortalama 16 geçiş gerektireceğinden bu mantıklıdır.
  • John'un testlerine göre, orifadeleri kullanmak C ++ 'da a kullanmaktan daha hızlıdır switch, ancak Java ve C #' da orve arasında hiçbir fark yoktur switch.
  • Ayrıca bir arama tablosu (64 boolean özel bir statik dizi olarak) yapmaya çalıştım. Sonra ya anahtar ya da orifade yerine, sadece söyleyebilirim if(lookup[(int)(n&0x3F)]) { test } else return false;. Şaşırtıcı bir şekilde, bu (sadece biraz) daha yavaştı. Bunun nedeni dizi sınırlarının Java'da işaretlenmiş olmasıdır .

21
Bu, int == 32 bit ve long == 64 bit olan ve her ikisinin de imzalandığı Java kodudur.
Kip

14
@Shreevasta: Büyük değerler (2 ^ 53'den büyük) üzerinde bazı testler yaptım ve yönteminiz bazı yanlış pozitifler veriyor. İlk karşılaşılan n = 9007199326062755 içindir, ki bu mükemmel bir kare değildir ancak bir olarak döndürülür.
Kip

37
Lütfen ona "John Carmack hack" demeyin. Onunla gelmedi.
user9282

84
@mamama - Belki, ama ona atfedilir. Henry Ford arabayı icat etmedi, Wright Bros. uçağı icat etmedi ve Galleleo Dünya'nın güneşin etrafında döndüğünü anlayan ilk kişi değildi ... dünya çalıntı icatlardan oluşuyor (ve Aşk).
Robert Fraser

4
((1<<(n&15))|65004) != 0Üç ayrı kontrole sahip olmak yerine, benzer bir şey kullanarak 'hızlı başarısızlık'ta küçük bir hız artışı elde edebilirsiniz .
Nabb

Yanıtlar:


736

En azından CPU'm (x86) ve programlama dili (C / C ++) ile 6 bit + Carmack + sqrt kodundan ~% 35 daha hızlı çalışan bir yöntem buldum. Sonuçlarınız değişebilir, özellikle de Java faktörünün nasıl oynayacağını bilmiyorum.

Benim yaklaşımım üç yönlü:

  1. İlk olarak, bariz cevapları filtreleyin. Buna negatif sayılar ve son 4 bite bakmak da dahildir. (Son altıya baktığımda yardım etmedim.) Ayrıca 0 için evet yanıtı verdim. (Aşağıdaki kodu okurken, girişimin olduğunu unutmayın int64 x.)
    if( x < 0 || (x&2) || ((x & 7) == 5) || ((x & 11) == 8) )
        return false;
    if( x == 0 )
        return true;
  2. Sonra, bunun kare modü 255 = 3 * 5 * 17 olup olmadığını kontrol edin. Bu, üç ayrı primerin bir ürünü olduğundan, 255 mod kalıntılarının sadece 1 / 8'i karedir. Ancak, tecrübelerime göre, modulo operatörünü (%) çağırmak bir faydadan daha pahalıya mal oluyor, bu yüzden kalıntıyı hesaplamak için 255 = 2 ^ 8-1 içeren bit hileleri kullanıyorum. (Daha iyisi ya da daha kötüsü, tek bir baytın tek bir bayttan okunma hilesi kullanmıyorum, sadece bitsel ve ve vardiya.)
    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.
    Artığın bir kare olup olmadığını gerçekten kontrol etmek için, cevabı önceden hesaplanmış bir tabloda ararım.
    if( bad255[y] )
        return false;
    // However, I just use a table of size 512
  3. Son olarak, kare kökünü Hensel lemmasına benzer bir yöntem kullanarak hesaplamaya çalışın . (Doğrudan uygulanabilir olduğunu düşünmüyorum, ancak bazı değişikliklerle çalışıyor.) Bunu yapmadan önce, 2'nin tüm güçlerini ikili bir arama ile bölüyorum:
    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;
    Bu noktada, sayımızın kare olması için 1 mod 8 olmalıdır.
    if((x & 7) != 1)
        return false;
    Hensel lemmasının temel yapısı şöyledir. (Not: test edilmemiş kod; çalışmazsa t = 2 veya 8'i deneyin.)
    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.
    Fikir, her yinelemede, x'in "geçerli" kare kökü olan r'ye bir bit eklediğinizdir; her kare kök doğru modülo 2 daha büyük ve daha büyük bir güç, yani t / 2. Sonunda, r ve t / 2-r x modulo t / 2'nin kare kökleri olacaktır. (Eğer r, x'in karekökü ise, o zaman -r'dir. Bu bile modulo sayılarıdır, ancak bazı sayıları modulo'ya dikkat edin, her şeyin 2'den fazla kare kökü olabileceğine dikkat edin; özellikle 2'nin gücünü içerir. ) Gerçek kare kökümüz 2 ^ 32'den az olduğu için, bu noktada aslında r veya t / 2-r'nin gerçek kare kökleri olup olmadığını kontrol edebiliriz. Benim gerçek kod, aşağıdaki değiştirilmiş döngü kullanın:
    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) );
    Buradaki hız üç şekilde elde edilir: önceden hesaplanmış başlangıç ​​değeri (döngünün ~ 10 tekrarına eşdeğer), döngünün erken çıkışı ve bazı t değerlerini atlama. Son kısımda, bakıyorum z = r - x * xve t'yi 2 bölünme z'nin biraz hile ile en büyük gücü olarak ayarladım. Bu zaten r değerini etkilemeyecek t değerlerini atlamama izin veriyor. Benim durumumda önceden hesaplanmış başlangıç ​​değeri "en küçük pozitif" kare kök modulo 8192 alır.

Bu kod sizin için daha hızlı çalışmıyor olsa bile, umarım içerdiği bazı fikirlerden keyif alırsınız. Önceden hesaplanmış tablolar da dahil olmak üzere eksiksiz, test edilmiş kod aşağıdaki gibidir.

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
Vaov! Bunu Java'ya dönüştürmeye çalışacağım ve bir karşılaştırma yapmanın yanı sıra sonuçların doğruluğunu kontrol edeceğim. Ne bulduğumu sana bildireceğim.
Kip

79
Vay canına, bu çok güzel. Hensel'i daha önce kaldırdığını görmüştüm (polinomların modulo köklerini hesaplamak bir başbakan) ama hatta lemmanın sayıların kare köklerini hesaplamak için dikkatlice düşürülebileceğini fark etmemiştim; this ... uplifting :)
ShreevatsaR

3
@nightcracker Değil. 9 < 0 => false, 9&2 => 0, 9&7 == 5 => false, 9&11 == 8 => false.
primo

53
Maartinus, aşağıdan 2 kat daha hızlı bir çözüm (ve çok daha kısa) yayınladı , biraz sonra, bu çok fazla sevmiyor gibi görünüyor.
Jason C

3
Açıkça görülen kareler filtrelenerek farklı çözümlerde çok fazla hız avantajı elde ediliyor gibi görünüyor. Herkes Maartinus'un çözümü aracılığıyla filtreleme ve daha sonra sadece yerleşik bir işlev olarak sqrt işlevini kullanma durumunu karşılaştırdı mı?
user1914292

377

Partiye oldukça geç kaldım, ancak daha iyi bir cevap vermeyi umuyorum; daha kısa ve ( karşılaştırmamın doğru olduğunu varsayarak ) çok daha hızlı .

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

İlk test karelerin çoğunu hızlı bir şekilde yakalar. Uzun süredir paketlenmiş 64 maddelik bir tablo kullanır, bu nedenle dizi erişim maliyeti yoktur (dolaylı ve sınır kontrolleri). Eşit olarak rasgele için long, burada bitirme olasılığı% 81.25'dir.

İkinci test, çarpanlarına ayırmada tek sayıda ikişer ikişer sayıyı yakalar. Yöntem Long.numberOfTrailingZeros, JIT-ed'i tek bir i86 komutuna aldığından çok hızlıdır.

Sondaki sıfırları bıraktıktan sonra, üçüncü test 011, 101 veya 111 ile biten sayıları ikili kareler halinde işler, ki bunlar mükemmel kareler değildir. Ayrıca negatif sayıları da önemsiyor ve 0 ile de ilgileniyor.

Son test doublearitmetiğe düşer . Olarak doublesadece 53 bit mantis, dönüşüm vardır longiçin doublebüyük değerler için yuvarlama içerir. Bununla birlikte, test doğrudur ( kanıt yanlış olmadığı sürece ).

Mod255 fikrini birleştirmeye çalışmak başarılı olmadı.


3
Vardiya değerinin örtülü olarak maskelenmesi biraz ... şeytani. Bunun Java spesifikasyonunda neden olduğu hakkında bir fikriniz var mı?
dfeuer

6
@ dfeuer Sanırım iki sebep var: 1. Daha fazla değişmek mantıklı değil. 2. Bu, HW'nin çalışması gibi ve bitsel işlemleri kullanan herkes performansla ilgileniyor, bu yüzden başka bir şey yapmak yanlış olur. -goodMask Test yapar, ama bunu yapar önce sağa kaydırma. Bu yüzden tekrarlamanız gerekecek, ancak bu şekilde daha basit ve AFAIK biraz daha hızlı ve eşit derecede iyi.
maaartinus

3
@dfeuer Kıyaslama için ASAP yanıtı vermek önemlidir ve izleyen sıfır sayısının kendisi yanıt vermez; bu sadece bir hazırlık adımı. i86 / amd64 yap. Cep telefonlarındaki küçük CPU'lar hakkında hiçbir fikrim yok, ama en kötüsü, Java onlar için bir AND talimatı üretmeli ve bu da diğer taraftan daha basit.
maaartinus

2
@Sebastian bir olasılıkla daha iyi bir test: if ((x & (7 | Integer.MIN_VALUE)) != 1) return x == 0;.
maaartinus

4
"Çifte sadece 56 bit mantis var" -> Ben daha büyük olasılıkla 53 bit bir tane olduğunu söyleyebilirim . Ayrıca
chux - Monica

132

Bazı kıyaslamalar yapmanız gerekecek. En iyi algoritma, girdilerinizin dağıtımına bağlıdır.

Algoritmanız neredeyse optimal olabilir, ancak karekök rutininizi çağırmadan önce bazı olasılıkları göz ardı etmek için hızlı bir kontrol yapmak isteyebilirsiniz. Örneğin, numaranızın son basamağına onaltılık bir sayı olarak "ve." Mükemmel kareler sadece taban 16'da 0, 1, 4 veya 9 ile bitebilir, bu nedenle girişlerinizin% 75'i için (eşit olarak dağıtıldıkları varsayılarak), çok hızlı bit twiddling karşılığında kare kökü çağırmaktan kaçınabilirsiniz.

Kip, altıgen numarayı uygulayan aşağıdaki kodu karşılaştırdı. 1 ile 100.000.000 arasındaki sayıları test ederken, bu kod orijinalinden iki kat daha hızlı çalışıyordu.

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 ++ 'da benzer kodu test ettiğimde, aslında orijinalinden daha yavaş koştu. Ancak, switch deyimini ortadan kaldırdığımda, hex hile bir kez daha kodu iki kat daha hızlı hale getirir.

int isPerfectSquare(int n)
{
    int h = n & 0xF;  // h is the last hex "digit"
    if (h > 9)
        return 0;
    // Use lazy evaluation to jump out of the if statement as soon as possible
    if (h != 2 && h != 3 && h != 5 && h != 6 && h != 7 && h != 8)
    {
        int t = (int) floor( sqrt((double) n) + 0.5 );
        return t*t == n;
    }
    return 0;
}

Switch ifadesinin kaldırılmasının C # kodu üzerinde çok az etkisi oldu.


bu oldukça zekice ... bunu düşünmezdi
warren

Sondaki bitler hakkında güzel bir nokta. Bu testi buradaki diğer bazı yorumlarla birleştirmeye çalışacağım.
PeterAllenWebb

3
Mükemmel çözüm. Nasıl bulduğunuzu merak ediyor musunuz? Oldukça yerleşik bir prensip mi yoksa sadece anladığınız bir şey mi? : D
Jeel Shah

3
@LarsH 0.5 eklemeye gerek yok, prova bağlantısı için benim çözümüme bakın.
maaartinus

2
@JerryGoyal Derleyiciye ve vakaların değerlerine bağlıdır. Mükemmel bir derleyicide, anahtar her zaman en az başka bir hız kadar hızlıdır. Ancak derleyiciler mükemmel değil, bu yüzden John'un yaptığı gibi denemek en iyisidir.
balıkçılarKas

52

Sayısal Analiz dersinde geçirdiğim korkunç zamanları düşünüyordum.

Ve sonra hatırlıyorum, Quake Source kodundan 'net etrafında dönen bu fonksiyon vardı:

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

Hangi temelde bir kare kök, Newton'un yaklaşık işlevini kullanarak hesaplar (tam adı hatırlayamıyorum).

Kullanılabilir olmalı ve hatta daha hızlı olabilir, olağanüstü kimlik yazılımı oyunlarından birinden!

C ++ ile yazılmıştır, ancak fikri aldıktan sonra aynı tekniği Java'da tekrar kullanmak çok zor olmamalıdır:

Başlangıçta buldum: http://www.codemaestro.com/reviews/9

Newton yöntemi wikipedia'da açıklanmıştır: http://en.wikipedia.org/wiki/Newton%27s_method

Nasıl çalıştığına dair daha fazla açıklama için bağlantıyı takip edebilirsiniz, ancak çok fazla umursamıyorsanız, blog'u okurken ve Sayısal Analiz kursunu almaktan hatırladığım şey kabaca budur:

  • * (long*) &ytemelde tamsayı işlemleri ham bayt uygulanabilir, böylece hızlı dönüştürme-uzun işlevdir.
  • 0x5f3759df - (i >> 1);Yaklaşım fonksiyon için önceden hesaplanan tohum değerdir.
  • * (float*) &iKayan nokta değeri geri dönüştürür.
  • y = y * ( threehalfs - ( x2 * y * y ) )çizgi çağrısının tekrar fonksiyonu üzerinde bir değer dolaşır.

Yaklaştırma işlevi, sonuç üzerinde işlevi yinelediğinizde daha kesin değerler verir. Quake'in durumunda, bir yineleme "yeterince iyi" dir, ancak sizin için uygun değilse ... o zaman ihtiyacınız kadar yineleme ekleyebilirsiniz.

Bu daha hızlı olmalıdır, çünkü saf karekök işleminde yapılan bölme işlemlerinin sayısını 2'ye basit bir bölmeye (aslında * 0.5Fçarpma işlemi) indirir ve bunun yerine birkaç sabit sayıda çarpma işlemiyle değiştirir.


9
Bunun sqrt (sayı) yerine 1 / sqrt (sayı) döndürdüğüne dikkat edilmelidir. Bazı testler yaptım ve bu n = 410881'den başlayamıyor: John Carmack sihirli formülü, gerçek kare kökü 641 olduğunda 642.00104 döndürür
Kip

11
Chris Lomonts gazetesine hızlı ters kare kökler üzerinde bakabilirsiniz: lomont.org/Math/Papers/2003/InvSqrt.pdf Burada aynı tekniği kullanır, ancak farklı bir sihirli sayı ile. Makale, sihirli sayının neden seçildiğini açıklıyor.

4
Ayrıca beyond3d.com/content/articles/8 ve beyond3d.com/content/articles/15 , bu yöntemin kökenleri konusunda biraz ışık tuttu. Genellikle John Carmack'e atfedilir, ancak orijinal kod (muhtemelen) Gary Tarolli, Greg Walsh ve muhtemelen diğerleri tarafından yazılmıştır.

3
Ayrıca Java'da float ve ints yazamazsınız.
Antimon


38

Daha hızlı mı yoksa daha doğru mı olacağından emin değilim, ancak karekökünü daha hızlı çözmek için John Carmack'in Sihirli Kare Kökü algoritmasını kullanabilirsiniz. Muhtemelen bunu tüm olası 32 bit tamsayılar için test edebilir ve sadece bir tayin olduğu için doğru sonuçlara sahip olduğunuzu doğrulayabilirsiniz. Ancak, şimdi düşündüğümde, çiftleri kullanmak da yaklaşıyor, bu yüzden bunun nasıl oynayacağından emin değilim.


10
Carmack'in numarasının bu günlerde oldukça anlamsız olduğuna inanıyorum. Yerleşik sqrt talimatı eskisinden çok daha hızlıdır, bu yüzden sadece düzenli bir kare kök yapmak ve sonuç bir int ise test etmek daha iyi olabilir. Her zaman olduğu gibi kıyaslayın.
jalf

4
Bu, n = 410881'den başlayan John Carmack sihirli formülü, gerçek kare kökü 641 olduğunda 642.00104 döndürür
Kip

11
Son zamanlarda bir Java oyununda Carmack'in hilesini kullandım ve çok etkili oldu, yaklaşık% 40'lık bir hız kazandırdı, bu yüzden en azından Java'da hala yararlı.
finnw

3
@Robert Fraser Evet, toplam kare hızında +% 40. Oyunda, neredeyse tüm CPU döngülerini alan bir parçacık fiziği sistemi vardı, kare kök fonksiyonu ve en yakın tamsayı fonksiyonuna (benzer bir bit dönen kesmek kullanarak da optimize ettim)
hakimdi

5
Bağlantı koptu.
Pixar

36

"Doğru" kare kökü bulmaya çalışmak için bir ikili kesme yaparsanız, sahip olduğunuz değerin anlatacak kadar yakın olup olmadığını kolayca tespit edebilirsiniz:

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

Yani hesapladıktan n^2sonra, seçenekler şunlardır:

  • n^2 = target: bitti, geri dön
  • n^2 + 2n + 1 > target > n^2 : yakınsın, ama mükemmel değil: yanlış döndür
  • n^2 - 2n + 1 < target < n^2 : aynen
  • target < n^2 - 2n + 1 : bir altta ikili pirzola n
  • target > n^2 + 2n + 1 : daha yüksek bir ikili pirzola n

(Üzgünüz, bu nmevcut tahmininiz ve targetparametre için kullanılır. Karışıklık için özür dileriz !)

Bunun daha hızlı olup olmayacağını bilmiyorum, ama denemeye değer.

DÜZENLEME: İkili pirzola, tam sayı aralığını da almak zorunda değildir (2^x)^2 = 2^(2x), bu nedenle hedefinizde en iyi set bitini bulduktan sonra (bu, biraz dönen bir numara ile yapılabilir; nasıl olduğunu tamamen unuttum) hızlıca bir dizi potansiyel cevap alabilirsiniz. Dikkat edin, saf bir ikili pirzola hala sadece 31 veya 32 yineleme alacaktır.


Param bu tür bir yaklaşımda. Tam kare kökü hesapladığı için sqrt () öğesini çağırmaktan kaçının ve yalnızca ilk birkaç basamağa ihtiyacınız vardır.
PeterAllenWebb

3
Öte yandan, kayan nokta özel bir FP biriminde yapılıyorsa, her türlü eğlenceli hile kullanıyor olabilir. Ben bir kıyaslama olmadan üzerine bahse girmek istemiyorum :) (Bu gece C # olsa da, sadece görmek için deneyebilirsiniz ...)
Jon Skeet

8
Donanım sqrts aslında bugünlerde oldukça hızlı.
Adam Rosenfield

24

Bu iş parçacığındaki algoritmaların birkaçını kendi analizimi yaptım ve bazı yeni sonuçlar elde ettim. Bu eski sonuçları bu cevabın düzenleme geçmişinde görebilirsiniz, ancak bir hata yaptığım gibi doğru değiller ve yakın olmayan birkaç algoritmayı analiz etmek için zaman harcadık. Ancak, birkaç farklı cevap dersleri çekerek, şimdi bu iş parçacığının "kazanan" ezmek iki algoritmalar var. İşte herkesten farklı yaptığım temel şey:

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

Bununla birlikte, çoğu zaman bir veya iki çok hızlı talimat ekleyen bu basit satır, switch-caseifadeyi bir if ifadesine büyük ölçüde basitleştirir . Bununla birlikte, test edilen sayıların çoğunda önemli iki güç faktörü varsa çalışma zamanına eklenebilir.

Aşağıdaki algoritmalar aşağıdaki gibidir:

  • İnternet - Kip'in cevabı gönderildi
  • Durron - Tek geçişli cevabı temel olarak kullanarak değiştirilmiş cevabım
  • DurronTwo - Diğer bazı küçük değişikliklerle iki geçişli cevabı (@JohnnyHeggheim tarafından) kullanarak değiştirilmiş cevabım.

Sayılar kullanılarak oluşturulmuşsa örnek bir çalışma zamanı 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

Ve sadece ilk milyon uzunluğunda çalıştırılırsa örnek bir çalışma zamanı:

 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

Gördüğünüz gibi, DurronTwobüyük girdiler için daha iyisini yapar, çünkü sihir numarasını çok sık kullanır, ancak ilk algoritmaya kıyasla ve Math.sqrtsayılar çok daha küçük olduğu için hızlanır. Bu arada, daha basit Durronbüyük bir kazanan çünkü ilk milyon sayısında asla birçok kez 4'e bölmek zorunda değil.

İşte 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;
}

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

Ve karşılaştırmalı değerlendirme kayışım: (Google kaliper 0.1-rc5 gerektirir)

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

GÜNCELLEME: Bazı senaryolarda daha hızlı, diğerlerinde daha yavaş yeni bir algoritma yaptım, farklı girdilere dayalı farklı ölçütler aldım. Modulo hesaplarsak 0xFFFFFF = 3 x 3 x 5 x 7 x 13 x 17 x 241, kareler olamaz sayıların% 97.82'sini ortadan kaldırabiliriz. Bu, 5 bitsel işlemle bir satırda (bir çeşit) yapılabilir:

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

Elde edilen indeks 1) tortu, 2) tortu + 0xFFFFFFveya 3) tortudur + 0x1FFFFFE. Tabii ki, 0xFFFFFF3mb dosyası hakkında kalıntı modulo için bir arama tablosuna ihtiyacımız var (bu durumda ascii metin ondalık sayıları olarak saklanır, optimal değil, ancak ByteBuffervb. kadar önemli t. burada dosyayı bulabilirsiniz (veya kendiniz oluşturmak):

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

Ben böyle bir booleandiziye yüklemek :

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

Örnek çalışma zamanı. Çalıştığım Durronher denemede (sürüm bir) yendi .

 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
Dev bir arama tablosu iyi bir fikir gibi görünmüyor. Önbellek kaçışı, x86 donanım sqrt komutundan (~ 20 döngü) daha yavaştır (~ 100 ila 150 döngü). Verimlilik açısından, çok sayıda olağanüstü önbellek özlemini sürdürebilirsiniz, ancak yine de başka yararlı veriler çıkarıyorsunuz. Büyük bir arama tablosu, yalnızca diğer seçeneklerden çok daha hızlı olsaydı buna değer olurdu ve bu işlev, tüm programınızın performansında ana faktördü.
Peter Cordes

1
@SwissFrank: programınızın yaptığı tek şey mükemmel kare kontrolü mü? Bir arama tablosu, onu sıkı bir döngüde art arda çağıran bir mikrobenchmarkta iyi görünebilir, ancak çalışma kümesinde başka verileri olan gerçek bir programda iyi değildir.
Peter Cordes

1
Paketlenmiş bir bitmap olarak saklanırsa , 0x1FFFFFE bitlik bir bitmap 4 mega bayt alır . Modern bir Intel masaüstünde L3 önbellek vuruşu ,> 40 döngü gecikmesine ve büyük bir Xeon'da daha kötüdür; donanım sqrt + mul gecikme süresinden daha uzun. Değer başına 1 bayt ile bir bayt -map olarak depolanırsa , yaklaşık 32 MB'dir; tüm çekirdeklerin büyük bir önbelleği paylaştığı çok çekirdekli bir Xeon'dan başka bir şeyin L3 önbelleğinden daha büyük. Dolayısıyla, giriş verileriniz yeterince geniş bir aralıkta eşit dağılımlı rasgele dağıtıma sahipse, dar bir döngüde bile çok sayıda L2 önbellek özlemi alırsınız. (Intel'deki özel çekirdek başına L2 yalnızca 256 bin, ~ 12 döngü gecikme süresine sahip.)
Peter Cordes

1
@SwissFrank: Oh, eğer yaptığınız tek şey kök kontrolü ise, o zaman L3 isabetlerini almak için bir bitmap ile potansiyel var. Gecikmeye bakıyordum, ama birçok özlem aynı anda uçuşta olabilir, bu yüzden iş hacmi potansiyel olarak iyi. OTOH, SIMD sqrtpsverimi ve hatta sqrtpd(çift kesinlikli) Skylake için çok kötü değil, ancak eski CPU'lardaki gecikmeden çok daha iyi değil. Neyse 7-cpu.com/cpu/Haswell.html bazı güzel deneysel numaraları ve diğer CPU'lar için sayfaları vardır. Agner Fog'un mikroarş kılavuzu pdf'sinde Intel ve AMD uarches için bazı önbellek gecikme numaraları vardır: agner.org/optimize
Peter Cordes

1
Java'dan x86 SIMD kullanmak bir sorundur ve int-> fp ve fp-> int dönüşüm maliyetine eklediğinizde, bir bitmap'in daha iyi olabileceği düşünülebilir. double+ -2 ^ 24 aralığının dışında bir tamsayıyı yuvarlamaktan kaçınmak için hassasiyete ihtiyacınız vardır (bu nedenle 32 bit tam sayı bunun dışında olabilir) ve talimat başına sadece (SIMD vektörü başına) öğelerin yarısını işlemekten sqrtpddaha yavaştır sqrtps.
Peter Cordes

18

Tamsayı Karekökünü hesaplamak için Newton'un yöntemini kullanmak , daha sonra bu sayıyı kareye almak ve mevcut çözümünüzde yaptığınız gibi kontrol etmek çok daha hızlı olmalıdır . Newton'un yöntemi, diğer bazı cevaplarda bahsedilen Carmack çözümünün temelidir. Daha hızlı bir yanıt alabilmeniz gerekir, çünkü kökün tamsayı kısmı ile ilgilenirsiniz, bu da yaklaşık algoritmayı daha erken durdurmanıza izin verir.

Deneyebileceğiniz başka bir optimizasyon: Sayının Dijital Kökü 1, 4, 7 veya 9 ile bitmezse, sayı mükemmel bir kare değildir . Bu, daha yavaş karekök algoritmasını uygulamadan önce girişlerinizin% 60'ını ortadan kaldırmanın hızlı bir yolu olarak kullanılabilir.


1
Dijital kök kesinlikle modulo ile tamamen eşdeğerdir, bu nedenle mod 16 ve mod 255 gibi diğer modulo yöntemleriyle birlikte düşünülmelidir.
Christian Oudard

1
Dijital kökün modulo ile eşdeğer olduğundan emin misiniz? Bağlantının açıkladığı gibi tamamen farklı bir şey gibi görünüyor. Listenin 1,4,5,9 değil 1,4,7,9 olduğuna dikkat edin.
Fractaly

1
Ondalık sistemdeki dijital kök, modulo 9 (kuyu dr (n) = 1 + ((n-1) mod 9) kullanmaya eşdeğerdir; bu nedenle hafif bir kayma). 0,1,4,5,9 sayıları modulo 16 içindir ve 0, 1, 4, 7 modulo 9 içindir - bu dijital kök için 1, 4, 7, 9'a karşılık gelir.
Hans Olsson

16

Bu işlevin tüm pozitif 64 bit işaretli tamsayılarla çalışmasını istiyorum

Math.sqrt()giriş parametreleri olarak çiftlerle çalışır, bu nedenle 2 ^ 53'ten büyük tamsayılar için doğru sonuçlar elde edemezsiniz .


5
Aslında cevabı 2 ^ 53'den büyük tüm mükemmel karelerde ve her mükemmel karenin altındaki 5'ten her mükemmel karenin 5'ine kadar olan tüm sayılar üzerinde test ettim ve doğru sonucu elde ettim. (sqrt cevabını uzun bir zamana yuvarladığımda yuvarlanma hatası düzeltildi, sonra bu değerin karesini alın ve karşılaştırın)
Kip

2
@Kip: Sanırım işe yaradığını kanıtladım .
maaartinus

Sonuçlar tam olarak doğru değil, düşündüğünüzden daha doğru. İki katına ve karekökten sonra dönüşümden sonra en az 15 doğru basamak varsayarsak, bu çoktur, çünkü 32 bit karekökü için 11: 10'dan fazla basamağa ve ondalık basamak için 1'den az basamağa ihtiyacımız vardır, çünkü +0,5 en yakınına yuvarlar.
mwfearnley

3
Math.sqrt () tamamen doğru değildir, ancak zorunlu değildir. İlk yazıda tst, sqrt (N) 'ye yakın bir tamsayıdır. N bir kare değilse, tst * tst! = N, tst değeri ne olursa olsun. N mükemmel bir kareyse, sqrt (N) <2 ^ 32 ve sqrt (N) <0.5 hata ile hesaplandığı sürece, biz iyiyiz.
gnasher729

13

Sadece kayıt için, bir başka yaklaşım da ana ayrışmayı kullanmaktır. Ayrışmanın her faktörü eşitse, sayı mükemmel bir karedir. Yani istediğiniz bir sayı, asal sayıların karelerinin bir ürünü olarak ayrıştırılıp ayrıştırılamayacağını görmek. Tabii ki, böyle bir ayrışma elde etmenize gerek yok, sadece var olup olmadığını görmek için.

İlk önce 2 ^ 32'den küçük asal sayıların karelerinden oluşan bir tablo oluşturun. Bu, bu sınıra kadar olan tamsayıların bir tablosundan çok daha küçüktür.

Bir çözüm şu şekilde olur:

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

Sanırım biraz şifreli. Her adımda asal sayının karesinin giriş numarasını böldüğünü kontrol etmektir. Eğer öyleyse, sayıyı mümkün olduğu kadar kareye böler, bu kareyi asal ayrışmadan çıkarmak için. Bu işlemle 1'e geldiysek, girdi numarası asal sayıların karesinin bir ayrışmasıydı. Kare sayının kendisinden daha büyük olursa, bu karenin veya daha büyük karelerin bölebilmesi mümkün değildir, bu nedenle sayı asal sayıların karelerinin ayrıştırılması olamaz.

Günümüzde donanımda yapılan sqrt ve burada asal sayıları hesaplama ihtiyacı göz önüne alındığında, bu çözüm çok daha yavaş olduğunu düşünüyorum. Ancak cevabında mrzl'nin dediği gibi, 2 ^ 54 üzerinde çalışmayan sqrt çözümünden daha iyi sonuçlar vermelidir.


1
tamsayı bölümü, geçerli donanımda FP sqrt değerinden daha yavaştır. Bu fikrin hiç şansı yok. 2008'de bile sqrtsdCore2'nin verimi 6-58c'de birdir. Onun idiv12-36cycles başına biridir. (iş hacmine benzer gecikmeler: her iki birim de boru hattına bağlı değildir).
Peter Cordes

sqrt mükemmel bir şekilde doğru olmak zorunda değildir. Bu nedenle sonucu tamsayı-kareye alarak ve girdi tamsayısının tamsayı tamsayı olup olmadığını belirlemek için bir tamsayı karşılaştırması yaparak kontrol edersiniz.
Peter Cordes

11

dMükemmel bir karenin son basamaklarının sadece belirli değerleri alabileceği belirtildi. Bir sayının son dbasamağı (tabandaki b) , bölündüğünde nkalanla aynıdır , yani. C gösterimindenbdn % pow(b, d) .

Bu herhangi bir modül için genelleştirilebilir m, yani.n % msayıların bazı yüzdelerinin mükemmel kareler olmasını engellemek için kullanılabilir. Şu anda kullandığınız modül 12'dir, bu da 12'ye izin verir, yani. Kalanların% 19'u, mümkün olduğunca kareler. Küçük bir kodlama ile, sadece 2016, yani izin veren 110880 modülünü buldum. Kalanların% 1.8'i mümkün olduğunca kareler. Bu nedenle, bir modül işleminin (yani bölüm) ve tablo aramasının makinenizdeki karekökü karşısında maliyetine bağlı olarak, bu modülü kullanmak daha hızlı olabilir.

Bu arada, Java'nın arama tablosu için paketlenmiş bir bit dizisini saklamanın bir yolu varsa, bunu kullanmayın. 110880 32-bit kelimeler bugünlerde çok fazla RAM değil ve bir makine kelimesini getirmek tek bir bit getirmekten daha hızlı olacak.


Güzel. Bunu cebirsel olarak mı yoksa deneme yanılma yoluyla mı yaptınız? Neden bu kadar etkili olduğunu görebiliyorum - mükemmel kareler arasında çok sayıda çarpışma var, örneğin 333 ^ 2% 110880 == 3 ^ 2, 334 ^ 2% 110880 == 26 ^ 2, 338 ^ 2% 110880 == 58 ^ 2 .. .
finnw

IIRC kaba kuvvetti, ancak 110880 = 2 ^ 5 * 3 ^ 2 * 5 * 7 * 11, 6 * 3 * 2 * 2 * 2 - 1 = 143 uygun bölen verdiğini unutmayın.
Hugh Allen

Arama sınırlamaları nedeniyle 44352'nin% 2,6 geçiş oranıyla daha iyi çalıştığını buldum. En azından benim uygulamamda.
Fractaly

1
Tamsayı bölümü ( idiv), sqrtsdmevcut x86 donanımında FP sqrt ( ) değerine eşit veya daha kötü maliyetlidir . Ayrıca, bitfield'lerden kaçınmaya tamamen katılmıyorum. Önbellek isabet oranı bir bit alanı ile tonlarca daha iyi olacak ve bir bit alanında biraz test yapmak, bir baytın tamamını test etmekten sadece bir veya iki daha basit talimattır. (Bit alanı olmayanlar gibi önbelleğe sığabilen küçük tablolar için, bir bayt dizisi en iyi olurdu, 32bit ints değil. X86, 32bit dword'e eşit hızda tek bayt erişimine sahiptir.)
Peter Cordes

11

Bir tamsayı sorunu bir tamsayı çözümünü hak eder. Böylece

Bu şekilde en büyük tamsayıyı bulmak için (negatif olmayan) tamsayılarda ikili arama yapın t**2 <= n. Sonra r**2 = ntam olarak test edin . Bu zaman O (log n) alır.

Kümenin sınırsız olması nedeniyle pozitif tam sayıların ikili olarak nasıl aranacağını bilmiyorsanız, bu kolaydır. Artan fonksiyonunuzu f (yukarıda f(t) = t**2 - n) iki güç üzerinden hesaplayarak başlıyorsunuz . Pozitif olduğunu gördüğünüzde bir üst sınır buldunuz. Sonra standart ikili arama yapabilirsiniz.


Aslında zaman, en azından O((log n)^2)çarpma işleminin sabit zamanlı olmadığı, fakat aslında daha düşük bir sınırı olduğu için O(log n), büyük çok kesinlikli sayılarla çalışırken görünür hale gelir. Ama bu wiki'nin kapsamı 64 bit gibi görünüyor, bu yüzden belki de nbd.

10

Maaartinus'un çözümünün aşağıdaki basitleştirilmesi, çalışma süresinden birkaç yüzde puan alıyor gibi görünüyor, ancak güvenebileceğim bir karşılaştırma ölçütü oluşturmak için kıyaslamada yeterince iyi değilim:

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

İlk testi nasıl atladığınızı kontrol etmeye değer,

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

performansı etkiler.


2
Sonuçlar burada . İlk testi kaldırmak, çoğu vakayı oldukça ucuza çözdüğü için kötüdür. Kaynak cevabımda (güncellendi).
maaartinus

9

Performans için, çoğu zaman bazı uzlaşmalar yapmanız gerekir. Diğerleri çeşitli yöntemler ifade ettiler, ancak Carmack'in korsanlığının belirli N değerlerine kadar daha hızlı olduğunu fark ettiniz. Sonra, "n" yi kontrol etmelisiniz ve bu N sayısından azsa, Carmack'in korsanlığını kullanın, aksi takdirde açıklanan başka bir yöntem kullanın burada cevaplar.


Önerinizi de çözüme dahil ettim. Ayrıca, güzel sap. :)
Kip

8

Bu, bu iş parçacığında başkaları tarafından önerilen tekniklerin bir kombinasyonunu kullanarak, bulabildiğim en hızlı Java uygulamasıdır.

  • Mod-256 testi
  • Hatalı mod-3465 testi (bazı yanlış pozitiflerin maliyetinde tamsayı bölünmesini önler)
  • Kayan noktalı karekök, yuvarlak ve girdi değeri ile karşılaştır

Ayrıca bu değişiklikleri denedim ancak performansa yardımcı olmadı:

  • Ek mod-255 testi
  • Giriş değerinin 4'lü güçlere bölünmesi
  • Hızlı Ters Kare Kök (yüksek N değerleri için çalışmak için, donanım kare kök işlevinden daha yavaş hale getirmek için yeterli olan 3 yinelemeye ihtiyaç duyar.)

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

N'nin 2-güç kısmından en baştan kurtulmalısınız.

2. Edit Aşağıdaki m için sihirli ifade şöyle olmalıdır:

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

ve yazılı olarak değil

2. düzenlemenin sonu

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

1. Düzenleme:

Küçük gelişme:

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

1. düzenlemenin sonu

Şimdi her zamanki gibi devam edin. Bu şekilde, kayan nokta parçasına geldiğinizde, 2-güç kısmı garip olan (yaklaşık yarısı) tüm sayılardan zaten kurtuldunuz ve sonra sadece 1 / 8'inin kalanını düşünüyorsunuz. Yani kayan nokta kısmını sayıların% 6'sında çalıştırıyorsunuz.


7

Project Euler etiketlerinde bahsedilir ve içindeki sorunların birçoğu sayıların kontrol edilmesini gerektirir >> 2^64 . Yukarıda belirtilen optimizasyonların çoğu, 80 baytlık bir tamponla çalışırken kolayca çalışmaz.

Java BigInteger ve Newton'un yönteminin tam olarak daha iyi çalışan biraz değiştirilmiş bir sürümünü kullandım. Sorun tam kareler olmasıydı n^2yaklaşmıştır (n-1)yerine nnedeniyle n^2-1 = (n-1)(n+1)ve son hata sadece bir final bölen aşağıdaki adım ve sonlandırıldı algoritma oldu. Hatayı hesaplamadan önce orijinal bağımsız değişkene bir tane ekleyerek düzeltmek kolaydı. (Küp kökleri vb.İçin iki ekleyin)

Bu algoritmanın güzel bir özelliği, sayının mükemmel bir kare olup olmadığını hemen söyleyebilmenizdir - Newton'un yöntemindeki son hata (düzeltme değil) sıfır olacaktır. Basit bir değişiklik floor(sqrt(x)), en yakın tamsayı yerine hızlı bir şekilde hesaplamanızı sağlar . Bu, birkaç Euler probleminde kullanışlıdır.


1
Ben aynı algoritma çok hassas tamponlara iyi çeviri değil aynı şey düşünüyordum. Bu yüzden bunu buraya yapıştıracağımı düşündüm ... Aslında sayı teorisi uygulamalarının nadiren bulamadığı çok sayıda asimptotik karmaşıklığa sahip olasılıklı bir kare testi buldum . Project Euler'e aşina olmasa da ... ilginç görünüyor.

6

Bu, Ruby'de, bu soru için özel olarak uyarlanmış, eski Marchant hesap makinesi algoritmasının ondalıktan ikilisine (üzgünüm, referansım yok) yeniden işleme:

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

İşte benzer bir şeyden oluşan bir çalışma (lütfen bana kodlama stili / kokular veya tıkalı O / O için oy vermeyin - önemli olan algoritma ve C ++ benim ana dilim değil). Bu durumda, kalıntı == 0 arıyoruz:

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

Yineleme sayısı O (ln n), n'nin v bit uzunluğu olduğu görünüyor, bu yüzden bu daha büyük v için çok tasarruf edeceğinden şüphe ediyorum. Kayan nokta sqrt yavaş, belki 100-200 döngü, ancak tamsayı matematik değil ücretsiz. Her biri 15 döngü olan bir düzine yineleme ve bir yıkama olurdu. Yine de, ilginç olduğu için +1.
Tadmas

Aslında toplama ve çıkarma işlemlerinin XOR tarafından yapılabileceğine inanıyorum.
Brent.Longborough

Bu şaşkın bir yorumdu - sadece ekleme bir XOR tarafından yapılabilir; çıkarma aritmetiktir.
Brent.Longborough

1
XOR'un çalışma süresi ile yine de ekleme arasında gerçekten önemli bir fark var mı?
Tadmas

1
@Tadmas: "daha sonra optimize et" kuralını kırmak için yeterli olmayabilir. (:-)
Brent.Longborough

6

Sqrt çağrısı, belirtildiği gibi mükemmel bir şekilde doğru değildir, ancak diğer cevapları hız açısından uçurmaması ilginç ve öğreticidir. Sonuçta, bir sqrt için montaj dili talimatlarının sırası küçüktür. Intel, Java tarafından kullanılmayan bir donanım talimatına sahiptir, çünkü IEEE'ye uymadığına inanıyorum.

Peki neden yavaş? Java aslında JNI aracılığıyla bir C rutini çağırıyor ve aslında bunu yapmak bir Java alt yordamını çağırmaktan daha yavaş, ki bu satır içi yapmaktan daha yavaş. Bu çok can sıkıcı ve Java daha iyi bir çözüm bulmalıydı, yani gerekirse kayan nokta kütüphanesi çağrıları oluşturmak. Oh iyi.

C ++ 'da, tüm karmaşık alternatiflerin hızda kaybedeceğinden şüpheleniyorum, ancak hepsini kontrol etmedim. Yaptığım ve Java kullanıcılarının faydalı bulacağı basit bir hack, A. Rex tarafından önerilen özel vaka testinin bir uzantısı. Bit dizisi olarak sınır işaretli olmayan tek bir uzun değer kullanın. Bu şekilde, 64 bit boole aramaya sahip olursunuz.

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

Rutin isPerfectSquare5 benim core2 duo makinemde yaklaşık 1/3 kez çalışır. Aynı hatlar boyunca daha fazla ince ayar yapmanın zamanı ortalama olarak daha da azaltabileceğinden şüpheleniyorum, ancak her kontrol ettiğinizde, daha fazla ortadan kaldırmak için daha fazla test yapıyorsunuz, bu yüzden bu yolda çok daha fazla ilerleyemezsiniz.

Elbette, negatif için ayrı bir test yapmak yerine, yüksek 6 biti aynı şekilde kontrol edebilirsiniz.

Yaptığım tek şey olası kareleri ortadan kaldırmak olduğunu unutmayın, ancak potansiyel bir durum olduğunda, orijinal, inline isPerfectSquare çağırmak zorunda.

Pp1 ve pp2'nin statik değerlerini başlatmak için init2 rutini bir kez çağrılır. C ++ uygulamamda uzun süre imzasız kullandığımı, bu nedenle imzaladığınızdan beri >>> işlecini kullanmanız gerektiğini unutmayın.

Diziyi kontrol etmek için içsel bir ihtiyaç yoktur, ancak Java'nın optimize edicisi bu şeyleri oldukça hızlı bir şekilde çözmelidir, bu yüzden onları bunun için suçlamıyorum.


3
Bahse girerim iki kez yanlış olduğunu. 1. Intel sqrt, IEEE'ye uygundur. Tek uygun olmayan talimatlar, lange argümanları için goniometrik talimatlardır. 2. Java Math.sqrt için intrinsics kullanır, JNI kullanmaz .
maaartinus

1
Kullanmayı unuttunuz pp2mu? Bunun pp1en az altı önemli biti test etmek için kullanıldığını anlıyorum , ancak sonraki altı biti test etmenin herhangi bir anlam ifade ettiğine inanmıyorum.
maaartinus

6

Bazı girdilerde neredeyse doğru bir yöntem kullanma fikrini seviyorum. İşte daha yüksek "ofset" olan bir versiyon. Kod çalışıyor gibi görünüyor ve basit test durumumu geçer.

Sadece değiştirin:

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

Bu kodla:

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

Genel bit uzunluğu göz önüne alındığında (burada belirli bir tür kullanmış olsam da) aşağıdaki gibi basit algo tasarlamaya çalıştım. Başlangıçta 0,1,2 veya <0 için basit ve açık bir kontrol gereklidir. Aşağıdaki, herhangi bir matematik fonksiyonunu kullanmaya çalışmadığı için basittir. Operatörün çoğu, bit bilge operatörlerle değiştirilebilir. Yine de herhangi bir bench mark verisi ile test yapmadım. Ne matematikte ne de bilgisayar algoritması tasarımında uzman değilim, problemi belirttiğinizi görmek isterim. Orada çok fazla gelişme şansı olduğunu biliyorum.

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: Tarayıcımla ilgili bir sorun var.
nabam serbang

1
Biraz girintiye ihtiyacınız var.
Steve Kuo 10'11

5

Bir karenin son n biti gözlendiğinde olası tüm sonuçları kontrol ettim. Art arda daha fazla bit incelendiğinde, 5 / 6'ya kadar giriş ortadan kaldırılabilir. Aslında bunu Fermat'ın Çarpanlara Ayırma algoritmasını uygulamak için tasarladım ve orada çok hızlı.

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

Sözde kodun son biti, daha fazla değeri ortadan kaldırmak için testleri genişletmek için kullanılabilir. Yukarıdaki testler k = 0, 1, 2, 3 içindir.

  • a biçimindedir (3 << 2k) - 1
  • b biçimindedir (2 << 2k)
  • c biçimindedir (2 << 2k + 2) - 1
  • d (2 << 2k - 1) * 10 biçimindedir

    İlk önce ikisinin güç modüllerine sahip bir kare kalıntıya sahip olup olmadığını test eder, daha sonra son bir modüle dayanarak test eder, daha sonra bir final testi yapmak için Math.sqrt'i kullanır. En üst yazıdan bir fikir buldum ve bunu genişletmeye çalıştım. Herhangi bir yorum veya öneri için teşekkür ederim.

    Güncelleme: Testi bir modül, (modSq) ve 44352 modül tabanıyla kullanarak, testim OP'nin 1.000.000.000'a kadar olan numaralar için olan süresinin% 96'sında çalışır.


  • 2

    İşte bir bölme ve fethetme çözümü.

    Doğal bir sayının ( number) kare kökü doğal bir sayı ( ) ise solution, aşağıdakilerin solutionbasamak sayısına göre kolayca bir aralık belirleyebilirsiniz number:

    • number1 basamaklı: solutionaralık = 1 - 4
    • number2 basamaklı: solutionaralık = 3 - 10
    • number3 basamaklı: solutionaralık = 10 - 40
    • number4 basamağı vardır: solutionaralık = 30 - 100
    • number5 basamağı vardır: solutionaralık = 100-400

    Tekrar fark ettiniz mi?

    Bu aralığı, aşağıdakiler için bir olup olmadığını görmek için ikili arama yaklaşımında kullanabilirsiniz solution:

    number == solution * solution

    İşte kod

    İşte benim sınıf 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;
        }
    
    }

    Ve işte nasıl kullanılacağına dair bir örnek.

    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
    Kavramı seviyorum, ama kibarca büyük bir kusura işaret etmek istiyorum: sayılar temel 2 ikili. Taban 2'yi taban 10'a dönüştürmek toString, bitsel operatörlere kıyasla inanılmaz derecede pahalı bir işlemdir. Bu nedenle, sorunun - performans - amacını karşılamak için temel 10 dizeler yerine bitsel operatörler kullanmalısınız. Yine, konseptinizi gerçekten seviyorum. Bununla birlikte, uygulamanız (şu anda olduğu gibi), soru için gönderilen tüm olası çözümlerden en yavaş olanıdır.
    Jack Giffin

    1

    Hız bir endişe ise, neden en sık kullanılan girdi kümesini ve değerlerini bir arama tablosuna bölmeyip istisnai durumlar için ortaya çıkardığınız optimize edilmiş sihirli algoritmayı yapmıyorsunuz?


    Sorun "yaygın olarak kullanılan bir girdi kümesi" olmamasıdır - genellikle bir liste üzerinden yineleme yapıyorum, bu yüzden aynı girişleri iki kez kullanmayacağım.
    Kip

    1

    Eğer son X rakamı N ise 'mükemmel bir kare olamaz' paketlemek mümkün olmalıdır! Java 32 bit ints kullanacağım ve sayının son 16 bitini kontrol etmek için yeterli veri üreteceğim - bu 2048 onaltılık int değeridir.

    ...

    Tamam. Ya biraz ötesinde bir sayı teorisi ile karşılaştım ya da kodumda bir hata var. Her durumda, kod İşte:

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

    ve işte sonuçlar:

    (ed: prettify.js'deki düşük performans için elut edildi; görmek için düzeltme geçmişini görüntüleyin.)


    1

    Tamsayı aritmetiği ile Newton yöntemi

    Tamsayı olmayan işlemlerden kaçınmak isterseniz, aşağıdaki yöntemi kullanabilirsiniz. Temelde tamsayı aritmetiği için değiştirilmiş Newton Metodunu kullanır.

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

    Bu uygulama kullanılan çözümlerle rekabet edemez Math.sqrt. Bununla birlikte, diğer direklerin bazılarında açıklanan filtreleme mekanizmaları kullanılarak performansı arttırılabilir.


    1

    Newton'un yöntemiyle karekök hesaplamak, başlangıç ​​değerinin makul olması şartıyla korkunç derecede hızlıdır. Bununla birlikte, makul bir başlangıç ​​değeri yoktur ve pratikte, ikiye ayırma ve log (2 ^ 64) davranışı ile biteriz.
    Gerçekten hızlı olmak için makul bir başlangıç ​​değerine ulaşmanın hızlı bir yoluna ihtiyacımız var ve bu da makine diline inmemiz gerektiği anlamına geliyor. Bir işlemci Pentium'da POPCNT gibi bir yönerge sağlıyorsa, baştaki sıfırları sayar, bunu önemli bitlerin yarısı ile bir başlangıç ​​değerine sahip olmak için kullanabiliriz. Dikkatle, her zaman yeterli olacak sabit bir Newton adımı bulabiliriz. (Böylece döngü ve çok hızlı yürütme ihtiyacını ortadan kaldırarak.)

    İkinci bir çözüm, hızlı bir sqrt hesaplaması (i87 yardımcı işlemcisi gibi) olabilen kayan nokta tesisinden geçiyor. Exp () ve log () yoluyla bir gezi bile Newton'un ikili aramaya dönüştüğünden daha hızlı olabilir. Bunun zor bir yönü var, daha sonra neyin iyileştirilmesi ve neyin gerekli olduğuna dair işlemciye bağlı bir analiz.

    Üçüncü bir çözüm biraz farklı bir sorunu çözmektedir, ancak söz konusu durum açıklandığı için bahsetmeye değer. Biraz farklılık gösteren sayılar için çok sayıda kare kök hesaplamak istiyorsanız, başlangıç ​​değerini asla yeniden başlatmazsanız, ancak önceki hesaplamanın kaldığı yerde bıraktığınızda Newton yinelemesini kullanabilirsiniz. Bunu en az bir Euler probleminde başarıyla kullandım.


    İyi bir tahmin yapmak çok zor değil. Çözüm için bir alt ve üst sınır tahmin etmek için sayının basamak sayısını kullanabilirsiniz. Ayrıca, bölme ve fethetme çözümü önerdiğim yanıtı da görün.
    MWB

    POPCNT ile basamak sayısını sayma arasındaki fark nedir? Tek bir nanosaniye içinde POPCNT yapabilirsiniz.
    Albert van der Horst

    1

    Kare Sayının mükemmel bir kare olduğu bir sayının kökü.

    Karmaşıklık 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

    Tamsayılarınızın sonlu boyutta olduğu göz önüne alındığında, en hızlı yolun (a) parametreleri boyuta göre bölümlere ayırmayı (örn. En büyük bit kümesine göre kategorilere), ardından değeri bir dizi mükemmel kareye karşı kontrol etmeyi içerdiğinden şüpheleniyorum bu aralık dahilinde.


    2
    Uzun bir aralıkta 2 ^ 32 mükemmel kare var. Bu masa çok büyük olurdu. Ayrıca, değeri bir bellek erişimi üzerinden hesaplamanın avantajı çok büyük olabilir.
    PeterAllenWebb

    Oh hayır yok, 2 ^ 16 var. 2 ^ 32, 2 ^ 16 karedir. 2 ^ 16 vardır.
    Göksel M Weasel

    3
    Evet, ancak uzun bir aralık 32 bit değil, 64 bittir. sqrt (2 ^ 64) 2 ^ 32 =. (matematiği biraz daha kolaylaştırmak için işaret bitini görmezden geliyorum ... aslında (uzun) (2 ^ 31.5) = 3037000499 mükemmel kareler)
    Kip

    0

    Carmac yöntemiyle ilgili olarak, sadece bir kez daha yinelemenin oldukça kolay olduğu görülüyor, bu da doğruluk basamaklarının sayısını iki katına çıkarmalıdır. Ne de olsa, son derece kesik bir yinelemeli yöntem - Newton, çok iyi bir ilk tahmin.

    Mevcut en iyi durumunuzla ilgili olarak, iki mikro optimizasyon görüyorum:

    • mod255 kullanarak kontrol ettikten sonra kontrolü vs. 0'a taşıyın
    • olağan (% 75) vaka için tüm kontrolleri atlamak üzere bölücü güçleri yeniden düzenleyin.

    yani:

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

    Daha da iyisi basit olabilir

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

    Açıkçası, her bir kontrol noktasında kaç sayı çıkarıldığını bilmek ilginç olurdu - kontrollerin gerçekten bağımsız olduğundan şüphe duyuyorum, bu da işleri zorlaştırıyor.

    Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
    Licensed under cc by-sa 3.0 with attribution required.