如何编写一个计算功率的循环?
Posted
技术标签:
【中文标题】如何编写一个计算功率的循环?【英文标题】:How to write a loop that calculates power? 【发布时间】:2020-02-02 02:22:53 【问题描述】:我正在尝试编写一个不使用 pow() 函数来计算功率的循环。我被困在如何做到这一点上。执行base *= base
甚至适用于高达 4 的幂,所以有一些我似乎无法弄清楚的完全奇怪的东西。
int Fast_Power(int base, int exp)
int i = 2;
int result;
if(exp == 0)
result = 1;
if(exp == 1)
result = base;
else
for(i = 2; i < exp; i++)
base *= base;
result = base;
return result;
【问题讨论】:
result = base; if (!exp) return 1; for(i = 1; i < exp; i++) result *= base; return result;
-- 但您需要检查整数溢出。即使是相对较小的指数也可能导致大整数溢出...
"Doing base *= base works for even power up to 4" --> base
使用了什么值?
@chux,四的幂是巧合,以3^4
为例。你想要3*3*3*3 = 81
,但你从3开始得到base
,然后平方两次(对于2
和3
的i
),得到9然后是81。2的幂实际上导致未初始化的结果。跨度>
您不允许int
可以表示的值范围是有限的。该标准不保证int
可以表示超过32767
,因此如果base
的值超过13
,如果提高到4
的幂,可能会导致溢出。即使对于 32 位 int
,如果将大于 215
的值提高到 4
的幂,也会发生溢出。当int
发生溢出时,行为未定义。
【参考方案1】:
base *= base;
您的问题在于该声明,您根本不应该更改base
。相反,您应该根据base
的常量值调整result
。
要进行幂运算,您需要重复乘法,但base *= base
会为您提供重复的平方值,因此您将获得比想要的。这实际上适用于四的幂,因为您迭代 4 - 2
次,对每次迭代求平方,然后 x<sup>4</sup> == (x<sup>2</sup>)<sup>2</sup>
。
它不会适用于像六这样的更高的幂,因为你迭代了6 - 2
次和x<sup>6</sup> != (((x<sup>2</sup>)<sup>2</sup>)<sup>2</sup>)<sup>2</sup>
。后一个值实际上等价于x<sup>16</sup>
。
顺便说一句(尽管您有争论),它实际上不保证适用于二的幂。如果您在这种情况下按照代码进行操作,您将看到
result
从未分配过值,因此返回值将是任意的。如果它对你有用,那是偶然的,可能会在某个时候咬你。
你可以使用的算法应该是这样的:
float power(float base, int exponent):
# 0^0 is undefined.
if base == 0 and exponent == 0:
throw bad_input
# Handle negative exponents.
if exponent < 0:
return 1 / power(base, -exponent)
# Repeated multiplication to get power.
float result = 1
while exponent > 0:
# Use checks to detect overflow.
float oldResult = result
result *= base
if result / base is not close to oldResult:
throw overflow
exponent -= 1
return result
这个算法处理:
负整数指数(自x<sup>-y</sup> = <sup>1</sup>/<sub>x<sup>y</sup></sub>
);
0<sup>0</sup>
的未定义情况;和
如果您没有任意精度值,则溢出(基本上,如果(x * y) / y != x
,您可以合理地确定发生了溢出)。请注意“不接近”的使用,由于精度限制可能导致错误,因此检查浮点数是否完全相等是不明智的 - 最好对某些描述实施“足够接近”检查。
在转换为 C 或 C++ 时要记住一件事,2 的补码实现在使用最大负整数时会引起问题,因为由于正负值之间的不平衡,它的否定通常再次成为相同的值.这很可能导致无限递归。
您可以通过尽早(在其他任何事情之前)检测案例来解决这个问题,例如:
if INT_MIN == -INT_MAX - 1 and exp == INT_MIN:
throw bad_input
第一部分检测 2 的补码实现,而第二部分检测 INT_MIN
作为指数的(有问题的)使用。
【讨论】:
好点,@chux,已经添加了一个注释,希望能涵盖这个案例。【参考方案2】:你做错的是base *= base
每次循环,每次迭代都会改变基础本身。
相反,您希望基数保持不变,并将最终结果乘以原始基数“exp”的时间。
int Fast_Power(int base, int exp)
int result=1;
if(exp == 0)
result = 1;
if(exp == 1)
result = base;
else
for(int i = 0; i < exp; i++)
result *= base;
return result;
【讨论】:
【参考方案3】:您正在寻找的基本但幼稚的算法很容易受到整数溢出的影响:
int Fast_Power (int base, int exp)
int result = base;
if (exp == 0)
return result ? 1 : 0;
for (int i = 1; i < exp; i++)
result *= base;
return result;
注意: result
很容易溢出。您需要使用一些基本检查来防止 integer-overflow 和 Undefined Behavior。
可以按如下方式合并最小检查(请参阅:Catch and compute overflow during multiplication of two large integers)。您必须在此处使用更宽的类型进行临时计算,然后将结果与INT_MIN
和INT_MAX
(在limits.h
标头中提供)进行比较以确定是否发生溢出:
#include <limits.h>
...
int Fast_Power (int base, int exp)
int result = base;
if (exp == 0)
return result ? 1 : 0;
for (int i = 1; i < exp; i++)
long long int tmp = (long long) result * base; /* tmp of wider type */
if (tmp < INT_MIN || INT_MAX < tmp) /* check for overflow */
fputs ("error: overflow occurred.\n", stderr);
return 0;
result = tmp;
return result;
现在,如果您尝试,例如Fast_Power (2, 31);
产生错误并返回零。
另外,正如@paxdiablo 注释中的注释Zero to the power of zero 可能未定义,因为没有商定的值。如果需要,您可以添加测试并在这种情况下发出警告/错误。
【讨论】:
顺便说一句,我相信零的零次方是未定义的,而不是零。 将溢出的除法测试替换为与INT_MIN
和INT_MAX
的比较,作为更清晰的检查,避免C11 Standard - 3.4.3(p3) 是C18 Standard - 3.4.3(p4)
(示例文本在两者中相同)由中间产生result * base
在除法测试之前需要乘法。【参考方案4】:
首先,我同意使用base *= base
可能是一个错误。也就是说,这不一定是错误。我的第一印象是 OP 试图以人类手动计算的方式计算能力。例如,如果你想计算 3^13,一个合理的方法是开始计算指数,它是 2 的幂。
然后你可以使用这些结果来计算 3^13 为
3^13 = 3^1 * 3^4 * 3^8 = 1,594,323一旦您了解了这些步骤,您就可以编写此代码。最困难的部分可能是确定何时停止对基数进行平方,以及哪些平方应包含在最终计算中。也许令人惊讶的是,指数的(无符号)二进制表示告诉我们这一点!这是因为二进制中的数字代表两个的幂,它们加在一起形成数字。考虑到这一点,我们可以编写以下内容。
int Fast_Power(int base, int exp)
int result = 1;
unsigned int expu = exp;
unsigned int power_of_two = 1;
while (expu > 0)
if (power_of_two & expu)
result *= base;
expu ^= power_of_two;
power_of_two <<= 1;
base *= base;
return result;
此代码没有溢出保护,但这是个好主意。坚持原来的原型,它仍然接受负指数并返回整数,这是一个矛盾。由于 OP 没有指定在溢出或负指数时应该发生什么,因此此代码不会尝试处理任何一种情况。其他答案提供了解决这些问题的合理方法。
【讨论】:
以上是关于如何编写一个计算功率的循环?的主要内容,如果未能解决你的问题,请参考以下文章