数値のn乗根を見つけるための効率的なアルゴリズムを探しています。答えは整数でなければなりません。ニュートン法と二分法が一般的な方法であることがわかりました。整数出力のための効率的で簡単な方法はありますか?
#include <math.h>
inline int root(int input, int n)
{
return round(pow(input, 1./n));
}
これはほぼ整数範囲全体で機能します(IEEE7548バイトのdouble
sは32ビットのint
範囲全体を正確に表すことができるため、ほとんどすべての言語で使用される表現とサイズです。システム)。そして、整数ベースのアルゴリズムが古代以外のハードウェアでより高速であるとは思えません。 ARMを含みます。ただし、組み込みコントローラー(電子レンジ洗濯機の種類)には浮動小数点ハードウェアがない場合があります。しかし、質問のその部分は特定されていませんでした。
私はこのスレッドがおそらく死んでいることを知っています、しかし私は私が好きな答えを見ません、そしてそれは私を悩ませます...
int root(int a, int n) {
int v = 1, bit, tp, t;
if (n == 0) return 0; //error: zeroth root is indeterminate!
if (n == 1) return a;
tp = iPow(v,n);
while (tp < a) { // first power of two such that v**n >= a
v <<= 1;
tp = iPow(v,n);
}
if (tp == a) return v; // answer is a power of two
v >>= 1;
bit = v >> 1;
tp = iPow(v, n); // v is highest power of two such that v**n < a
while (a > tp) {
v += bit; // add bit to value
t = iPow(v, n);
if (t > a) v -= bit; // did we add too much?
else tp = t;
if ( (bit >>= 1) == 0) break;
}
return v; // closest integer such that v**n <= a
}
// used by root function...
int iPow(int a, int e) {
int r = 1;
if (e == 0) return r;
while (e != 0) {
if ((e & 1) == 1) r *= a;
e >>= 1;
a *= a;
}
return r;
}
このメソッドは、sqrt(2)のようなものを小数点以下100桁まで計算する場合に備えて、任意精度の固定小数点演算でも機能します。
Cプログラム と言えば、 " algorithm "の使用に疑問があります。プログラムとアルゴリズムは同じではありません(アルゴリズムは数学的なものです。Cプログラムは実装何らかのアルゴリズムであることが期待されます)。
しかし、現在のプロセッサ(最近のx86-64ラップトップやデスクトップなど)では、 [〜#〜] fpu [〜#〜] はかなりうまくいっています。 n番目のルートを計算する高速な方法は次のようになると思います(ただしベンチマークはしませんでした)。
inline unsigned root(unsigned x, unsigned n) {
switch (n) {
case 0: return 1;
case 1: return x;
case 2: return (unsigned)sqrt((double)x);
case 3: return (unsigned)cbrt((double)x);
default: return (unsigned) pow (x, 1.0/n);
}
}
(多くのプロセッサにはsqrt
を計算するハードウェアがあり、一部のプロセッサにはcbrt
...を計算するハードウェアがあるため、切り替えを行いました。したがって、関連する場合はこれらを優先する必要があります...)。
負の数のn乗根が一般的に意味があるかどうかはわかりません。したがって、私のroot
関数はいくつかのunsigned x
そしていくつかのunsigned
数を返します。
これは、Cでの効率的な一般的な実装であり、「シフトn番目のルートアルゴリズム」の簡略化されたバージョンを使用して、nのxのルートのフロアを計算します。
uint64_t iroot(const uint64_t x, const unsigned n)
{
if ((x == 0) || (n == 0)) return 0;
if (n == 1) return x;
uint64_t r = 1;
for (int s = ((ilog2(x) / n) * n) - n; s >= 0; s -= n)
{
r <<= 1;
r |= (ipow(r|1, n) <= (x >> s));
}
return r;
}
nのxの累乗を計算するには、この関数が必要です(2乗による指数化の方法を使用)。
uint64_t ipow(uint64_t x, unsigned n)
{
if (x <= 1) return x;
uint64_t y = 1;
for (; n != 0; n >>= 1, x *= x)
if (n & 1)
y *= x;
return y;
}
そして、この関数は、xの2を底とする対数のフロアを計算します。
int ilog2(uint64_t x)
{
#if __has_builtin(__builtin_clzll)
return 63 - ((x != 0) * (int)__builtin_clzll(x)) - ((x == 0) * 64);
#else
int y = -(x == 0);
for (unsigned k = 64 / 2; k != 0; k /= 2)
if ((x >> k) != 0)
{ x >>= k; y += k; }
return y;
#endif
}
注:これは、コンパイラーがGCCの__has_builtin
テストを理解し、コンパイラーのuint64_t
タイプがunsigned long long
と同じサイズであることを前提としています。