实现基于整数的幂函数pow(int,int)的最有效方法

时间:2008-09-19 12:30:31

标签: c algorithm math exponentiation

将整数提升到C中另一个整数的幂的最有效方法是什么?

// 2^3
pow(2,3) == 8

// 5^5
pow(5,5) == 3125

20 个答案:

答案 0 :(得分:367)

通过平方来表示。

int ipow(int base, int exp)
{
    int result = 1;
    for (;;)
    {
        if (exp & 1)
            result *= base;
        exp >>= 1;
        if (!exp)
            break;
        base *= base;
    }

    return result;
}

这是在非对称加密中对大数进行模幂运算的标准方法。

答案 1 :(得分:61)

请注意,exponentiation by squaring不是最佳方法。作为适用于所有指数值的通用方法,这可能是最好的,但对于特定的指数值,可能会有更好的序列需要更少的乘法。

例如,如果你想计算x ^ 15,通过平方取幂的方法会给你:

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

没有有效的算法来找到这种最佳乘法序列。来自Wikipedia

  

找到最短加法链的问题不能通过动态规划来解决,因为它不满足最优子结构的假设。也就是说,将功率分解成较小的功率是不够的,每个功率都是最小的,因为较小功率的加法链可能是相关的(共享计算)。例如,在上面a¹的最短加法链中,a⁶的子问题必须计算为(a³)²,因为a³被重新使用(相反,例如a⁶=a²(a²)²,这也需要三次乘法)。

答案 2 :(得分:18)

如果你需要加电2。最快的方法就是按功率移动。

2 ** 3 == 1 << 3 == 8
2 ** 30 == 1 << 30 == 1073741824 (A Gigabyte)

答案 3 :(得分:14)

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

答案 4 :(得分:7)

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

答案 5 :(得分:6)

一个非常专业的情况是,当你需要说2 ^( - x到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,其他特殊的取幂情况可能会出现。

答案 6 :(得分:6)

如果你想得到一个2的整数值,那么使用shift选项总是更好:

pow(2,5)可以替换为1<<5

效率更高。

答案 7 :(得分:4)

正如通过平方对关于取幂效率的评论进行跟进。

该方法的优点是它在log(n)时间内运行。例如,如果你要计算一些巨大的东西,比如x ^ 1048575(2 ^ 20 - 1),你只需要通过循环20次,而不是100万+使用天真的方法。

此外,就代码复杂性而言,它比尝试找到最佳的乘法序列更简单,这是la pramod的建议。

编辑:

我想在有人给我标记溢出的可能性之前我应该​​澄清一下。这种方法假设您有某种hugeint库。

答案 8 :(得分:4)

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; } 功能适用于仅限整数

power()

复杂性= O(log(exp))

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 } } 函数适用于负exp和浮动基础

{{1}}

复杂性= O(log(exp))

答案 9 :(得分:2)

晚会:

以下是一个尽可能最好地处理y < 0的解决方案。

  1. 最大范围使用intmax_t的结果。没有提供不适合intmax_t的答案。
  2. powjii(0, 0) --> 1,对于这种情况是common result
  3. 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;
    }
    
  4. 此代码使用永久循环for(;;)来避免其他循环解决方案中的最终base *= base常见问题。乘法是1)不需要2)可能int*int溢出,即UB。

答案 10 :(得分:1)

考虑负指数的更通用的解决方案

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

答案 11 :(得分:1)

Swift 中的 O(log N) 解决方案...

// Time complexity is O(log N)
func power(_ base: Int, _ exp: Int) -> Int { 

    // 1. If the exponent is 1 then return the number (e.g a^1 == a)
    //Time complexity O(1)
    if exp == 1 { 
        return base
    }

    // 2. Calculate the value of the number raised to half of the exponent. This will be used to calculate the final answer by squaring the result (e.g a^2n == (a^n)^2 == a^n * a^n). The idea is that we can do half the amount of work by obtaining a^n and multiplying the result by itself to get a^2n
    //Time complexity O(log N)
    let tempVal = power(base, exp/2) 

    // 3. If the exponent was odd then decompose the result in such a way that it allows you to divide the exponent in two (e.g. a^(2n+1) == a^1 * a^2n == a^1 * a^n * a^n). If the eponent is even then the result must be the base raised to half the exponent squared (e.g. a^2n == a^n * a^n = (a^n)^2).
    //Time complexity O(1)
    return (exp % 2 == 1 ? base : 1) * tempVal * tempVal 

}

答案 12 :(得分:0)

我使用递归,如果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);
   }
}

答案 13 :(得分:0)

我已经实现了记忆所有计算能力的算法,然后在需要时使用它们。因此,例如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);
}

答案 14 :(得分:0)

除了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常量表达式。同样将sqrt()的结果强制转换为int也不是很好,因为在一个完美正方形的情况下,浮点数是精确的,但是我不知道在INT_MAX的任何实现中-或任何类型的最大值-是一个完美的正方形,您都可以忍受。

答案 15 :(得分:0)

另一个实现(在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;
    }       
}

答案 16 :(得分:0)

int pow(int const x, unsigned const e) noexcept
{
  return !e ? 1 : 1 == e ? x : (e % 2 ? x : 1) * pow(x * x, e / 2);
  //return !e ? 1 : 1 == e ? x : (((x ^ 1) & -(e % 2)) ^ 1) * pow(x * x, e / 2);
}

是的,它是递归的,但是一个好的优化编译器会优化递归。

答案 17 :(得分:-1)

我的情况有点不同,我正试图用力量创造一个面具,但我想我会分享我找到的解决方案。

显然,它仅适用于2的权力。

Mask1 = 1 << (Exponent - 1);
Mask2 = Mask1 - 1;
return Mask1 + Mask2;

答案 18 :(得分:-1)

如果您在编译时知道指数(并且它是整数),则可以使用模板来展开循环。这可以提高效率,但我想在此证明基本原则:

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

答案 19 :(得分:-4)

忽略2提升​​为幂的特殊情况,最有效的方法是简单迭代。

int pow(int base, int pow) {
  int res = 1;
  for(int i=pow; i<pow; i++)
    res *= base;

  return res;
}

编辑:正如已经指出的那样,这不是最有效的方式......只要你将效率定义为cpu周期,我认为这是公平的。

相关问题