如何编写一个计算功率的循环?

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 &lt; 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,然后平方两次(对于23i),得到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-overflowUndefined Behavior

可以按如下方式合并最小检查(请参阅:Catch and compute overflow during multiplication of two large integers)。您必须在此处使用更宽的类型进行临时计算,然后将结果与INT_MININT_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_MININT_MAX 的比较,作为更清晰的检查,避免C11 Standard - 3.4.3(p3) 是C18 Standard - 3.4.3(p4)(示例文本在两者中相同)由中间产生result * base 在除法测试之前需要乘法。【参考方案4】:

首先,我同意使用base *= base 可能是一个错误。也就是说,这不一定是错误。我的第一印象是 OP 试图以人类手动计算的方式计算能力。例如,如果你想计算 3^13,一个合理的方法是开始计算指数,它是 2 的幂。

3^1 = 3 3^2 = 3*3 = 9 3^4 = 3^2 * 3^2 = 81 3^8 = 3^4 * 3^4 = 6,561

然后你可以使用这些结果来计算 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 没有指定在溢出或负指数时应该发生什么,因此此代码不会尝试处理任何一种情况。其他答案提供了解决这些问题的合理方法。

【讨论】:

以上是关于如何编写一个计算功率的循环?的主要内容,如果未能解决你的问题,请参考以下文章

如何编写一个 for 循环来测试我的所有 5 个测试用例?

如何编写电脑FORNEXT工程

如何在 MATLAB 中编写向量化函数

在ARM中用汇编语言编写程序,计算2+4+6+8+......+2n

如何在 Haskell 中编写游戏循环?

如何在没有循环的情况下进行模拟?