如何检查数字是否为2的幂


584

今天,我需要一种简单的算法来检查数字是否为2的幂。

该算法需要为:

  1. 简单
  2. 更正任何ulong值。

我想出了这个简单的算法:

private bool IsPowerOfTwo(ulong number)
{
    if (number == 0)
        return false;

    for (ulong power = 1; power > 0; power = power << 1)
    {
        // This for loop used shifting for powers of 2, meaning
        // that the value will become 0 after the last shift
        // (from binary 1000...0000 to 0000...0000) then, the 'for'
        // loop will break out.

        if (power == number)
            return true;
        if (power > number)
            return false;
    }
    return false;
}

但是后来我想,如何检查一个确切的整数呢?但是当我检查2 ^ 63 + 1时,由于四舍五入而恰好返回了63。所以我检查了63的2次幂是否等于原始数字-是的,因为计算是用s而不是精确的数字完成的:log2 xMath.Logdouble

private bool IsPowerOfTwo_2(ulong number)
{
    double log = Math.Log(number, 2);
    double pow = Math.Pow(2, Math.Round(log));
    return pow == number;
}

这返回true给定的错误值:9223372036854775809

有更好的算法吗?


1
我认为,当解决方案是两个幂的和(x & (x - 1))时,可能会返回假阳性。X8 + 16
乔·布朗

32
所有数字都可以写成2的幂,这就是为什么我们可以用二进制表示任何数字。此外,您的示例不会返回误报,因为11000&10111 = 10000!=
0。– vlsd 2011年

1
@JoeBrown它没有任何误报。实际上,该表达式返回两个2的任意幂之和中的较大者。
Samy Bencherif

Answers:


1219

这个问题有一个简单的技巧:

bool IsPowerOfTwo(ulong x)
{
    return (x & (x - 1)) == 0;
}

注意,此功能将报告true0,这是不是一个动力2。如果您要排除此情况,请按以下步骤操作:

bool IsPowerOfTwo(ulong x)
{
    return (x != 0) && ((x & (x - 1)) == 0);
}

说明

首先是MSDN定义中的按位二进制和运算符:

已为整数类型和布尔预定义了二进制和运算符。对于整数类型,&计算其操作数的逻辑按位与。对于布尔操作数,&计算其操作数的逻辑与;也就是说,当且仅当两个操作数都为真时,结果才为真。

现在,让我们看看这一切如何进行:

该函数返回布尔值(true / false),并接受一个unsigned long类型的传入参数(在这种情况下为x)。为了简单起见,让我们假设有人已经传递了值4并像下面这样调用了函数:

bool b = IsPowerOfTwo(4)

现在我们将x的每次出现都替换为4:

return (4 != 0) && ((4 & (4-1)) == 0);

好了,我们已经知道4!= 0等于true,到目前为止,一切都很好。但是关于:

((4 & (4-1)) == 0)

这当然可以解释为:

((4 & 3) == 0)

但是到底是4&3什么呢?

4的二进制表示形式为100,3的二进制表示形式为011(请记住&取这些数字的二进制表示形式)。因此,我们有:

100 = 4
011 = 3

想象一下,这些值像基本加法一样堆积起来。该&运营商表示,如果这两个值等于1,则结果为1,否则为0,所以1 & 1 = 11 & 0 = 00 & 0 = 0,和0 & 1 = 0。所以我们做一下数学:

100
011
----
000

结果只是0。所以我们回头看看return语句现在转换为:

return (4 != 0) && ((4 & 3) == 0);

现在翻译成:

return true && (0 == 0);
return true && true;

众所周知,这true && true很简单true,这表明在我们的示例中4是2的幂。


56
@Kripp:数字将采用二进制格式1000 ... 000。当您将其设为-1时,其格式为0111 ... 111。因此,两个数字的二进制数将是000000。对于非2的幂,则不会发生,例如1010100将变为1010011,从而导致(续)
配置器

47
...导致二进制和后为1010000。唯一的误报将是0,这就是我要使用的原因:return(x!= 0)&&((x&(x-1))== 0);
配置器

6
克里普(Kripp),考虑(2:1,10:1)(4:3,100:11)(8:7,1000:111)(16:15,10000:1111)看到模式了吗?
Thomas L Holaday

13
@ShuggyCoUk:二进制补码是表示负数的方式。由于这是一个无符号整数,因此负数的表示形式无关紧要。该技术仅依赖于非负整数的二进制表示。
Greg Hewgill

4
@SoapBox-更常见的是什么?零或非零数字不是2的幂吗?这是一个没有更多上下文就无法回答的问题。真的,真的没关系。
配置器2010年

97

一些记录并解释此问题的网站以及其他一些令人发指的黑客是:

还有他们的祖父,由小亨利·沃伦(Henry Warren,Jr.)着的《骇客的喜悦》一书

正如肖恩·安德森(Sean Anderson)的页面所解释的那样,该表达式((x & (x - 1)) == 0)错误地指出0是2的幂。他建议使用:

(!(x & (x - 1)) && x)

纠正这个问题。


4
0是2的幂... 2 ^ -inf = 0.;););)
Michael Bray

4
由于这是一个用C#标记的线程,因此需要指出的是,(Sean Anderson的)最后一个表达式在C#中是非法的,因为!它只能应用于布尔类型,并且&&还要求两个操作数都为布尔值- (用户定义的运算符除外)使其他事情成为可能,但这与不相关ulong。)
Jeppe Stig Nielsen

40

return (i & -i) == i


2
任何提示为什么这将或将不起作用?我只在Java中检查了它的正确性,那里只有带符号的int / longs。如果是正确的话,那将是更好的答案。更快,更小
Andreas Petersson,2009年

7
它利用了二进制补码表示法的特性之一:要计算数字的负值,请执行按位求反,然后将1加到结果中。设置的最低有效位i也将设置为-i。低于该位的位(两个值)均为0,而高于该位的位则相对。因此,的值i & -i将是其中的最低有效位i(是2的幂)。如果i具有相同的值,则这是唯一的位。i由于相同的原因,当为0 时失败i & (i - 1) == 0
迈克尔·卡曼

6
如果i是无符号类型,则二进制补码与它无关。您仅利用了模块化算术和按位与的属性。
R .. GitHub停止帮助ICE,2010年

2
如果i==0(返回)(0&0==0),则此方法不起作用true。它应该是return i && ( (i&-i)==i )
bobobobo

22
bool IsPowerOfTwo(ulong x)
{
    return x > 0 && (x & (x - 1)) == 0;
}

3
该解决方案是更好,因为它也可以处理负数,如果负能在传递(如果长时间ULONG代替)。
史蒂芬

在这种情况下,为什么小数通过作为2的幂?
克里斯·弗里斯纳


17

这是一个简单的C ++解决方案:

bool IsPowerOfTwo( unsigned int i )
{
    return std::bitset<32>(i).count() == 1;
}

8
在gcc上,它将编译为一个名为的gcc内置函数__builtin_popcount。不幸的是,一个处理器系列还没有一个汇编指令来执行此操作(x86),因此,它是最快的位计数方法。在任何其他体系结构上,这是单个汇编指令。
deft_code

3
@deft_code较新的x86微体系结构支持popcnt
phuclv

13

以下已接受答案的附录可能对某些人有用:

用二进制表示的2的幂总是看起来像1,后跟n个零,其中n大于或等于0。

Decimal  Binary
1        1     (1 followed by 0 zero)
2        10    (1 followed by 1 zero)
4        100   (1 followed by 2 zeroes)
8        1000  (1 followed by 3 zeroes)
.        .
.        .
.        .

等等。

当我们1从这类数字中减去时,它们变为0,后跟n,再一次,n与上述相同。例如:

Decimal    Binary
1 - 1 = 0  0    (0 followed by 0 one)
2 - 1 = 1  01   (0 followed by 1 one)
4 - 1 = 3  011  (0 followed by 2 ones)
8 - 1 = 7  0111 (0 followed by 3 ones)
.          .
.          .
.          .

等等。

症结所在

当我们做位与一些会发生什么x,这是2的幂,和x - 1

的之一x与的零对齐,x - 1而所有的零与的零x对齐x - 1,导致按位与结果为0。这就是我们使上述单行答案正确的方式。


进一步增加了上述公认答案的美感-

因此,我们现在有财产可供使用:

当我们从任何数字中减去1时,那么在二进制表示形式中,最右边的1将变为0,而最右边1之前的所有零现在变为1

此属性的一个绝佳用途是找出- 给定数字的二进制表示形式中存在多少个1?对于给定的整数,执行此操作的简短代码x如下:

byte count = 0;
for ( ; x != 0; x &= (x - 1)) count++;
Console.Write("Total ones in the binary representation of x = {0}", count);

可以从上述概念证明的数字的另一个方面是:“每个正数都可以表示为2的幂吗?”

是的,每个正数都可以表示为2的乘方之和。对于任何数字,请采用其二进制表示形式。例如:取数字117

The binary representation of 117 is 1110101

Because  1110101 = 1000000 + 100000 + 10000 + 0000 + 100 + 00 + 1
we have  117     = 64      + 32     + 16    + 0    + 4   + 0  + 1

@Michi:我在某处声称0是正数吗?还是2的幂?
displayName

是的,以0为例,并在二进制表示形式中对其进行数学运算。它造成了混乱。
Michi's

1
如果将两个数字相加会使您误以为他们必须是正数,那么我将无能为力。此外,在图示中已显示0表示该数字被跳过2的幂。任何了解基础数学的人都知道,加0表示不添加任何内容。
displayName

10

发布问题后,我想到了以下解决方案:

我们需要检查二进制数字之一是否恰好是一。因此,我们只需将数字一次一次右移一位,然后返回true等于1的数字即可。如果在任何时候,我们得到一个奇数((number & 1) == 1),我们就会知道结果是false。对于(大)真值,这证明(使用基准)比原始方法要快一些,对于假或小值,这要快得多。

private static bool IsPowerOfTwo(ulong number)
{
    while (number != 0)
    {
        if (number == 1)
            return true;

        if ((number & 1) == 1)
            // number is an odd number and not 1 - so it's not a power of two.
            return false;

        number = number >> 1;
    }
    return false;
}

当然,格雷格的解决方案要好得多。


10
    bool IsPowerOfTwo(int n)
    {
        if (n > 1)
        {
            while (n%2 == 0)
            {
                n >>= 1;
            }
        }
        return n == 1;
    }

这是确定一个数字是否是另一个数字的幂的通用算法。

    bool IsPowerOf(int n,int b)
    {
        if (n > 1)
        {
            while (n % b == 0)
            {
                n /= b;
            }
        }
        return n == 1;
    }

6
bool isPow2 = ((x & ~(x-1))==x)? !!x : 0;

1
是这个c#吗 我想这是c++作为x布尔值返回的。
Mariano Desanze 2010年

1
我确实将其编写为C ++。为了使C#变得微不足道:bool isPow2 =((x&〜(x-1))== x)?x!= 0:错误;
abelenky 2010年

4

查找给定数字是否为2的幂。

#include <math.h>

int main(void)
{
    int n,logval,powval;
    printf("Enter a number to find whether it is s power of 2\n");
    scanf("%d",&n);
    logval=log(n)/log(2);
    powval=pow(2,logval);

    if(powval==n)
        printf("The number is a power of 2");
    else
        printf("The number is not a power of 2");

    getch();
    return 0;
}

或者,在C#中:return x == Math.Pow(2,Math.Log(x,2));
configurator

4
破碎。遭受主要浮点舍入问题的困扰。如果要使用浮点,请使用frexp而不是讨厌的log东西。
R .. GitHub停止帮助ICE 2010年

4
bool isPowerOfTwo(int x_)
{
  register int bitpos, bitpos2;
  asm ("bsrl %1,%0": "+r" (bitpos):"rm" (x_));
  asm ("bsfl %1,%0": "+r" (bitpos2):"rm" (x_));
  return bitpos > 0 && bitpos == bitpos2;
}

4
int isPowerOfTwo(unsigned int x)
{
    return ((x != 0) && ((x & (~x + 1)) == x));
}

这真的非常快。检查所有2 ^ 32整数大约需要6分43秒。


4
return ((x != 0) && !(x & (x - 1)));

如果x为2的幂,则其唯一的1位在位置n。这意味着x – 1位置为0 n。要了解原因,请回想一下二进制减法的工作原理。当从中减去1时x,借位一直传播到头寸n; 位n变为0,所有低位变为1。现在,由于x没有与1相同的位x – 1,因此x & (x – 1)为0,并且!(x & (x – 1))为true。


3

如果数字仅包含1个设置位,则该数字为2的幂。我们可以使用该属性和泛型函数countSetBits来查找数字是否为2的幂。

这是一个C ++程序:

int countSetBits(int n)
{
        int c = 0;
        while(n)
        {
                c += 1;
                n  = n & (n-1);
        }
        return c;
}

bool isPowerOfTwo(int n)
{        
        return (countSetBits(n)==1);
}
int main()
{
    int i, val[] = {0,1,2,3,4,5,15,16,22,32,38,64,70};
    for(i=0; i<sizeof(val)/sizeof(val[0]); i++)
        printf("Num:%d\tSet Bits:%d\t is power of two: %d\n",val[i], countSetBits(val[i]), isPowerOfTwo(val[i]));
    return 0;
}

我们不需要显式检查0为2的幂,因为它也返回0的False。

输出值

Num:0   Set Bits:0   is power of two: 0
Num:1   Set Bits:1   is power of two: 1
Num:2   Set Bits:1   is power of two: 1
Num:3   Set Bits:2   is power of two: 0
Num:4   Set Bits:1   is power of two: 1
Num:5   Set Bits:2   is power of two: 0
Num:15  Set Bits:4   is power of two: 0
Num:16  Set Bits:1   is power of two: 1
Num:22  Set Bits:3   is power of two: 0
Num:32  Set Bits:1   is power of two: 1
Num:38  Set Bits:3   is power of two: 0
Num:64  Set Bits:1   is power of two: 1
Num:70  Set Bits:3   is power of two: 0

当函数的返回类型为“ ulong”时,将c返回为“ int”吗?使用while而不是if?我个人看不出原因,但是似乎可以。编辑:-不...它将返回大于1的任何值0
James Khoury 2012年

@JamesKhoury我正在编写一个c ++程序,所以我错误地返回了一个int。但是,这是一个很小的错别字,不应该受到谴责。但是,我无法理解其余注释的理由“使用while而不是if”,并且“如果大于0,它将返回1”。我添加了主存根以检查输出。AFAIK其预期的输出。如果我错了,请纠正我。
jerrymouse

3

这是我设计的另一种方法,在这种情况下使用|代替&

bool is_power_of_2(ulong x) {
    if(x ==  (1 << (sizeof(ulong)*8 -1) ) return true;
    return (x > 0) && (x<<1 == (x|(x-1)) +1));
}

您需要(x > 0)这里吗?
Configurator

@configurator,是的,否则is_power_of_2(0)将返回true
Chethan

3

对于任何2的幂,以下条件也成立。

n&(-n)== n

注意:对于n = 0失败,因此需要对其进行检查。
起作用的原因是:
-n是n的2s补码。与n相比,-n将n的最右置位的每一位的左边翻转。对于2的幂,只有一个置1位。


2

0000 0001    Yes
0001 0001    No

算法

  1. 使用位掩码,将NUM变量分成二进制

  2. IF R > 0 AND L > 0: Return FALSE

  3. 否则,NUM成为非零的那个

  4. IF NUM = 1: Return TRUE

  5. 否则,请执行步骤1

复杂

时间〜O(log(d))这里d是二进制位数


1

改进@ user134548的答案,无需进行位运算:

public static bool IsPowerOfTwo(ulong n)
{
    if (n % 2 != 0) return false;  // is odd (can't be power of 2)

    double exp = Math.Log(n, 2);
    if (exp != Math.Floor(exp)) return false;  // if exp is not integer, n can't be power
    return Math.Pow(2, exp) == n;
}

这适用于:

IsPowerOfTwo(9223372036854775809)

浮点运算远比简单的按位表达式慢
phuclv

1

马克gravell建议,如果您有.NET核心3,System.Runtime.Intrinsics.X86.Popcnt.PopCount

public bool IsPowerOfTwo(uint i)
{
    return Popcnt.PopCount(i) == 1
}

单指令,速度更快,(x != 0) && ((x & (x - 1)) == 0)但移植性较差。


您确定它比快(x != 0) && ((x & (x - 1)) == 0)吗?我对此表示怀疑。在无法使用
popcnt的

不快。我刚刚在现代的Intel CPU上进行了测试,并验证了反汇编中使用的POPCNT(允许使用C代码,而不是.NET)。一般而言,POPCNT的计数速度更快,但是对于单位开启情况,位纠缠技巧仍然要快10%。
eraoul

糟糕,我收回了。我在循环测试中认为分支预测“作弊”。POPCNT确实是一条可在单个时钟周期内运行的指令,如果可用,它会更快。
eraoul

0

在C语言中,我测试了该i && !(i & (i - 1)技巧,并__builtin_popcount(i)在Linux上使用gcc 将其与-mpopcnt标志进行了比较,以确保使用CPU的POPCNT指令。我的测试程序计算了0到2 ^ 31之间的整数,它们是2的幂。

一开始,我以为i && !(i & (i - 1)速度提高了10%,即使我验证了POPCNT已用于我使用的反汇编中__builtin_popcount

但是,我意识到我已经包含了一个if语句,并且分支预测可能在bit twiddling版本上做得更好。如预期的那样,我删除了if和POPCNT的运行速度更快。

结果:

英特尔(R)酷睿TM i7-4771 CPU最大3.90GHz

Timing (i & !(i & (i - 1))) trick
30

real    0m13.804s
user    0m13.799s
sys     0m0.000s

Timing POPCNT
30

real    0m11.916s
user    0m11.916s
sys     0m0.000s

AMD锐龙Threadripper 2950X 16核处理器最大3.50GHz

Timing (i && !(i & (i - 1))) trick
30

real    0m13.675s
user    0m13.673s
sys 0m0.000s

Timing POPCNT
30

real    0m13.156s
user    0m13.153s
sys 0m0.000s

请注意,这里的英特尔CPU似乎有点慢,但有点慢,但是POPCNT快得多。AMD POPCNT没有提供太多的提升。

popcnt_test.c:

#include "stdio.h"

// Count # of integers that are powers of 2 up to 2^31;
int main() {
  int n;
  for (int z = 0; z < 20; z++){
      n = 0;
      for (unsigned long i = 0; i < 1<<30; i++) {
       #ifdef USE_POPCNT
        n += (__builtin_popcount(i)==1); // Was: if (__builtin_popcount(i) == 1) n++;
       #else
        n += (i && !(i & (i - 1)));  // Was: if (i && !(i & (i - 1))) n++;
       #endif
      }
  }

  printf("%d\n", n);
  return 0;
}

运行测试:

gcc popcnt_test.c -O3 -o test.exe
gcc popcnt_test.c -O3 -DUSE_POPCNT -mpopcnt -o test-popcnt.exe

echo "Timing (i && !(i & (i - 1))) trick"
time ./test.exe

echo
echo "Timing POPCNT"
time ./test-opt.exe

-1
private static bool IsPowerOfTwo(ulong x)
{
    var l = Math.Log(x, 2);
    return (l == Math.Floor(l));
}

尝试用它作为编号9223372036854775809。是否有效?由于舍入错误,我认为不会。
配置器

1
@configurator 922337203685477580_9_在我看来不像是2的幂;)
Kirschstein 2010年

1
@Kirschstein:这个数字给了他一个假阳性。
Erich Mirabal

7
Kirschstein:对我来说也不是一个。它确实看起来像一个功能……
configurator

-2

如果number是2的幂,则此程序在Java中返回“ true”,如果不是2的幂,则返回“ false”

// To check if the given number is power of 2

import java.util.Scanner;

public class PowerOfTwo {
    int n;
    void solve() {
        while(true) {
//          To eleminate the odd numbers
            if((n%2)!= 0){
                System.out.println("false");
                break;
            }
//  Tracing the number back till 2
            n = n/2;
//  2/2 gives one so condition should be 1
            if(n == 1) {
                System.out.println("true");
                break;
            }
        }
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Scanner in = new Scanner(System.in);
        PowerOfTwo obj = new PowerOfTwo();
        obj.n = in.nextInt();
        obj.solve();
    }

}

OUTPUT : 
34
false

16
true

1
这个问题被标记为C#,与以前的解决方案相比,您的解决方案也非常慢[
phuclv
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.