long
値が完全な正方形(つまり、その平方根が別の整数)であるかどうかを判断するための最速の方法を探しています。
Math.sqrt()
関数を使用して簡単に実行できましたが、整数のみのドメインに制限することでより高速に実行できる方法があるかどうか疑問に思っています。以下は、私が今やっている非常にシンプルで簡単な方法です。
public final static boolean isPerfectSquare(long n)
{
if (n < 0)
return false;
long tst = (long)(Math.sqrt(n) + 0.5);
return tst*tst == n;
}
注:多くの Project Euler 問題でこの関数を使用しています。したがって、他の誰もこのコードを保守する必要はありません。そして、この種のマイクロ最適化は実際に違いを生む可能性があります。チャレンジの一部は、すべてのアルゴリズムを1分以内に実行することであり、問題によってはこの関数を何百万回も呼び出す必要があるためです。
私は問題のさまざまな解決策を試しました:
0.5
を追加する必要はなく、少なくとも私のマシンでは必要ないことがわかりました。Math.sqrt()
よりもかなり遅かった。これはおそらくMath.sqrt()
がNewtonのメソッドに似たものを使用しているが、ハードウェアに実装されているため、Javaよりもはるかに高速であるためです。また、ニュートン法では、倍精度を使用する必要がありました。Math.sqrt()
。or
ステートメントを使用する方がswitch
を使用するよりも高速ですが、JavaとC#ではor
とswitch
に違いはないようです。or
ステートメントの代わりに、if(lookup[(int)(n&0x3F)]) { test } else return false;
とだけ言います。驚いたことに、これは(わずかに)遅くなりました。これは、 Javaでは配列の境界がチェックされる であるためです。少なくとも私のCPU(x86)とプログラミング言語(C/C++)を使えば、6bits + Carmack + sqrtのコードよりも35%早く動作する方法を考え出しました。あなたの結果は異なるかもしれません。Javaファクタがどのように機能するのか私にはわからないからです。
私のアプローチは3つあります。
int64 x
であることに注意してください)。if( x < 0 || (x&2) || ((x & 7) == 5) || ((x & 11) == 8) )
return false;
if( x == 0 )
return true;
int64 y = x;
y = (y & 4294967295LL) + (y >> 32);
y = (y & 65535) + (y >> 16);
y = (y & 255) + ((y >> 8) & 255) + (y >> 16);
// At this point, y is between 0 and 511. More code can reduce it farther.
実際に残余が正方形かどうかを調べるために、私は事前に計算された表で答えを調べます。if( bad255[y] )
return false;
// However, I just use a table of size 512
if((x & 4294967295LL) == 0)
x >>= 32;
if((x & 65535) == 0)
x >>= 16;
if((x & 255) == 0)
x >>= 8;
if((x & 15) == 0)
x >>= 4;
if((x & 3) == 0)
x >>= 2;
この時点で、数字が正方形になるには、1 mod 8でなければなりません。if((x & 7) != 1)
return false;
ヘンゼルの補題の基本構造は次の通りです。 (注:テストされていないコード。正しく機能しない場合は、t = 2または8を試してください。)int64 t = 4, r = 1;
t <<= 1; r += ((x - r * r) & t) >> 1;
t <<= 1; r += ((x - r * r) & t) >> 1;
t <<= 1; r += ((x - r * r) & t) >> 1;
// Repeat until t is 2^33 or so. Use a loop if you want.
アイデアは、各反復で、xに「現在の」平方根であるrに1ビットを追加するというものです。各平方根は、2の累乗、すなわちt/2で正確な法です。最後に、rとt/2-rはxモジュロt/2の平方根になります。 (rがxの平方根であるなら、-rもそうであることに注意してください。これはモジュロ数でも真実ですが、モジュロいくつかの数では2平方根以上になる可能性があります。私たちの実際の平方根は2 ^ 32より小さいので、その時点で私たちは実際にちょうどrかt/2-rが実平方根であるかどうかをチェックすることができます。私の実際のコードでは、私は以下の修正されたループを使います:int64 r, t, z;
r = start[(x >> 3) & 1023];
do {
z = x - r * r;
if( z == 0 )
return true;
if( z < 0 )
return false;
t = z & (-z);
r += (z & t) >> 1;
if( r > (t >> 1) )
r = t - r;
} while( t <= (1LL << 33) );
ここでのスピードアップは、3つの方法で得られます。事前計算された開始値(ループの10回の繰り返しに相当)、ループの早期終了、およびいくつかのt値のスキップ。最後の部分として、私はz = r - x * x
を見て、tを少しトリックでzを割る2のべき乗になるように設定します。これにより、とにかくrの値に影響を与えなかったであろうt値をスキップすることができます。私の場合の事前計算された開始値は、「最小の正の」平方根モジュロ8192を選び出します。このコードがあなたのために速く動作しないとしても、私はあなたがそれが含んでいるアイデアのいくつかを楽しむことを望みます。事前に計算されたテーブルを含む、完全なテスト済みコードが続きます。
typedef signed long long int int64;
int start[1024] =
{1,3,1769,5,1937,1741,7,1451,479,157,9,91,945,659,1817,11,
1983,707,1321,1211,1071,13,1479,405,415,1501,1609,741,15,339,1703,203,
129,1411,873,1669,17,1715,1145,1835,351,1251,887,1573,975,19,1127,395,
1855,1981,425,453,1105,653,327,21,287,93,713,1691,1935,301,551,587,
257,1277,23,763,1903,1075,1799,1877,223,1437,1783,859,1201,621,25,779,
1727,573,471,1979,815,1293,825,363,159,1315,183,27,241,941,601,971,
385,131,919,901,273,435,647,1493,95,29,1417,805,719,1261,1177,1163,
1599,835,1367,315,1361,1933,1977,747,31,1373,1079,1637,1679,1581,1753,1355,
513,1539,1815,1531,1647,205,505,1109,33,1379,521,1627,1457,1901,1767,1547,
1471,1853,1833,1349,559,1523,967,1131,97,35,1975,795,497,1875,1191,1739,
641,1149,1385,133,529,845,1657,725,161,1309,375,37,463,1555,615,1931,
1343,445,937,1083,1617,883,185,1515,225,1443,1225,869,1423,1235,39,1973,
769,259,489,1797,1391,1485,1287,341,289,99,1271,1701,1713,915,537,1781,
1215,963,41,581,303,243,1337,1899,353,1245,329,1563,753,595,1113,1589,
897,1667,407,635,785,1971,135,43,417,1507,1929,731,207,275,1689,1397,
1087,1725,855,1851,1873,397,1607,1813,481,163,567,101,1167,45,1831,1205,
1025,1021,1303,1029,1135,1331,1017,427,545,1181,1033,933,1969,365,1255,1013,
959,317,1751,187,47,1037,455,1429,609,1571,1463,1765,1009,685,679,821,
1153,387,1897,1403,1041,691,1927,811,673,227,137,1499,49,1005,103,629,
831,1091,1449,1477,1967,1677,697,1045,737,1117,1737,667,911,1325,473,437,
1281,1795,1001,261,879,51,775,1195,801,1635,759,165,1871,1645,1049,245,
703,1597,553,955,209,1779,1849,661,865,291,841,997,1265,1965,1625,53,
1409,893,105,1925,1297,589,377,1579,929,1053,1655,1829,305,1811,1895,139,
575,189,343,709,1711,1139,1095,277,993,1699,55,1435,655,1491,1319,331,
1537,515,791,507,623,1229,1529,1963,1057,355,1545,603,1615,1171,743,523,
447,1219,1239,1723,465,499,57,107,1121,989,951,229,1521,851,167,715,
1665,1923,1687,1157,1553,1869,1415,1749,1185,1763,649,1061,561,531,409,907,
319,1469,1961,59,1455,141,1209,491,1249,419,1847,1893,399,211,985,1099,
1793,765,1513,1275,367,1587,263,1365,1313,925,247,1371,1359,109,1561,1291,
191,61,1065,1605,721,781,1735,875,1377,1827,1353,539,1777,429,1959,1483,
1921,643,617,389,1809,947,889,981,1441,483,1143,293,817,749,1383,1675,
63,1347,169,827,1199,1421,583,1259,1505,861,457,1125,143,1069,807,1867,
2047,2045,279,2043,111,307,2041,597,1569,1891,2039,1957,1103,1389,231,2037,
65,1341,727,837,977,2035,569,1643,1633,547,439,1307,2033,1709,345,1845,
1919,637,1175,379,2031,333,903,213,1697,797,1161,475,1073,2029,921,1653,
193,67,1623,1595,943,1395,1721,2027,1761,1955,1335,357,113,1747,1497,1461,
1791,771,2025,1285,145,973,249,171,1825,611,265,1189,847,1427,2023,1269,
321,1475,1577,69,1233,755,1223,1685,1889,733,1865,2021,1807,1107,1447,1077,
1663,1917,1129,1147,1775,1613,1401,555,1953,2019,631,1243,1329,787,871,885,
449,1213,681,1733,687,115,71,1301,2017,675,969,411,369,467,295,693,
1535,509,233,517,401,1843,1543,939,2015,669,1527,421,591,147,281,501,
577,195,215,699,1489,525,1081,917,1951,2013,73,1253,1551,173,857,309,
1407,899,663,1915,1519,1203,391,1323,1887,739,1673,2011,1585,493,1433,117,
705,1603,1111,965,431,1165,1863,533,1823,605,823,1179,625,813,2009,75,
1279,1789,1559,251,657,563,761,1707,1759,1949,777,347,335,1133,1511,267,
833,1085,2007,1467,1745,1805,711,149,1695,803,1719,485,1295,1453,935,459,
1151,381,1641,1413,1263,77,1913,2005,1631,541,119,1317,1841,1773,359,651,
961,323,1193,197,175,1651,441,235,1567,1885,1481,1947,881,2003,217,843,
1023,1027,745,1019,913,717,1031,1621,1503,867,1015,1115,79,1683,793,1035,
1089,1731,297,1861,2001,1011,1593,619,1439,477,585,283,1039,1363,1369,1227,
895,1661,151,645,1007,1357,121,1237,1375,1821,1911,549,1999,1043,1945,1419,
1217,957,599,571,81,371,1351,1003,1311,931,311,1381,1137,723,1575,1611,
767,253,1047,1787,1169,1997,1273,853,1247,413,1289,1883,177,403,999,1803,
1345,451,1495,1093,1839,269,199,1387,1183,1757,1207,1051,783,83,423,1995,
639,1155,1943,123,751,1459,1671,469,1119,995,393,219,1743,237,153,1909,
1473,1859,1705,1339,337,909,953,1771,1055,349,1993,613,1393,557,729,1717,
511,1533,1257,1541,1425,819,519,85,991,1693,503,1445,433,877,1305,1525,
1601,829,809,325,1583,1549,1991,1941,927,1059,1097,1819,527,1197,1881,1333,
383,125,361,891,495,179,633,299,863,285,1399,987,1487,1517,1639,1141,
1729,579,87,1989,593,1907,839,1557,799,1629,201,155,1649,1837,1063,949,
255,1283,535,773,1681,461,1785,683,735,1123,1801,677,689,1939,487,757,
1857,1987,983,443,1327,1267,313,1173,671,221,695,1509,271,1619,89,565,
127,1405,1431,1659,239,1101,1159,1067,607,1565,905,1755,1231,1299,665,373,
1985,701,1879,1221,849,627,1465,789,543,1187,1591,923,1905,979,1241,181};
bool bad255[512] =
{0,0,1,1,0,1,1,1,1,0,1,1,1,1,1,0,0,1,1,0,1,0,1,1,1,0,1,1,1,1,0,1,
1,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,0,1,1,1,1,0,1,1,1,
0,1,0,1,1,0,0,1,1,1,1,1,0,1,1,1,1,0,1,1,0,0,1,1,1,1,1,1,1,1,0,1,
1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,0,1,1,1,0,1,1,1,1,0,0,1,1,1,1,1,1,
1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,0,1,1,0,1,1,1,1,1,
1,1,1,1,1,1,0,1,1,0,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,
1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,
1,0,1,1,1,0,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,
0,0,1,1,0,1,1,1,1,0,1,1,1,1,1,0,0,1,1,0,1,0,1,1,1,0,1,1,1,1,0,1,
1,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,0,1,1,1,1,0,1,1,1,
0,1,0,1,1,0,0,1,1,1,1,1,0,1,1,1,1,0,1,1,0,0,1,1,1,1,1,1,1,1,0,1,
1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,0,1,1,1,0,1,1,1,1,0,0,1,1,1,1,1,1,
1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,0,1,1,0,1,1,1,1,1,
1,1,1,1,1,1,0,1,1,0,1,0,1,1,0,1,1,1,1,1,1,1,1,1,1,1,0,1,1,0,1,1,
1,1,1,0,0,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,1,
1,0,1,1,1,0,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,
0,0};
inline bool square( int64 x ) {
// Quickfail
if( x < 0 || (x&2) || ((x & 7) == 5) || ((x & 11) == 8) )
return false;
if( x == 0 )
return true;
// Check mod 255 = 3 * 5 * 17, for fun
int64 y = x;
y = (y & 4294967295LL) + (y >> 32);
y = (y & 65535) + (y >> 16);
y = (y & 255) + ((y >> 8) & 255) + (y >> 16);
if( bad255[y] )
return false;
// Divide out powers of 4 using binary search
if((x & 4294967295LL) == 0)
x >>= 32;
if((x & 65535) == 0)
x >>= 16;
if((x & 255) == 0)
x >>= 8;
if((x & 15) == 0)
x >>= 4;
if((x & 3) == 0)
x >>= 2;
if((x & 7) != 1)
return false;
// Compute sqrt using something like Hensel's lemma
int64 r, t, z;
r = start[(x >> 3) & 1023];
do {
z = x - r * r;
if( z == 0 )
return true;
if( z < 0 )
return false;
t = z & (-z);
r += (z & t) >> 1;
if( r > (t >> 1) )
r = t - r;
} while( t <= (1LL << 33) );
return false;
}
パーティーにはかなり遅れていますが、もっと良い答えを出したいと思います。もっと短く(私の ベンチマーク が正しいと仮定しても) もっと速く 。
long goodMask; // 0xC840C04048404040 computed below
{
for (int i=0; i<64; ++i) goodMask |= Long.MIN_VALUE >>> (i*i);
}
public boolean isSquare(long x) {
// This tests if the 6 least significant bits are right.
// Moving the to be tested bit to the highest position saves us masking.
if (goodMask << x >= 0) return false;
final int numberOfTrailingZeros = Long.numberOfTrailingZeros(x);
// Each square ends with an even number of zeros.
if ((numberOfTrailingZeros & 1) != 0) return false;
x >>= numberOfTrailingZeros;
// Now x is either 0 or odd.
// In binary each odd square ends with 001.
// Postpone the sign test until now; handle zero in the branch.
if ((x&7) != 1 | x <= 0) return x == 0;
// Do it in the classical way.
// The correctness is not trivial as the conversion from long to double is lossy!
final long tst = (long) Math.sqrt(x);
return tst * tst == x;
}
最初のテストは、ほとんどの非正方形を素早く捕らえます。これは、長い間詰め込まれた64項目のテーブルを使用するので、配列アクセスのコストはありません(間接および境界チェック)。一様にランダムなlong
の場合、81.25%の確率でここで終わることがあります。
2番目の検定は、因数分解において奇数の2を持つすべての数を受け取ります。メソッドLong.numberOfTrailingZeros
は、JITされて単一のi86命令になるため非常に高速です。
末尾のゼロを削除した後の3番目のテストでは、011、101、または111で終わる2進数の数字を処理しますが、これは完全な正方形ではありません。負の数も考慮し、0も扱います。
最後のテストはdouble
算術に頼ります。 double
は53ビットの仮数しかないので、long
からdouble
への変換は大きな値の丸めを含みます。それにもかかわらず、テストは正しいです( proof が間違っていない限り)。
Mod255のアイデアを取り入れようとしても成功しませんでした。
ベンチマークをする必要があります。最適なアルゴリズムは入力の分布に依存します。
あなたのアルゴリズムはほぼ最適かもしれませんが、あなたはあなたの平方根ルーチンを呼び出す前にいくつかの可能性を除外するために簡単なチェックをしたいと思うかもしれません。たとえば、ビットごとの "and"を実行して、16進数の末尾の数字を調べます。完全な二乗は基数16の0、1、4、または9で終わることができます。したがって、入力の75%(一様に分布していると仮定した場合)では、非常に速いビット回転と引き換えに平方根の呼び出しを避けることができます。
Kipは、ヘックストリックを実装する次のコードのベンチマークを行いました。 1〜100,000,000の数字をテストすると、このコードは元のコードの2倍の速度で実行されました。
public final static boolean isPerfectSquare(long n)
{
if (n < 0)
return false;
switch((int)(n & 0xF))
{
case 0: case 1: case 4: case 9:
long tst = (long)Math.sqrt(n);
return tst*tst == n;
default:
return false;
}
}
C++で類似のコードをテストしたところ、実際にはオリジナルのコードより遅くなっていました。しかし、switchステートメントを削除したときには、もう一度16進数のトリックを使用してコードを2倍高速にしました。
int isPerfectSquare(int n)
{
int h = n & 0xF; // h is the last hex "digit"
if (h > 9)
return 0;
// Use lazy evaluation to jump out of the if statement as soon as possible
if (h != 2 && h != 3 && h != 5 && h != 6 && h != 7 && h != 8)
{
int t = (int) floor( sqrt((double) n) + 0.5 );
return t*t == n;
}
return 0;
}
Switchステートメントを削除してもC#コードにはほとんど影響がありません。
私は数値解析コースで過ごした恐ろしい時間について考えていました。
そして、私は覚えている、私は覚えている、Quakeのソースコードから 'ネットの周りを一周するこの関数があった:
float Q_rsqrt( float number )
{
long i;
float x2, y;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = * ( long * ) &y; // evil floating point bit level hacking
i = 0x5f3759df - ( i >> 1 ); // wtf?
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed
#ifndef Q3_VM
#ifdef __linux__
assert( !isnan(y) ); // bk010122 - FPE?
#endif
#endif
return y;
}
これは基本的にニュートンの近似関数を使って平方根を計算します(厳密な名前は覚えていません)。
それは使用可能であるべきであり、さらに速いかもしれません、それは驚異的なidソフトウェアのゲームの1つからです!
これはC++で書かれていますが、いったんアイデアを得たら、Javaで同じ手法を再利用することはそれほど難しくないはずです。
私はもともとそれを見つけた: http://www.codemaestro.com/reviews/9
ニュートンの方法はウィキペディアで説明されています: http://en.wikipedia.org/wiki/Newton%27s_method
リンクの詳細な説明についてはリンクをたどることができますが、あまり気にしないのであれば、ブログを読んだりNumerical Analysisコースを受講したときによく覚えていることです。
* (long*) &y
は基本的に高速のlong-to-convert関数なので、整数演算を生のバイトに適用することができます。0x5f3759df - (i >> 1);
行は近似関数の事前計算されたシード値です。* (float*) &i
は値を浮動小数点に変換します。y = y * ( threehalfs - ( x2 * y * y ) )
行は再び基本的に関数の上で値を繰り返します。近似関数は、結果に対して関数を反復するほど、より正確な値を与えます。 Quakeの場合、1回の反復で "十分十分"ですが、それがあなたのためではなかった場合...必要なだけ反復を追加することができます。
これは素朴な平方根で実行される除算操作の数を単純な2の除算(実際は* 0.5F
multiply操作)に減らし、代わりに少数の固定数の乗算操作に置き換えるためです。
それがもっと速いのか、それとももっと正確なのかさえわからないが、 John CarmackのMagical Square Root のアルゴリズムを使ってもっと早く平方根を解くことができる。おそらくこれは可能性のある32ビット整数すべてに対してこれを簡単にテストし、実際に正しい結果が得られたことを検証することができます。これは近似値にすぎません。しかし、今考えてみると、doubleを使用することも近似しているので、それがどのように役立つのかわかりません。
あなたが "正しい"平方根を見つけようとするためにバイナリチョップをするならば、あなたはあなたが得た値が言うのに十分に近いかどうかをかなり簡単に検出することができます:
(n+1)^2 = n^2 + 2n + 1
(n-1)^2 = n^2 - 2n + 1
n^2
を計算したので、オプションは次のとおりです。
n^2 = target
:完了、trueを返すn^2 + 2n + 1 > target > n^2
:あなたは親しいです、しかしそれは完璧ではありません:falseを返しますn^2 - 2n + 1 < target < n^2
:同上target < n^2 - 2n + 1
:低いn
上のバイナリチョップtarget > n^2 + 2n + 1
:より高いn
上のバイナリチョップ(申し訳ありませんが、これは現在の推測としてn
を、パラメータにtarget
を使用しています。混乱をお詫び申し上げます。)
これが速くなるかどうかはわかりませんが、試してみる価値があります。
編集:バイナリチョップは整数の全範囲、(2^x)^2 = 2^(2x)
を取り込む必要はないので、一旦あなたがあなたのターゲットのトップセットビットを見つけたら(これは少しひねくれたトリックで行うことができます。 )あなたはすぐに潜在的な答えの範囲を得ることができます。気を付けて、素朴なバイナリチョップはまだ31または32の反復までかかるだけです。
私はこのスレッドでいくつかのアルゴリズムの私自身の分析を実行し、そしていくつかの新しい結果を思い付きました。これらの古い結果はこの回答の編集履歴で見ることができますが、私がミスをしたため正確ではないため、近いものではないいくつかのアルゴリズムを分析するのに時間を浪費しました。しかし、いくつかの異なる答えから教訓を引き出して、私は今このスレッドの「勝者」を粉砕する2つのアルゴリズムを持っています。これが私が他の誰とも違うやり方でやっているコアなことです:
// 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;
しかし、この単純な行は、ほとんどの場合1つか2つの非常に高速な命令を追加するので、switch-case
ステートメントを1つのifステートメントに大幅に単純化します。ただし、テストした数値の多くに2のべき乗係数がある場合は、ランタイムが長くなる可能性があります。
以下のアルゴリズムは以下のとおりです。
これは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
それが最初の100万の長さだけで実行されるなら、そしてここにサンプルランタイムがあります:
0% Scenario{vm=Java, trial=0, benchmark=Internet} 2933380.84 ns; ?=56939.84 ns @ 10 trials
33% Scenario{vm=Java, trial=0, benchmark=Durron} 2243266.81 ns; ?=50537.62 ns @ 10 trials
67% Scenario{vm=Java, trial=0, benchmark=DurronTwo} 3159227.68 ns; ?=10766.22 ns @ 3 trials
benchmark ms linear runtime
Internet 2.93 ===========================
Durron 2.24 =====================
DurronTwo 3.16 ==============================
vm: Java
trial: 0
ご覧のとおり、DurronTwo
name__は非常に頻繁にマジックトリックを使用するようになるため、大規模な入力には適していますが、最初のアルゴリズムやMath.sqrt
に比べて数値が非常に小さいため、壊れてしまいます。その一方で、単純なDurron
name__は、最初の100万個の数字の中で何度も4で割り算する必要がないため、大きな勝者です。
これがDurron
name__です。
public final static boolean isPerfectSquareDurron(long n) {
if(n < 0) return false;
if(n == 0) return true;
long x = n;
// This is faster because a number is divisible by 16 only 6% of the time
// and more than that a vanishingly small percentage.
while((x & 0x3) == 0) x >>= 2;
// This is effectively the same as the switch-case statement used in the original
// answer.
if((x & 0x7) == 1) {
long sqrt;
if(x < 410881L)
{
int i;
float x2, y;
x2 = x * 0.5F;
y = x;
i = Float.floatToRawIntBits(y);
i = 0x5f3759df - ( i >> 1 );
y = Float.intBitsToFloat(i);
y = y * ( 1.5F - ( x2 * y * y ) );
sqrt = (long)(1.0F/y);
} else {
sqrt = (long) Math.sqrt(x);
}
return sqrt*sqrt == x;
}
return false;
}
そしてDurronTwo
name__
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;
}
そして私のベンチマークハーネス:(グーグルキャリパー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);
}
}
更新: いくつかのシナリオでは速く、他のシナリオでは遅い新しいアルゴリズムを作りました。異なる入力に基づいて異なるベンチマークを得ました。 modulo 0xFFFFFF = 3 x 3 x 5 x 7 x 13 x 17 x 241
を計算すると、平方にできない数の97.82%を排除できます。これは(一種の)一行で、5つのビットごとの操作で行うことができます。
if (!goodLookupSquares[(int) ((n & 0xFFFFFFl) + ((n >> 24) & 0xFFFFFFl) + (n >> 48))]) return false;
結果のインデックスは、1)残基、2)残基+ 0xFFFFFF
、または3)残基+ 0x1FFFFFE
のいずれかです。もちろん、剰余数0xFFFFFF
のルックアップテーブルが必要です。これは約3MBのファイルです(この場合はASCIIテキストの10進数として格納されますが、最適ではありませんがByteBuffer
name__などで明らかに改善可能です)。それほど問題ではありません。 ここでファイルを見つけることができます (または自分で生成してください):
public final static boolean isPerfectSquareDurronThree(long n) {
if(n < 0) return false;
if(n == 0) return true;
long x = n;
while((x & 0x3) == 0) x >>= 2;
if((x & 0x7) == 1) {
if (!goodLookupSquares[(int) ((n & 0xFFFFFFl) + ((n >> 24) & 0xFFFFFFl) + (n >> 48))]) return false;
long sqrt;
if(x < 410881L)
{
int i;
float x2, y;
x2 = x * 0.5F;
y = x;
i = Float.floatToRawIntBits(y);
i = 0x5f3759df - ( i >> 1 );
y = Float.intBitsToFloat(i);
y = y * ( 1.5F - ( x2 * y * y ) );
sqrt = (long)(1.0F/y);
} else {
sqrt = (long) Math.sqrt(x);
}
return sqrt*sqrt == x;
}
return false;
}
これをboolean
name__配列にロードします。
private static boolean[] goodLookupSquares = null;
public static void initGoodLookupSquares() throws Exception {
Scanner s = new Scanner(new File("24residues_squares.txt"));
goodLookupSquares = new boolean[0x1FFFFFE];
while(s.hasNextLine()) {
int residue = Integer.valueOf(s.nextLine());
goodLookupSquares[residue] = true;
goodLookupSquares[residue + 0xFFFFFF] = true;
goodLookupSquares[residue + 0x1FFFFFE] = true;
}
s.close();
}
ランタイムの例私が実行したすべてのトライアルでDurron
name__(バージョン1)を上回りました。
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
ニュートン法 を使用して 整数平方根 を計算し、次にこの数を二乗してチェックするほうが、はるかに速いはずです。ニュートンの方法は、他のいくつかの答えで言及されているCarmackソリューションの基礎です。根の整数部分だけに興味があるので、より速い答えを得ることができるはずです。そして、あなたはより早く近似アルゴリズムを止めることができます。
あなたが試すことができるもう一つの最適化: デジタルルート の数字の1、4、7、または9で終わらない場合、その数字は ではなく 完全な二乗です。これはより遅い平方根アルゴリズムを適用する前にあなたの入力の60%を除去するための素早い方法として使用することができます。
この関数をすべての正の64ビット符号付き整数で機能させたい
Math.sqrt()
は入力パラメータとして倍精度を扱うので、 2 ^ 53 より大きい整数に対しては正確な結果が得られません。
記録のためだけに、もう1つのアプローチは素数分解を使用することです。分解のすべての要素が偶数の場合、数は完全な二乗です。それであなたが欲しいのは、数が素数の二乗の積として分解されることができるかどうか見ることです。もちろん、存在するかどうかを確認するためだけに、このような分解を取得する必要はありません。
最初に2 ^ 32より小さい素数の二乗の表を作る。これは、この制限までのすべての整数の表よりはるかに小さいです。
その場合、解決策は次のようになります。
boolean isPerfectSquare(long number)
{
if (number < 0) return false;
if (number < 2) return true;
for (int i = 0; ; i++)
{
long square = squareTable[i];
if (square > number) return false;
while (number % square == 0)
{
number /= square;
}
if (number == 1) return true;
}
}
少々わかりにくいと思います。それがすることは、素数の二乗が入力数を割り算することをあらゆるステップでチェックすることです。もしそうならば、それは素数分解からこの正方形を取り除くために、可能な限り正方形で数を割る。この過程で1になった場合、入力数は素数の2乗の分解です。平方が数自体よりも大きくなると、この平方、またはそれ以上の平方がそれを分割することはできないので、数は素数の平方の分解にはなり得ません。
今日ハードウェアで行われている、そしてここで素数を計算する必要性を考えると、私はこの解決策がもっと遅いと思います。彼の答えでmrzlが言うように、しかしそれは2 ^ 54以上では動作しませんsqrtとの解決よりも良い結果を与えるはずです。
整数問題は整数解に値する。このように
(負でない)整数を二分探索して、t**2 <= n
のような最大の整数tを見つけます。それから正確にr**2 = n
かどうかテストしてください。これには時間O(log n)かかります。
セットが無制限であるために正の整数を二分検索する方法がわからない場合は、簡単です。 2のべき乗で増加する関数f(f(t) = t**2 - n
の上)を計算することから始めます。あなたがそれが前向きになるのを見るとき、あなたは上限を見つけました。それからあなたは標準的な二分探索をすることができます。
完全な正方形の最後のd
は、特定の値しか取り得ないことが指摘されています。 d
の最後のb
(基底n
内の)桁は、n
をb
で割ったときの剰余と同じです。d
すなわち。 C表記法ではn % pow(b, d)
です。
これは任意のモジュラスm
に一般化することができます。 n % m
を使用して、数パーセントの数字を完全な正方形から除外することができます。あなたが現在使用しているモジュラスは64です。可能な正方形として、残りの19%。ちょっとしたコーディングでモジュラス110880を見つけました。可能な正方形としての残りの1.8%。そのため、モジュラス演算(除算)とテーブルルックアップとマシンの平方根のコストによっては、このモジュラスを使用する方が速い場合があります。
ところで、ルックアップテーブル用にパックされたビット配列を格納する方法がJavaにあるのなら、それを使わないでください。 110880 32ビットワードは最近ではそれほどRAMではなく、マシンをフェッチするワードはシングルビットをフェッチするよりも速くなるでしょう。
パフォーマンスのために、あなたは非常にしばしばいくつかの妥協をしなければなりません。他の人はさまざまな方法を表現していますが、Carmackのハックは特定のNの値まで速くなったことに気付きました。ここの答えに。
これは私が思い付くことができる最も速いJava実装です、このスレッドの他の人によって提案されたテクニックの組み合わせを使って。
私もこれらの修正を試しましたが、それらはパフォーマンスには役立ちませんでした。
public class SquareTester {
public static boolean isPerfectSquare(long n) {
if (n < 0) {
return false;
} else {
switch ((byte) n) {
case -128: case -127: case -124: case -119: case -112:
case -111: case -103: case -95: case -92: case -87:
case -79: case -71: case -64: case -63: case -60:
case -55: case -47: case -39: case -31: case -28:
case -23: case -15: case -7: case 0: case 1:
case 4: case 9: case 16: case 17: case 25:
case 33: case 36: case 41: case 49: case 57:
case 64: case 65: case 68: case 73: case 81:
case 89: case 97: case 100: case 105: case 113:
case 121:
long i = (n * INV3465) >>> 52;
if (! good3465[(int) i]) {
return false;
} else {
long r = round(Math.sqrt(n));
return r*r == n;
}
default:
return false;
}
}
}
private static int round(double x) {
return (int) Double.doubleToRawLongBits(x + (double) (1L << 52));
}
/** 3465<sup>-1</sup> modulo 2<sup>64</sup> */
private static final long INV3465 = 0x8ffed161732e78b9L;
private static final boolean[] good3465 =
new boolean[0x1000];
static {
for (int r = 0; r < 3465; ++ r) {
int i = (int) ((r * r * INV3465) >>> 52);
good3465[i] = good3465[i+1] = true;
}
}
}
以下のmaaartinusのソリューションの単純化は、ランタイムを数パーセントポイント短縮するように見えますが、私が信頼できるベンチマークを作成するにはベンチマークが十分ではありません。
long goodMask; // 0xC840C04048404040 computed below
{
for (int i=0; i<64; ++i) goodMask |= Long.MIN_VALUE >>> (i*i);
}
public boolean isSquare(long x) {
// This tests if the 6 least significant bits are right.
// Moving the to be tested bit to the highest position saves us masking.
if (goodMask << x >= 0) return false;
// Remove an even number of trailing zeros, leaving at most one.
x >>= (Long.numberOfTrailingZeros(x) & (-2);
// Repeat the test on the 6 least significant remaining bits.
if (goodMask << x >= 0 | x <= 0) return x == 0;
// Do it in the classical way.
// The correctness is not trivial as the conversion from long to double is lossy!
final long tst = (long) Math.sqrt(x);
return tst * tst == x;
}
最初のテストをどれだけ省略したかをチェックする価値があります。
if (goodMask << x >= 0) return false;
パフォーマンスに影響を与えます。
最初からNの2のべき乗部分を取り除くべきです。
第2編集 下のmの不思議な表現は
m = N - (N & (N-1));
そして書かれたように
第2編集の終わり
m = N & (N-1); // the lawest bit of N
N /= m;
byte = N & 0x0F;
if ((m % 2) || (byte !=1 && byte !=9))
return false;
第1回編集:
軽微な改善
m = N & (N-1); // the lawest bit of N
N /= m;
if ((m % 2) || (N & 0x07 != 1))
return false;
最初の編集の終わり
今もいつものように続けます。このようにして、浮動小数点部分にたどり着く頃には、2のべき乗部分が奇数(約半分)であるすべての数をすでに取り除いており、残りの1/8しか考慮していません。すなわちあなたは数の6%で浮動小数点部分を実行します。
私はいくつかの入力に対してほぼ正しい方法を使うという考えが好きです。これは、より高い「オフセット」を持つバージョンです。コードは動作しているようで、私の簡単なテストケースに合格しています。
交換するだけです。
if(n < 410881L){...}
これを使ったコード:
if (n < 11043908100L) {
//John Carmack hack, converted to Java.
// See: http://www.codemaestro.com/reviews/9
int i;
float x2, y;
x2 = n * 0.5F;
y = n;
i = Float.floatToRawIntBits(y);
//using the magic number from
//http://www.lomont.org/Math/Papers/2003/InvSqrt.pdf
//since it more accurate
i = 0x5f375a86 - (i >> 1);
y = Float.intBitsToFloat(i);
y = y * (1.5F - (x2 * y * y));
y = y * (1.5F - (x2 * y * y)); //Newton iteration, more accurate
sqrt = Math.round(1.0F / y);
} else {
//Carmack hack gives incorrect answer for n >= 11043908100.
sqrt = (long) Math.sqrt(n);
}
Rubyでは、これは10進数から古いMarchant計算機アルゴリズムの2進数への手直し(すみません、私は参照を持っていません)、特にこの質問に適応しました:
def isexactsqrt(v)
value = v.abs
residue = value
root = 0
onebit = 1
onebit <<= 8 while (onebit < residue)
onebit >>= 2 while (onebit > residue)
while (onebit > 0)
x = root + onebit
if (residue >= x) then
residue -= x
root = x + onebit
end
root >>= 1
onebit >>= 2
end
return (residue == 0)
end
ここに似たようなものの後処理があります(コーディングスタイル/匂いや不格好なO/Oのために私を投票しないでください - それは重要なアルゴリズムです、そしてC++は私の母国語ではありません)。この場合、我々は残留物== 0を探しています:
#include <iostream>
using namespace std;
typedef unsigned long long int llint;
class ISqrt { // Integer Square Root
llint value; // Integer whose square root is required
llint root; // Result: floor(sqrt(value))
llint residue; // Result: value-root*root
llint onebit, x; // Working bit, working value
public:
ISqrt(llint v = 2) { // Constructor
Root(v); // Take the root
};
llint Root(llint r) { // Resets and calculates new square root
value = r; // Store input
residue = value; // Initialise for subtracting down
root = 0; // Clear root accumulator
onebit = 1; // Calculate start value of counter
onebit <<= (8*sizeof(llint)-2); // Set up counter bit as greatest odd power of 2
while (onebit > residue) {onebit >>= 2; }; // Shift down until just < value
while (onebit > 0) {
x = root ^ onebit; // Will check root+1bit (root bit corresponding to onebit is always zero)
if (residue >= x) { // Room to subtract?
residue -= x; // Yes - deduct from residue
root = x + onebit; // and step root
};
root >>= 1;
onebit >>= 2;
};
return root;
};
llint Residue() { // Returns residue from last calculation
return residue;
};
};
int main() {
llint big, i, q, r, v, delta;
big = 0; big = (big-1); // Kludge for "big number"
ISqrt b; // Make q sqrt generator
for ( i = big; i > 0 ; i /= 7 ) { // for several numbers
q = b.Root(i); // Get the square root
r = b.Residue(); // Get the residue
v = q*q+r; // Recalc original value
delta = v-i; // And diff, hopefully 0
cout << i << ": " << q << " ++ " << r << " V: " << v << " Delta: " << delta << "\n";
};
return 0;
};
Project Eulerはタグで言及されており、その中の問題の多くは数字>> 2 ^ 64をチェックする必要があります。 80バイトのバッファで作業している場合、上記の最適化のほとんどはうまくいきません。
私はJava BigIntegerとニュートンの方法のわずかに修正されたバージョンを使いました。 n ^ 2-1 =(n-1)(n + 1)で、最終的な誤差は最終的な約数の1ステップ下にあるため、正確な平方n ^ 2はnではなく(n-1)に収束します。アルゴリズムは終了しました。エラーを計算する前に、元の引数に1を加えることで修正するのは簡単でした。 (立方根などの場合は2を追加します)
このアルゴリズムの1つのいいところは、数が完全な2乗であるかどうかをすぐに見分けることができるということです。ニュートン法の最終誤差(補正ではない)はゼロになります。簡単な修正で、最も近い整数の代わりにすばやくfloor(sqrt(x))を計算することもできます。これはいくつかのオイラー問題で便利です。
前述したように、sqrt呼び出しは完全に正確ではありませんが、速度の点で他の答えを失うことはないということは興味深いし、有益です。結局のところ、sqrtのためのアセンブリ言語命令のシーケンスはごくわずかです。 Intelにはハードウェア命令がありますが、これはIEEEに準拠していないため、Javaでは使用されていません。
それで、なぜそれは遅いのですか?なぜなら、Javaは実際にはJNIを介してCルーチンを呼び出しているためです。そして、Javaサブルーチンを呼び出すよりも実際に呼び出した方が遅くなります。これは非常に面倒であり、Javaはより良い解決策、すなわち必要なら浮動小数点ライブラリ呼び出しを組み込むことを考え出すべきでした。しかたがない。
C++では、すべての複雑な選択肢がスピードを失うことになると思いますが、それらすべてをチェックしたことはありません。私がしたこと、そしてJavaの人々が役に立つと思うことは、単純なハック、A. Rexによって提案された特別なケーステストの拡張です。境界チェックされていない単一のlong値をビット配列として使用します。そうすれば、64ビットのブール値検索ができます。
typedef unsigned long long UVLONG
UVLONG pp1,pp2;
void init2() {
for (int i = 0; i < 64; i++) {
for (int j = 0; j < 64; j++)
if (isPerfectSquare(i * 64 + j)) {
pp1 |= (1 << j);
pp2 |= (1 << i);
break;
}
}
cout << "pp1=" << pp1 << "," << pp2 << "\n";
}
inline bool isPerfectSquare5(UVLONG x) {
return pp1 & (1 << (x & 0x3F)) ? isPerfectSquare(x) : false;
}
ルーチンisPerfectSquare5は私のcore2デュオマシンでは約1/3の時間で動作します。同じ線に沿ってさらに調整すると、平均でさらに時間が短縮される可能性がありますが、確認するたびに、さらに多くのテストを排除することで妥協しているため、それ以上先に進むことはできません。
確かに、負の値を別にテストするのではなく、上位6ビットを同じ方法でチェックできます。
私がしていることは、可能な四角形を排除することだけですが、可能性がある場合は、元のインラインのisPerfectSquareを呼び出さなければなりません。
Init2ルーチンは、pp1とpp2の静的値を初期化するために一度呼び出されます。 C++での私の実装では、私はunsigned long longを使っているので、あなたが署名しているので、あなたは>>>演算子を使わなければならないでしょう。
配列をチェックするための本質的な必要性はありませんが、Javaのオプティマイザはこれをかなり迅速に把握しなければならないので、それらを非難しません。
正方形の最後のnビットが観察されたとき、私はすべての可能な結果をチェックしました。さらに多くのビットを連続して調べることにより、最大5/6の入力を除去することができます。私は実際にFermatの因数分解アルゴリズムを実装するためにこれを設計しました、そしてそれはそこで非常に速いです。
public static boolean isSquare(final long val) {
if ((val & 2) == 2 || (val & 7) == 5) {
return false;
}
if ((val & 11) == 8 || (val & 31) == 20) {
return false;
}
if ((val & 47) == 32 || (val & 127) == 80) {
return false;
}
if ((val & 191) == 128 || (val & 511) == 320) {
return false;
}
// if((val & a == b) || (val & c == d){
// return false;
// }
if (!modSq[(int) (val % modSq.length)]) {
return false;
}
final long root = (long) Math.sqrt(val);
return root * root == val;
}
疑似コードの最後のビットを使用して、テストを拡張して値を増やすことができます。上記の検定は、k = 0、1、2、3に対するものです。
最初に2のべき乗の係数を持つ二乗残差があるかどうかをテストしてから、最終的な法に基づいてテストし、次にMath.sqrtを使って最終的なテストを行います。私はトップのポストからアイデアを思い付き、それを拡張しようとしました。コメントや提案をいただければ幸いです。
更新: 44352のモジュラス(modSq)とモジュラスベースによるテストを使用して、私のテストは1,000,000,000までの数のためのOPの更新におけるものの時間の96%で実行されます。
一般的なビット長を考慮して(ここでは特定の型を使用していますが)、以下のように単純化したアルゴを設計しようとしました。最初は、0、1、2、または<0の単純で明白なチェックが必要です。以下は既存の数学関数を使用しようとしないという意味で単純です。ほとんどの演算子をビット単位の演算子に置き換えることができます。私はベンチマークデータでテストしていません。私は数学もコンピュータアルゴリズム設計も特に専門家ではありません。問題を指摘してくれるのを見たいです。私はそこに多くの改善の可能性があることを知っています。
int main()
{
unsigned int c1=0 ,c2 = 0;
unsigned int x = 0;
unsigned int p = 0;
int k1 = 0;
scanf("%d",&p);
if(p % 2 == 0) {
x = p/2;
}
else {
x = (p/2) +1;
}
while(x)
{
if((x*x) > p) {
c1 = x;
x = x/2;
}else {
c2 = x;
break;
}
}
if((p%2) != 0)
c2++;
while(c2 < c1)
{
if((c2 * c2 ) == p) {
k1 = 1;
break;
}
c2++;
}
if(k1)
printf("\n Perfect square for %d", c2);
else
printf("\n Not perfect but nearest to :%d :", c2);
return 0;
}
これが以前に言及されたことがあるかどうかわかりません。しかし、私は解決策を見つけました ここ /:
int result = (int)(floor(sqrt(b)) - ceil(sqrt(a)) + 1);
速度が問題になる場合は、最も一般的に使用される一連の入力とその値をルックアップテーブルに分割してから、例外的な場合に備えて最適化されたマジックアルゴリズムを実行してください。
これが最も単純で最も簡潔な方法ですが、CPUサイクルの観点から比較するとわかりません。根が整数であるかどうかだけを知りたいのであれば、これはとてもうまくいきます。それが整数であるかどうかを本当に気にしているのであれば、それも理解できます。これは単純な(そして純粋な)関数です。
public static boolean isRootWhole(double number) {
return Math.sqrt(number) % 1 == 0;
}
マイクロ最適化が不要な場合は、この答えが単純さと保守性の点で優れています。負の数になる場合は、おそらく、number引数にMath.abs()をMath.sqrt()引数として使用します。
私の3.6GHz Intel i7-4790 CPUでは、0から10,000,000のこのアルゴリズムを実行すると、1回の計算で平均35 - 37ナノ秒かかりました。私は10回連続して実行し、1000万平方メートルの計算それぞれに費やされた平均時間を印刷しました。それぞれのトータルランは、完了するまでに600ミリ秒強かかりました。
あなたがより少ない数の計算を実行しているならば、より早い計算は少し長くかかります。
「最後のX桁がNであれば完全な正方形にはなり得ない」をそれよりはるかに効率的にまとめることが可能であるはずです。 Java 32ビット整数を使用して、数値の最後の16ビットをチェックするのに十分なデータを作成します。これは、2048の16進整数値です。
...
OK。私はもう少し大きい数論に出くわしたか、あるいは私のコードにバグがあります。いずれにせよ、ここにコードがあります:
public static void main(String[] args) {
final int BITS = 16;
BitSet foo = new BitSet();
for(int i = 0; i< (1<<BITS); i++) {
int sq = (i*i);
sq = sq & ((1<<BITS)-1);
foo.set(sq);
}
System.out.println("int[] mayBeASquare = {");
for(int i = 0; i< 1<<(BITS-5); i++) {
int kk = 0;
for(int j = 0; j<32; j++) {
if(foo.get((i << 5) | j)) {
kk |= 1<<j;
}
}
System.out.print("0x" + Integer.toHexString(kk) + ", ");
if(i%8 == 7) System.out.println();
}
System.out.println("};");
}
これが結果です。
(ed:prettify.jsのパフォーマンスが悪いために削除されました。改訂履歴を見るために参照してください。)
問題のための最良のアルゴリズムは高速整数平方根アルゴリズムであるかもしれません https://stackoverflow.com/a/51585204/5191852
そこに@Kdeは、ニュートン法の3回の反復が32ビット整数に対して±1の精度のために十分であると主張している。確かに、64ビット整数にはもっと多くの反復が必要であり、6または7であるかもしれません。
Carmac法に関しては、もう一度繰り返すことは非常に簡単であるように思えます。これは精度の桁数を2倍にするはずです。結局のところ、それは非常に切り捨てられた反復法 - ニュートンの、非常に良い最初の推測があります。
あなたの現在の最善に関して、私は2つのマイクロ最適化を見ます:
イー:
// Divide out powers of 4 using binary search
if((n & 0x3L) == 0) {
n >>=2;
if((n & 0xffffffffL) == 0)
n >>= 32;
if((n & 0xffffL) == 0)
n >>= 16;
if((n & 0xffL) == 0)
n >>= 8;
if((n & 0xfL) == 0)
n >>= 4;
if((n & 0x3L) == 0)
n >>= 2;
}
さらに良いのは簡単かもしれません
while ((n & 0x03L) == 0) n >>= 2;
明らかに、各チェックポイントで何個の数値が間引かれるのかを知っているのは面白いでしょう。チェックが本当に独立しているのではないかと思います。
整数以外の操作を避けたい場合は、以下の方法を使用できます。それは基本的に整数算術のために修正されたニュートンの方法を使います。
/**
* Test if the given number is a perfect square.
* @param n Must be greater than 0 and less
* than Long.MAX_VALUE.
* @return <code>true</code> if n is a perfect
* square, or <code>false</code> otherwise.
*/
public static boolean isSquare(long n)
{
long x1 = n;
long x2 = 1L;
while (x1 > x2)
{
x1 = (x1 + x2) / 2L;
x2 = n / x1;
}
return x1 == x2 && n % x1 == 0L;
}
この実装はMath.sqrt
を使用するソリューションと競合することはできません。しかし、その性能は他の記事のいくつかで説明されているフィルタリングメカニズムを使用することによって改善することができます。
これが分割統治法です。
自然数(number
)の平方根が自然数(solution
)の場合、solution
の桁数に基づいて、number
の範囲を簡単に決定できます。
number
の桁数は1です。solution
の範囲= 1 - 4number
の桁数は2桁です。solution
の範囲= 3 - 10number
の桁数は3桁です。solution
の範囲= 10 - 40number
の桁数は4桁です。solution
の範囲= 30 - 100number
の桁数は5桁です。solution
の範囲= 100 - 400繰り返しに注意してください。
バイナリサーチアプローチでこの範囲を使用して、以下の目的でsolution
があるかどうかを確認できます。
number == solution * solution
これがコードです
これが私のクラスですSquareRootChecker
public class SquareRootChecker {
private long number;
private long initialLow;
private long initialHigh;
public SquareRootChecker(long number) {
this.number = number;
initialLow = 1;
initialHigh = 4;
if (Long.toString(number).length() % 2 == 0) {
initialLow = 3;
initialHigh = 10;
}
for (long i = 0; i < Long.toString(number).length() / 2; i++) {
initialLow *= 10;
initialHigh *= 10;
}
if (Long.toString(number).length() % 2 == 0) {
initialLow /= 10;
initialHigh /=10;
}
}
public boolean checkSquareRoot() {
return findSquareRoot(initialLow, initialHigh, number);
}
private boolean findSquareRoot(long low, long high, long number) {
long check = low + (high - low) / 2;
if (high >= low) {
if (number == check * check) {
return true;
}
else if (number < check * check) {
high = check - 1;
return findSquareRoot(low, high, number);
}
else {
low = check + 1;
return findSquareRoot(low, high, number);
}
}
return false;
}
}
そしてこれがその使い方の例です。
long number = 1234567;
long square = number * number;
SquareRootChecker squareRootChecker = new SquareRootChecker(square);
System.out.println(square + ": " + squareRootChecker.checkSquareRoot()); //Prints "1524155677489: true"
long notSquare = square + 1;
squareRootChecker = new SquareRootChecker(notSquare);
System.out.println(notSquare + ": " + squareRootChecker.checkSquareRoot()); //Prints "1524155677490: false"
整数が有限のサイズであることを考えれば、スピードが欲しいなら、(a)パラメータをサイズで(例えば、最大ビットセットでカテゴリに)分割し、それから値を完全な二乗の配列に対してチェックすることその範囲内です。
これが最速の方法であるかどうかわからないが、これは私が数学の授業中に私の計算機で遊んでいたときに私が(昔は高校時代に)遭遇したものである。その当時、私は本当にこれが働いていたことに驚きました...
public static boolean isIntRoot(int number) {
return isIntRootHelper(number, 1);
}
private static boolean isIntRootHelper(int number, int index) {
if (number == index) {
return true;
}
if (number < index) {
return false;
}
else {
return isIntRootHelper(number - 2 * index, index + 1);
}
}
「長い値が完全な平方であるかどうかを判断する最も早い方法を探しています(つまり、その平方根は別の整数です)」
答えは印象的ですが、私は簡単なチェックを見ることができませんでした:
長い方の右側の最初の数字がセットのメンバーであるかどうかをチェックします(0,1,4,5,6,9)。そうでない場合、それはおそらく「完全な正方形」になることはできません。
例えば。
4567 - 完璧な正方形にはなれない.
ニュートン法による平方根の計算は非常に高速です...開始値が妥当であれば。しかし、妥当な初期値はなく、実際には二分法とlog(2 ^ 64)法で終わります。
本当に速くするためには、合理的な初期値に到達するための迅速な方法が必要です。それは、機械語に降りる必要があることを意味します。プロセッサがPentiumでPOPCNTのような命令を提供する場合、それは先行ゼロをカウントし、それを使用して、有効ビットの半分の開始値を得ることができます。注意を払って、常に十分な数のニュートンステップを見つけることができます。 (このようにして、ループして非常に高速に実行する必要があります。)
2番目の解決策は(i87コプロセッサのような)高速なsqrt計算を持つかもしれない浮動小数点機能を経由することです。exp()とlog()によるエクスカーションでさえニュートンが二分探索に縮退するより速いかもしれません。これには注意が必要な点があります。後で何を改善するのか、そして後で改善する必要があるのかをプロセッサに依存して分析する必要があります。
3番目の解決策はわずかに異なる問題を解決しますが、状況が問題で説明されているので言及する価値があります。わずかに異なる数に対して非常に多くの平方根を計算したい場合は、開始値を再初期化しないでニュートン反復を使用することができますが、前の計算を中断したところでそのままにします。私はこれを少なくとも1つのEuler問題で成功裡に使った。