将整数提升为C中另一个整数的幂的最有效方法是什么?
// 2^3
pow(2,3) == 8
// 5^5
pow(5,5) == 3125
int
s(而不是某些huge-int类),则对ipow的许多调用将溢出。这让我想知道是否存在一种聪明的方法来预先计算表并将所有非溢出组合减少为简单的表查找。这将比大多数常规答案占用更多的内存,但在速度方面可能更高效。
pow()
不安全的功能
将整数提升为C中另一个整数的幂的最有效方法是什么?
// 2^3
pow(2,3) == 8
// 5^5
pow(5,5) == 3125
int
s(而不是某些huge-int类),则对ipow的许多调用将溢出。这让我想知道是否存在一种聪明的方法来预先计算表并将所有非溢出组合减少为简单的表查找。这将比大多数常规答案占用更多的内存,但在速度方面可能更高效。
pow()
不安全的功能
Answers:
通过平方求幂。
int ipow(int base, int exp)
{
int result = 1;
for (;;)
{
if (exp & 1)
result *= base;
exp >>= 1;
if (!exp)
break;
base *= base;
}
return result;
}
这是对不对称密码中的大量数字进行模幂运算的标准方法。
while (exp)
和。if (exp & 1)
while (exp != 0)
if ((exp & 1) != 0)
unsigned exp
,否则应exp
正确处理负数。
n*n*n*n*n*n*n*n
使用7个乘法。相反m=n*n
,此算法只用三个乘法计算,然后o=m*m
,然后p=o*o
,其中p
= n ^ 8。对于较大的指数,性能差异很大。
请注意,通过平方求幂不是最佳方法。作为适用于所有指数值的通用方法,这可能是您最好的选择,但是对于特定的指数值,可能会有更好的序列,需要较少的乘法。
例如,如果要计算x ^ 15,通过平方的求幂方法将为您提供:
x^15 = (x^7)*(x^7)*x
x^7 = (x^3)*(x^3)*x
x^3 = x*x*x
总共有6个乘法。
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²)²,这也需要三个乘法) )。
这是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);
}
pow(1, -1)
即使指数为负也不会离开int的范围。现在,一个人偶然地工作了pow(-1, -1)
。
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))
negative exp and float base
解决方案?为什么我们使用temp,将exp除以2并检查exp(偶数/奇数)?谢谢!
一个非常特殊的情况是,当您需要说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浮动,其他幂运算的特殊情况可能会出现。
晚会:
下面是一个解决方案,也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)可能int*int
是UB溢出。
powjii(INT_MAX, 63)
导致UB发生base *= base
。考虑检查您是否可以乘法,或移至无符号并让其环绕。
exp
签名。由于在(-1) ** (-N)
某些情况下有效,所以任何情况abs(base) > 1
都会使代码复杂化,这会使代码复杂化0
的负值进行处理exp
,因此最好对它进行未签名并保存该代码。
y
是的,确实不是真的需要签名,它带来了您所评论的复杂性,但是OP的请求是特定的pow(int, int)
。因此,这些好的评论属于OP的问题。由于OP没有指定溢出时的处理方式,因此定义明确的错误答案仅比UB好。考虑到“最有效的方式”,我怀疑OP在乎OF。
考虑负数exponenet的更通用的解决方案
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);
}
pow(i, INT_MIN)
可能是一个无限循环。
另一种实现(用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;
}
}
除了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
或任何类型的最大值)是一个完美的平方,接着就,随即。
我已经实现了一种算法,该算法可以记住所有计算出的功率,然后在需要时使用它们。因此,例如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问题。
我的情况有些不同,我尝试使用电源创建蒙版,但我想我还是会分享我找到的解决方案。
显然,它仅适用于2的幂。
Mask1 = 1 << (Exponent - 1);
Mask2 = Mask1 - 1;
return Mask1 + Mask2;
#define MASK(e) (((e) >= 64) ? -1 :( (1 << (e)) - 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;
}
(c != c++) == 1