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


249

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

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

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

3
当您说“效率”时,您需要指定与效率相关的效率。速度?内存使用情况?代码大小?可维护性?
安迪·莱斯特

C没有pow()函数吗?
jalf

16
是的,但是仅适用于浮点数或双打,而不适用于整数
Nathan Fellman 2009年

1
如果您坚持使用ints(而不是某些huge-int类),则对ipow的许多调用将溢出。这让我想知道是否存在一种聪明的方法来预先计算表并将所有非溢出组合减少为简单的表查找。这将比大多数常规答案占用更多的内存,但在速度方面可能更高效。
阿德里安·麦卡锡

pow()不安全的功能
EsmaeelE

Answers:


391

通过平方求幂。

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

    return result;
}

这是对不对称密码中的大量数字进行模幂运算的标准方法。


38
您可能应该添加一个检查,以确保“ exp”不是负数。当前,此功能将给出错误的答案或永远循环。(取决于在有符号的int上是>> =是否进行零填充或符号扩展-允许C编译器选择任一行为)。
user9876

23
我为此编写了一个更优化的版本,可在此处免费下载:gist.github.com/3551590 在我的计算机上,它的速度提高了约2.5倍。
orlp

10
@AkhilJain:完美的C语言;为了使其在Java中也有效,请分别用和替换while (exp)和。if (exp & 1)while (exp != 0)if ((exp & 1) != 0)
Ilmari Karonen

3
您的函数可能应该具有unsigned exp,否则应exp正确处理负数。
Craig McQueen

5
@ZinanXing乘n次会导致更多的乘法,并且速度较慢。此方法通过有效地重复使用来节省乘法。例如,要计算n ^ 8的朴素方法,需要n*n*n*n*n*n*n*n使用7个乘法。相反m=n*n,此算法只用三个乘法计算,然后o=m*m,然后p=o*o,其中p= n ^ 8。对于较大的指数,性能差异很大。
bames53

68

请注意,通过平方求幂不是最佳方法。作为适用于所有指数值的通用方法,这可能是您最好的选择,但是对于特定的指数值,可能会有更好的序列,需要较少的乘法。

例如,如果要计算x ^ 15,通过平方的求幂方法将为您提供:

x^15 = (x^7)*(x^7)*x 
x^7 = (x^3)*(x^3)*x 
x^3 = x*x*x

总共有6个乘法。

事实证明,这可以通过加法使用“仅” 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

没有找到这种最佳乘法序列的有效算法。从维基百科

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


4
@JeremySalwen:正如这个答案所指出的,二进制取幂通常不是最理想的方法。当前没有有效的算法可找到最小乘法序列。
Eric Postpischil 2013年

2
@EricPostpischil,这取决于您的应用程序。通常,我们不需要通用算法即可处理所有数字。参见计算机编程艺术,第一卷。2:半数值算法
Pacerier 2014年

3
Alexander Stepanov和Daniel Rose 在《从数学到泛型编程》中很好地阐述了这个确切的问题。本书应该摆在所有软件从业者恕我直言的货架上。
Toby Speight 2015年

2
另请参见en.wikipedia.org/wiki/…
lhf

可以对整数进行优化,因为整数幂小于255,不会导致32位整数溢出。您可以为每个int缓存最佳乘法结构。我想代码和数据仍然比仅缓存所有功能要小...
Josiah Yoder 18'Aug

22

如果需要加2的幂。最快的方法是通过电源进行移位。

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

有没有一种优雅的方法可以做到2 ** 0 == 1?
罗布·斯莫特郡,

16
2 ** 0 == 1 << 0 == 1
杰克

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

不适用于大麻木,例如战俘(71045970,41535484)
Anushree Acharjee 2015年

16
@AnushreeAcharjee当然不是。计算这样的数字将需要任意精度的算术。
David Etler

对大数使用BigInteger#modPow或Biginteger#pow,已经实现了基于参数大小的适当算法
Raman Yelianevich


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

不是我的投票,但pow(1, -1)即使指数为负也不会离开int的范围。现在,一个人偶然地工作了pow(-1, -1)
MSalters

可能不会使您离开int范围的唯一负指数是-1。并且仅在base为1或-1时有效。因此,只有两对(base,exp)且exp <0不会导致非整数幂。尽管我是一名数学家并且喜欢量词,但在这种情况下,我认为在实践中可以肯定地说,负指数会使您离开整数领域……
bartgol

6

如果您想将2的整数值提高到某个乘方的幂,最好使用shift选项:

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

这样效率更高。


6

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

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


这与Abhijit Gaikwadchux的答案有何不同?请争论float在第二个代码块中的使用(考虑显示如何power(2.0, -3)计算)。
greybeard,2016年

@greybeard我已经提到了一些评论。可能可以解决您的查询
roottraveller 2016年

1
GNU科学图书馆已经具有您的第二个功能:gnu.org/software/gsl/manual/html_node/Small-integer-powers.html
Cacahuete Frito,

@roottraveller您能否解释negative exp and float base解决方案?为什么我们使用temp,将exp除以2并检查exp(偶数/奇数)?谢谢!
列夫

6

一个非常特殊的情况是,当您需要说2 ^(-y的-x)时,其中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浮动,其他幂运算的特殊情况可能会出现。


漂亮的解决方案,但不容忽视?
paxdiablo

IEEE浮点数是基数x 2 ^ exp,更改指数值不会导致除以2的幂的乘积,而且很有可能会使浮点数
归一

你们都是正确的,我记不清我的解决方案最初是在很久以前写的,用于2的幂。我已将答案重写为该问题的特例解决方案。
Doug T.

首先,代码被引用破坏,需要进行编辑才能编译。其次,使用gcc在core2d上破坏代码。看到这个转储 也许我做错了。但是,我认为这不可行,因为IEEE浮点数以10为底
。–自由空间

3
以10为基数?嗯,这是基数2,除非您在二进制中表示10 :)
Drealmer's

4

就像对平方求幂的效率进行评论的跟进一样。

这种方法的优点是它以log(n)时间运行。例如,如果您要计算巨大的东西,例如x ^ 1048575(2 ^ 20-1),则只需经过20次循环,而无需使用天真的方法进行100万次以上。

而且,就代码复杂度而言,这比尝试找到最佳的乘法序列要简单得多,这是la Pramod的建议。

编辑:

我猜我应该在有人标记我溢出的可能性之前弄清楚。这种方法假定您具有某种hugeint库。


2

晚会:

下面是一个解决方案,也y < 0将尽其所能。

  1. 它使用结果intmax_t表示最大范围。没有规定不适合的答案intmax_t
  2. powjii(0, 0) --> 1这是这种情况的常见结果
  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;
    }

此代码使用永久循环,for(;;)以避免base *= base其他循环解决方案中的最终共同点。乘法是1)不需要,2)可能int*int是UB溢出。


powjii(INT_MAX, 63)导致UB发生base *= base。考虑检查您是否可以乘法,或移至无符号并让其环绕。
Cacahuete Frito

没有理由exp签名。由于在(-1) ** (-N)某些情况下有效,所以任何情况abs(base) > 1都会使代码复杂化,这会使代码复杂化0的负值进行处理exp,因此最好对它进行未签名并保存该代码。
卡卡胡埃特·弗里托(Cacahuete Frito)

1
@CacahueteFrito确实如此 y是的,确实不是真的需要签名,它带来了您所评论的复杂性,但是OP的请求是特定的pow(int, int)。因此,这些好的评论属于OP的问题。由于OP没有指定溢出时的处理方式,因此定义明确的错误答案仅比UB好。考虑到“最有效的方式”,我怀疑OP在乎OF。
chux-恢复莫妮卡

1

考虑负数exp​​onenet的更通用的解决方案

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

1
整数除法得到一个整数,因此您的负指数可能会更有效,因为它只会返回0、1或-1 ...
jswolf19 2014年

pow(i, INT_MIN)可能是一个无限循环。
chux-恢复莫妮卡2015年

1
@chux:它可以格式化硬盘:整数溢出为UB。
MSalters

@MSalters pow(i, INT_MIN)不是整数溢出。该结果的分配temp肯定会溢出,可能会导致时间结束,但是我会选择一个看似随机的值。:-)
chux-恢复莫妮卡2015年

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


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

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()为an int也不是很好,但是我不知道任何实现方式(INT_MAX或任何类型的最大值)是一个完美的平方,接着就,随即。


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

public?2个功能命名相同吗?这是一个C问题。
卡卡胡埃·弗里托(Cacahuete Frito)

-1

我的情况有些不同,我尝试使用电源创建蒙版,但我想我还是会分享我找到的解决方案。

显然,它仅适用于2的幂。

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

我试过了,它不能在64位上使用,它已经偏移了永不返回,在这种情况下,我试图将所有位都设置为小于X(包括X)。
MarcusJ

那是1 << 64吗?那是一个溢出。最大整数正好低于该整数:(1 << 64)-1
–MichaëlRoy

1 << 64 == 0,这就是原因。也许您的代表最适合您的应用。我喜欢的东西,可以放在一个宏,没有额外的变量,比如 #define MASK(e) (((e) >= 64) ? -1 :( (1 << (e)) - 1)),这样可以在编译时计算
麦克罗伊

是的,我知道溢出是什么。仅仅因为我没有使用该词,就不是在不必要地屈服。正如我所说,这对我来说很有效,因此花了点功夫去发现并分享。就这么简单。
MarcusJ

如果我得罪了你,我很抱歉。我真的不是故意的。
麦克罗伊

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

1
显然这不是C ++问题。 (c != c++) == 1
卡卡胡埃特·弗里托(Cacahuete Frito)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.