Cで整数を別の整数の累乗にする最も効率的な方法は何ですか?
// 2^3
pow(2,3) == 8
// 5^5
pow(5,5) == 3125
二乗によるべき乗。
int ipow(int base, int exp)
{
int result = 1;
for (;;)
{
if (exp & 1)
result *= base;
exp >>= 1;
if (!exp)
break;
base *= base;
}
return result;
}
これは、非対称暗号法で膨大な数のモジュラーべき乗を行うための標準的な方法です。
二乗によるべき乗 は最適な方法ではないことに注意してください。すべての指数値に対して機能する一般的な方法としては、おそらく最善の方法ですが、特定の指数値に対しては、必要な乗算が少ない、より良いシーケンスがあるかもしれません。
たとえば、x ^ 15を計算する場合、2乗によるべき乗の方法は次のようになります。
x^15 = (x^7)*(x^7)*x
x^7 = (x^3)*(x^3)*x
x^3 = x*x*x
これは合計6回の乗算です。
これは、 addition-chain exponentiation を介した「ちょうど」5回の乗算を使用して行うことができます。
n*n = n^2
n^2*n = n^3
n^3*n^3 = n^6
n^6*n^6 = n^12
n^12*n^3 = n^15
この最適な乗算シーケンスを見つけるための効率的なアルゴリズムはありません。 From Wikipedia :
最短の加算チェーンを見つける問題は、最適な部分構造の仮定を満たさないため、動的計画法では解決できません。つまり、パワーを小さなパワーに分解するだけでは十分ではありません。小さなパワーの加算チェーンは(計算を共有するために)関連している可能性があるため、それぞれが最小限に計算されます。たとえば、上のa¹⁵の最短加算チェーンでは、a³が再利用されるため、a⁶の副問題は(a³)²として計算する必要があります(たとえば、a⁶=a²(a²)²ではなく、3つの乗算が必要です) )。
2を累乗する必要がある場合。これを行う最も速い方法は、パワーによるビットシフトです。
2 ** 3 == 1 << 3 == 8
2 ** 30 == 1 << 30 == 1073741824 (A Gigabyte)
これがJavaのメソッドです
private int ipow(int base, int exp)
{
int result = 1;
while (exp != 0)
{
if ((exp & 1) == 1)
result *= base;
exp >>= 1;
base *= base;
}
return result;
}
int pow( int base, int exponent)
{ // Does not work for negative exponents. (But that would be leaving the range of int)
if (exponent == 0) return 1; // base case;
int temp = pow(base, exponent/2);
if (exponent % 2 == 0)
return temp * temp;
else
return (base * temp * temp);
}
非常に特殊なケースは、2 ^(-x to y)と言う必要がある場合です。xはもちろん負で、yはintでシフトするには大きすぎます。フロートでねじ込むことで、一定時間で2 ^ xを実行できます。
struct IeeeFloat
{
unsigned int base : 23;
unsigned int exponent : 8;
unsigned int signBit : 1;
};
union IeeeFloatUnion
{
IeeeFloat brokenOut;
float f;
};
inline float twoToThe(char exponent)
{
// notice how the range checking is already done on the exponent var
static IeeeFloatUnion u;
u.f = 2.0;
// Change the exponent part of the float
u.brokenOut.exponent += (exponent - 1);
return (u.f);
}
Doubleをベースタイプとして使用すると、2の累乗をさらに取得できます。 (この投稿を整理する手助けをしてくれたコメント者に感謝します)。
IEEE floats についてさらに学習する可能性もあります。これは、指数のその他の特殊なケースが現れる可能性があります。
2の累乗の整数の値を取得する場合は、shiftオプションを使用することをお勧めします。
pow(2,5)
は1<<5
に置き換えることができます
これははるかに効率的です。
二乗によるべき乗の効率に関するコメントへのフォローアップと同じように。
このアプローチの利点は、log(n)時間で実行されることです。たとえば、x ^ 1048575(2 ^ 20-1)などの巨大なものを計算する場合、単純なアプローチを使用して100万以上ではなく、ループを20回実行するだけで済みます。
また、コードの複雑さの観点からは、最適な乗算シーケンスを見つけようとするよりも簡単です。これは、Pramodの提案です。
編集:
誰かが私にオーバーフローの可能性があるとタグ付けする前に明確にする必要があると思います。このアプローチは、ある種のhugeintライブラリがあることを前提としています。
動作するpower()
関数整数のみ
int power(int base, unsigned int exp){
if (exp == 0)
return 1;
int temp = power(base, exp/2);
if (exp%2 == 0)
return temp*temp;
else
return base*temp*temp;
}
複雑さ= O(log(exp))
負のexpおよびfloat baseで機能するpower()
関数。
float power(float base, int exp) {
if( exp == 0)
return 1;
float temp = power(base, exp/2);
if (exp%2 == 0)
return temp*temp;
else {
if(exp > 0)
return base*temp*temp;
else
return (temp*temp)/base; //negative exponent computation
}
}
複雑さ= O(log(exp))
パーティーに遅れて:
以下は、y < 0
もできる限り最善の方法で処理するソリューションです。
intmax_t
の結果を使用します。 intmax_t
に収まらない回答に対する規定はありません。powjii(0, 0) --> 1
これは 一般的な結果 この場合です。別の未定義の結果であるpow(0,negative)
はINTMAX_MAX
を返します
intmax_t powjii(int x, int y) {
if (y < 0) {
switch (x) {
case 0:
return INTMAX_MAX;
case 1:
return 1;
case -1:
return y % 2 ? -1 : 1;
}
return 0;
}
intmax_t z = 1;
intmax_t base = x;
for (;;) {
if (y % 2) {
z *= base;
}
y /= 2;
if (y == 0) {
break;
}
base *= base;
}
return z;
}
このコードは、永久ループfor(;;)
を使用して、他のループソリューションで一般的な最終的なbase *= base
を回避します。その乗算は1)不要であり、2)UBであるint*int
オーバーフローである可能性があります。
負の指数を考慮したより一般的なソリューション
private static int pow(int base, int exponent) {
int result = 1;
if (exponent == 0)
return result; // base case;
if (exponent < 0)
return 1 / pow(base, -exponent);
int temp = pow(base, exponent / 2);
if (exponent % 2 == 0)
return temp * temp;
else
return (base * temp * temp);
}
コンパイル時に指数(および整数)がわかっている場合は、テンプレートを使用してループを展開できます。これはより効率的にすることができますが、ここで基本的な原理を示したいと思いました。
#include <iostream>
template<unsigned long N>
unsigned long inline exp_unroll(unsigned base) {
return base * exp_unroll<N-1>(base);
}
テンプレートの特殊化を使用して再帰を終了します。
template<>
unsigned long inline exp_unroll<1>(unsigned base) {
return base;
}
実行時に指数を知る必要がありますが、
int main(int argc, char * argv[]) {
std::cout << argv[1] <<"**5= " << exp_unroll<5>(atoi(argv[1])) << ;std::endl;
}
もう1つの実装(Java)。最も効率的なソリューションではないかもしれませんが、反復回数は指数ソリューションと同じです。
public static long pow(long base, long exp){
if(exp ==0){
return 1;
}
if(exp ==1){
return base;
}
if(exp % 2 == 0){
long half = pow(base, exp/2);
return half * half;
}else{
long half = pow(base, (exp -1)/2);
return base * half * half;
}
}
すべての計算されたパワーを記憶し、必要なときにそれらを使用するアルゴリズムを実装しました。したがって、たとえばx ^ 13は(x ^ 2)^ 2 ^ 2 * x ^ 2 ^ 2 * xと等しくなります。ここでx ^ 2 ^ 2は、もう一度計算する代わりにテーブルから取得されます。これは基本的に@Pramod回答の実装です(ただし、C#で)。必要な乗算の数はCeil(Log n)です
public static int Power(int base, int exp)
{
int tab[] = new int[exp + 1];
tab[0] = 1;
tab[1] = base;
return Power(base, exp, tab);
}
public static int Power(int base, int exp, int tab[])
{
if(exp == 0) return 1;
if(exp == 1) return base;
int i = 1;
while(i < exp/2)
{
if(tab[2 * i] <= 0)
tab[2 * i] = tab[i] * tab[i];
i = i << 1;
}
if(exp <= i)
return tab[i];
else return tab[i] * Power(base, exp - i, tab);
}
私の場合は少し異なります。権力からマスクを作成しようとしていますが、とにかく見つけた解決策を共有すると思いました。
明らかに、2のべき乗に対してのみ機能します。
Mask1 = 1 << (Exponent - 1);
Mask2 = Mask1 - 1;
return Mask1 + Mask2;
Expが偶数の場合、5 ^ 10 = 25 ^ 5の場合、再帰を使用します。
int pow(float base,float exp){
if (exp==0)return 1;
else if(exp>0&&exp%2==0){
return pow(base*base,exp/2);
}else if (exp>0&&exp%2!=0){
return base*pow(base,exp-1);
}
}
Eliasの回答に加えて、符号付き整数で実装すると未定義の動作が発生し、符号なし整数で実装すると高入力の値が不正になる
これは、符号付き整数型でも機能し、不正な値を与えない、二乗法による指数の修正版です。
#include <stdint.h>
#define SQRT_INT64_MAX (INT64_C(0xB504F333))
int64_t alx_pow_s64 (int64_t base, uint8_t exp)
{
int_fast64_t base_;
int_fast64_t result;
base_ = base;
if (base_ == 1)
return 1;
if (!exp)
return 1;
if (!base_)
return 0;
result = 1;
if (exp & 1)
result *= base_;
exp >>= 1;
while (exp) {
if (base_ > SQRT_INT64_MAX)
return 0;
base_ *= base_;
if (exp & 1)
result *= base_;
exp >>= 1;
}
return result;
}
この機能に関する考慮事項:
(1 ** N) == 1
(N ** 0) == 1
(0 ** 0) == 1
(0 ** N) == 0
オーバーフローまたはラッピングが発生する場合は、return 0;
int64_t
を使用しましたが、ほとんどの幅(符号付きまたは符号なし)をほとんど変更せずに使用できます。ただし、固定幅以外の整数型を使用する必要がある場合は、SQRT_INT64_MAX
を(int)sqrt(INT_MAX)
(int
を使用する場合)または類似のもので変更する必要がありますが、最適化する必要がありますが、Cではなく、ugいです定数式。また、sqrt()
の結果をint
にキャストすることは、完全な正方形の場合の浮動小数点精度のためあまり良くありませんが、INT_MAX
-または任意の型の最大値-が完全な正方形である実装を知りません、あなたはそれで生きることができます。