计算整数的幂

105

在Java中,还有其他计算整数次幂的方法吗?

我目前使用 Math.pow(a, b),但它返回一个 double ,这通常需要更多的工作量,并且当您只想使用 int 时,看起来不够简洁(因为一个幂结果总是一个 int)。

是否有像Python中的 a**b 这样简单的方法?


2
这可能是这个问题的重复 https://dev59.com/hnVD5IYBdhLWcg3wE3No - bpgergo
18个回答

64

当指数为2的幂时,可以使用简单且快速的移位表达式 1 << 指数

例如:

22 = 1 << 2 = (int) Math.pow(2, 2)
210 = 1 << 10 = (int) Math.pow(2, 10)

对于更大的指数(超过31),请使用long类型

232 = 1L << 32 = (long) Math.pow(2, 32)

顺便提一下,在Kotlin中,你可以使用shl代替<<,所以

(java) 1L << 32 = 1L shl 32 (kotlin)


61

整数只有32位,这意味着它的最大值是2^31-1。正如您所看到的,对于非常小的数字,您很快就会得到一个无法再由整数表示的结果。这就是为什么Math.pow使用double的原因。

如果您需要任意精度的整数,请使用BigInteger.pow。但这当然不那么高效。


72
+1,这是真的。但如果Java的构建者添加了pow(int, int),那就更好了。有时候你只是想要一个整数的幂,而完全不关心浮点数。 - Petar Minchev
6
我猜他们没有这么做是因为在大多数情况下,这种方法会导致完全错误的结果。最少惊讶原则。 - JB Nizet
3
是的,那是一个很好的观点。但是当你是个无知者并且使用 int 时,没有什么能阻止你将溢出的值转换为 (int) - Petar Minchev
7
如果你将“大多数情况”定义为“对于输入值的大多数”,那么Java肯定不应该为intlong输入提供*运算符,因为对于大多数的输入,乘法都会溢出。实际上,即使加法也有约一半的时间会溢出! - BeeOnRope
double 的最大值约为10^308,而 long 的最大值约为10^19 - 这是一个巨大的差异。 - marcotama
显示剩余2条评论

42

最佳算法基于a^b的递归幂定义。

long pow (long a, int b)
{
    if ( b == 0)        return 1;
    if ( b == 1)        return a;
    if (isEven( b ))    return     pow ( a * a, b/2); //even a=(a^2)^b/2
    else                return a * pow ( a * a, b/2); //odd  a=a*(a^2)^b/2

}

该操作的运行时间为O(logb)。

参考资料:更多信息


3
isEven(b)方法是什么?它和b % 2 == 0一样吗? - HendraWD
2
我也很好奇...这个人走了,让我们陷入了困惑...邋遢的程序员... - Apostolos
不存在 isEven(b) 函数 -- 意思是 ((b & 1) == 0) -- 还有一个不必要复杂的算法! - Apostolos
2
更好地处理b<0的情况,以避免无限递归。 - Tzafrir
8
在我的基准测试中,相比于简单循环算法,这个递归方法实际上慢了2倍至20倍。这里的递归会带来很多开销。 - Gumby The Green
显示剩余2条评论

38

没有类似于a**b这样简短的方法。

如果你想避免重复,可以使用以下简单的循环:

long result = 1;
for (int i = 1; i <= b; i++) {
   result *= a;
}
如果您想使用pow并将结果转换为整数,请按以下方式强制转换结果:
int result = (int)Math.pow(a, b);

5
如果一个long是64位,那么O(n)的上限就是64。这真的很糟糕吗? - Thomas Weller
1
@Thomas,不对,如果b2_000_000_000,那么你需要执行2e9次操作,而不是其他解决方案中的31次操作。 - Dmitry Ginzburg
2
@DmitryGinzburg:在这种情况下,您将遇到许多溢出问题。 OP的代码不包括参数验证。如果a很小,最大允许数字应为64(对于a = 1没有意义)。 - Thomas Weller
@Thomas,溢出问题不是你的问题,而是运行程序的问题。 - Dmitry Ginzburg
2
在我的基准测试中,这个算法的速度实际上比递归的O(log(b))算法快2倍到20倍,而且对于所有值的完整范围都是如此。递归会带来很多开销。 - Gumby The Green
显示剩余2条评论

9

Google Guava拥有针对整数的数学实用工具。 IntMath


4
import java.util.*;

public class Power {

    public static void main(String args[])
    {
        Scanner sc=new Scanner(System.in);
        int num = 0;
        int pow = 0;
        int power = 0;

        System.out.print("Enter number: ");
        num = sc.nextInt();

        System.out.print("Enter power: ");
        pow = sc.nextInt();

        System.out.print(power(num,pow));
    }

    public static int power(int a, int b)
    {
        int power = 1;

        for(int c = 0; c < b; c++)
            power *= a;

        return power;
    }

}

for循环的改进:for (; b > 0; b--) - tckmn
7
严重的改进:每一步将底数平方并使指数减半。 - kevin cline

2

您可以简单地使用Math.pow(a,b),就像之前所使用的一样,并在其前面使用(int)进行值转换。以下可用作示例。

int x = (int) Math.pow(a,b);

其中ab可以是您想要的doubleint值。这将根据您的要求简单地将其输出转换为整数值。


4
我不同意这个观点。如果由于舍入误差导致 3.0 ^ 1.0 = 2.999999...,那么答案将会是错误的 2 - Hidde
@Hidde,是的,你说得对,朋友,这肯定有一个缺点,谢谢你提醒。它可能会给出错误的结果,但是你在这里的例子可能会给出正确的结果,如3.0。只是举个例子,乘法的四舍五入误差2.2*3.0 = 6.0000005而不是6.6 - Shubham Srivastava
2
@Hidde Java中的double可以精确表示所有Java int。请参阅Java语言规范第5.1.2节“扩展原始类型转换”。 - David Moles
3
@David,谢谢,然而我以上的论点仍然成立。 数字本身将是准确的表示,但幂的结果可能不准确。 特别是对于大的幂。 - Hidde
2
如果幂的结果适合于 int,那么它将是精确的。如果它不适合于 int,那么小数扩展不是你的问题,溢出才是你的问题。 - David Moles

2

以下是一个简单的(没有检查溢出或参数有效性)实现重复平方法计算幂的算法:

/** Compute a**p, assume result fits in a 32-bit signed integer */ 
int pow(int a, int p)
{
    int res = 1;
    int i1 = 31 - Integer.numberOfLeadingZeros(p); // highest bit index
    for (int i = i1; i >= 0; --i) {
        res *= res;
        if ((p & (1<<i)) > 0)
            res *= a;
    }
    return res;
}

时间复杂度对指数p取对数后是对数级别的(即与表示p所需的位数成线性关系)。

2
Guava的数学库提供了两种计算精确整数幂的方法: pow(int b, int k) 计算b的k次幂,并在溢出时进行包装。 checkedPow(int b, int k) 与前者相同,但在溢出时会抛出ArithmeticException异常。
个人而言,checkedPow()满足我大部分整数指数的需求,且比使用double版本和四舍五入等更为简洁和安全。在几乎所有需要幂函数的地方,溢出都是一个错误(或不可能,但如果不可能变成可能,我希望被告知)。

如果您想获得一个long类型的结果,您可以使用相应的LongMath方法并传递int类型的参数。


1

基数是你想要进行幂运算的数字,n是指数,如果n为0,则返回1,如果n为1,则返回基数,如果条件不满足,则使用公式base*(powerN(base,n-1))。例如:使用此公式将2的n次方计算为:2(基数)*2(powerN(基数,n-1))。

public int power(int base, int n){
   return n == 0 ? 1 : (n == 1 ? base : base*(power(base,n-1)));
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接