实现基于整数的幂函数 pow(int, int) 的最有效方法
Posted
技术标签:
【中文标题】实现基于整数的幂函数 pow(int, int) 的最有效方法【英文标题】:The most efficient way to implement an integer based power function pow(int, int) 【发布时间】:2010-09-11 05:09:07 【问题描述】:在 C 中将一个整数乘以另一个整数的幂的最有效方法是什么?
// 2^3
pow(2,3) == 8
// 5^5
pow(5,5) == 3125
【问题讨论】:
当您说“效率”时,您需要指定与什么相关的效率。速度?内存使用情况?代码大小?可维护性? C 没有 pow() 函数吗? 是的,但这适用于浮点数或双精度数,而不适用于整数 如果你坚持使用实际的int
s(而不是一些巨大的int类),很多对ipow的调用都会溢出。这让我想知道是否有一种聪明的方法来预先计算一个表并将所有非溢出组合减少为一个简单的表查找。这将比大多数一般答案占用更多内存,但在速度方面可能更有效。
pow()
不是一个安全函数
【参考方案1】:
平方取幂。
int ipow(int base, int exp)
int result = 1;
for (;;)
if (exp & 1)
result *= base;
exp >>= 1;
if (!exp)
break;
base *= base;
return result;
这是在非对称密码学中对大量数字进行模幂运算的标准方法。
【讨论】:
您可能应该添加一个检查“exp”不是负数。目前,这个函数要么给出错误答案,要么永远循环。 (取决于有符号 int 上的 >>= 是否进行零填充或符号扩展 - 允许 C 编译器选择任何一种行为)。 我写了一个更优化的版本,可以在这里免费下载:gist.github.com/3551590 在我的机器上它大约快 2.5 倍。 @AkhilJain:C 语言非常好;要使其在 Java 中也有效,请将while (exp)
和 if (exp & 1)
分别替换为 while (exp != 0)
和 if ((exp & 1) != 0)
。
你的函数应该有unsigned exp
,否则正确处理负数exp
。
@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,只需三个乘法。指数越大,性能差异越大。【参考方案2】:
请注意,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²)² 相反,它也需要三个乘法)。
【讨论】:
@JeremySalwen:正如这个答案所述,二进制取幂通常不是最佳方法。目前没有已知的有效算法可用于找到最小的乘法序列。 @EricPostpischil,这取决于您的应用程序。通常我们不需要general 算法来处理all 数字。参见计算机编程艺术,卷。 2:半数值算法 Alexander Stepanov 和 Daniel Rose 在 From Mathematics to Generic Programming 中很好地阐述了这个确切的问题。这本书应该放在每个软件从业者的书架上,恕我直言。 另见en.wikipedia.org/wiki/…。 这可以针对整数进行优化,因为远低于 255 的整数幂不会导致 32 位整数溢出。您可以为每个 int 缓存最佳乘法结构。我想代码+数据仍然比简单地缓存所有权力要小......【参考方案3】:如果您需要将 2 提高到一个幂。最快的方法是按幂移位。
2 ** 3 == 1 << 3 == 8
2 ** 30 == 1 << 30 == 1073741824 (A Gigabyte)
【讨论】:
有没有一种优雅的方法来做到这一点,以便 2 ** 0 == 1 ? @RobSmallshire 也许2 ** x = 1 << x
(因为 12 ** x = x ? (1 << x) : 1请注意,2 ** x
在 C 中是有含义的,这不是力量 :)【参考方案4】:
这是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;
【讨论】:
不适用于大数字,例如 pow(71045970,41535484) @AnushreeAcharjee 当然不是。计算这样一个数字需要任意精度的算术。 对大数字使用 BigInteger#modPow 或 Biginteger#pow,已经实现了基于参数大小的适当算法 这不是 Java 问题! @alx 为什么不是 java?【参考方案5】: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)
也是如此。
唯一可能不会让你离开int范围的负指数是-1。它仅在基数为 1 或 -1 时才有效。所以只有两对 (base,exp) exp
【参考方案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()
函数适用于 负 exp 和 float base。
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 Gaikwad 和chux 的回答有何不同?请争论在第二个代码块中使用float
(考虑展示power(2.0, -3)
是如何计算的)。
@greybeard 我已经提到了一些评论。可能是可以解决您的查询
GNU 科学图书馆已经有了你的第二个功能:gnu.org/software/gsl/manual/html_node/Small-integer-powers.html
@roottraveller 你能解释一下negative exp and float base
解决方案吗?为什么我们使用 temp,将 exp 分开 2 并检查 exp(偶数/奇数)?谢谢!【参考方案7】:
一个非常特殊的情况是,当您需要说 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,其他特殊的求幂情况可能会出现。
【讨论】:
漂亮的解决方案,但未签名?? 一个 IEEE 浮点数是基数 x 2 ^ exp,改变指数值只会导致乘以 2 的幂,并且很有可能会使浮点数非规范化......恕我直言,您的解决方案是错误的 你们都对,我记错了我的解决方案最初是写的,哦,很久以前,明确地为 2 的幂。我已将我的答案改写为问题的特殊解决方案。 首先,引用的代码被破坏,需要编辑才能编译。其次,代码在使用 gcc 的 core2d 上被破坏。见this dump也许我做错了什么。然而,我认为这不会起作用,因为 IEEE 浮点指数是以 10 为底的。 以 10 为基数?嗯,不,它是底数 2,除非你的意思是二进制的 10 :)【参考方案8】:如果您想将 2 的整数值提高到某次方的幂,最好使用 shift 选项:
pow(2,5)
可以替换为1<<5
这样效率更高。
【讨论】:
【参考方案9】:正如 cmets 关于平方求幂效率的后续。
这种方法的优点是它在 log(n) 时间内运行。例如,如果你要计算一个巨大的东西,比如 x^1048575 (2^20 - 1),你只需要循环 20 次,而不是使用简单的方法超过 100 万次。
此外,就代码复杂性而言,它比试图找到最佳乘法序列更简单,这是 la Pramod 的建议。
编辑:
我想在有人标记我可能溢出之前我应该澄清一下。这种方法假设您有某种 hugeint 库。
【讨论】:
【参考方案10】:晚会:
下面是一个解决方案,它也尽可能地处理y < 0
。
-
它使用
intmax_t
的结果作为最大范围。没有提供不适合intmax_t
的答案。
powjii(0, 0) --> 1
在这种情况下是 common result。
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)
导致 base *= base
中的 UB。考虑检查你是否可以乘法,或者移动到无符号并让它环绕。
没有理由让exp
被签名。由于(-1) ** (-N)
有效,任何abs(base) > 1
对于0
的负值exp
都将是0
,所以它使代码复杂化,因此最好将其无符号并保存该代码。
@CacahueteFrito 确实不需要 y
签名并带来您评论的复杂性,但 OP 的要求是具体的 pow(int, int)
。因此,那些好的 cmets 属于 OP 的问题。由于 OP 没有指定溢出时要做什么,一个明确定义的错误答案只比 UB 好一点。鉴于“最有效的方式”,我怀疑 OP 是否关心 OF。【参考方案11】:
考虑负指数的更通用的解决方案
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);
【讨论】:
整数除法会产生一个整数,因此您的负指数可能会更有效,因为它只会返回 0、1 或 -1...pow(i, INT_MIN)
可能是一个无限循环。
@chux:它可以格式化你的硬盘:整数溢出是UB。
@MSalters pow(i, INT_MIN)
不是整数溢出。将该结果分配给temp
当然可能会溢出,可能导致end of time,但我会接受一个看似随机的值。 :-)【参考方案12】:
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
【讨论】:
【参考方案13】: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);
是的,它是递归的,但是一个好的优化编译器会优化递归。
【讨论】:
Clang 确实优化了尾递归,但 gcc 不会,除非您替换乘法顺序,即(e % 2 ? x : 1) * pow(x * x, e / 2)
godbolt.org/z/EoWbfx5nc
@Andy 我确实注意到gcc
正在苦苦挣扎,但我不介意,因为我将此函数用作constexpr
函数。【参考方案14】:
另一种实现(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;
【讨论】:
不是 Java 问题!【参考方案15】:我使用递归,如果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);
【讨论】:
【参考方案16】:除了 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
或任何类型的最大值的任何实现- 是一个完美的正方形,你可以忍受它。
【讨论】:
【参考方案17】:我已经实现了一种算法,它可以记住所有计算的幂,然后在需要时使用它们。因此,例如 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 题。【参考方案18】:
我的情况有点不同,我正在尝试用一种力量创造一个面具,但我想我还是会分享我找到的解决方案。
显然,它只适用于 2 的幂。
Mask1 = 1 << (Exponent - 1);
Mask2 = Mask1 - 1;
return Mask1 + Mask2;
【讨论】:
我试过了,它不适用于 64 位,它被移出永远不会返回,在这种特定情况下,我试图将所有位设置为低于 X,包括 X。跨度> 那是 1 1 #define MASK(e) (((e) >= 64) ? -1 :( (1 << (e)) - 1)),这样可以在编译时计算 是的,我知道溢出是什么。仅仅因为我没有使用这个词并不是一种不必要的居高临下的邀请。正如我所说,这对我有用,并且需要一些努力才能发现并分享它。就是这么简单。 如果我冒犯了你,我很抱歉。我真的不是故意的。【参考方案19】:如果您在编译时知道指数(并且它是一个整数),您可以使用模板来展开循环。这可以提高效率,但我想在这里演示基本原理:
#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;
【讨论】:
这显然不是 C++ 问题。(c != c++) == 1
以上是关于实现基于整数的幂函数 pow(int, int) 的最有效方法的主要内容,如果未能解决你的问题,请参考以下文章