prosource

숫자가 2의 거듭제곱인지 확인하는 방법

probook 2023. 6. 2. 20:35
반응형

숫자가 2의 거듭제곱인지 확인하는 방법

오늘 나는 숫자가 2의 거듭제곱인지 확인하기 위한 간단한 알고리즘이 필요했습니다.

알고리즘은 다음과 같아야 합니다.

  1. 간단하죠.
  2. 다음 중 하나에 적합ulongvalue.value.value.

저는 다음과 같은 간단한 알고리즘을 생각해냈습니다.

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;
}

그런데 생각이 났어요.로그 x가 정확하게 둥근 숫자인지2 확인하는 것은 어떻습니까?제가 2^63+1을 확인했을 때,Math.Log()반올림 때문에 정확히 63을 반환했습니다.그래서 저는 2에서 63의 거듭제곱이 원래의 숫자와 같은지 확인했습니다. 왜냐하면 계산은 다음과 같이 이루어졌기 때문입니다.double정확한 숫자가 아닌 s.

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.

더 나은 알고리즘이 있습니까?

이 문제에 대한 간단한 속임수가 있습니다.

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

이은 참고, 이는다보을다니고합음함수▁note.true위해서0은 의 힘이 .2이를 제외하려면 다음과 같이 하십시오.

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

설명.

무엇보다도 MSDN 정의의 비트 바이너리 & 연산자:

이진 및 연산자는 적분 유형 및 bool에 대해 미리 정의되어 있습니다.적분 유형의 경우, 피연산자의 논리 비트 단위 AND를 계산합니다.쿨 피연산자의 경우 피연산자의 논리 AND를 계산합니다. 즉, 두 피연산자가 모두 참인 경우에만 결과가 참입니다.

이제 이 모든 것이 어떻게 진행되는지 살펴보겠습니다.

함수는 부울(true/false)을 반환하고 서명되지 않은 길이(x, 이 경우) 유형의 수신 매개 변수 하나를 허용합니다.단순성을 위해 누군가가 값 4를 넘겨 함수를 다음과 같이 불렀다고 가정해 보겠습니다.

bool b = IsPowerOfTwo(4)

이제 각 x의 발생을 4로 바꿉니다.

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

음, 우리는 이미 4!= 0이 사실이라는 것을 알고 있습니다, 지금까지 좋습니다.하지만 다음은 어떻습니까?

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

이는 물론 다음과 같은 의미가 있습니다.

((4 & 3) == 0)

하지만 정확히 무엇입니까?4&3?

4의 이진수 표현은 100이고 3의 이진수 표현은 011입니다(이러한 숫자의 이진수 표현은 &take).다음과 같은 이점이 있습니다.

100 = 4
011 = 3

이러한 가치들이 기본적인 덧셈과 매우 유사하게 쌓여 있다고 상상해 보십시오.&연산자는 두 값이 모두 1이면 결과가 1이고 그렇지 않으면 0이라고 말합니다.그렇게1 & 1 = 1,1 & 0 = 0,0 & 0 = 0,그리고.0 & 1 = 0그래서 우리는 계산을 합니다.

100
011
----
000

결과는 단순히 0입니다.이제 다시 돌아가서 반환 내역서의 의미를 살펴보겠습니다.

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

이제는 다음과 같은 뜻입니다.

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

우리 모두는 알고 있습니다.true && true간단히 말하면true의 예에서 4가 23 예 4는 2의 2거의 .

이를 문서화하고 설명하는 일부 사이트와 기타 비트 트위들링 해킹은 다음과 같습니다.

그리고 그들의 할아버지인 헨리 워렌 주니어의 책 "해커의 기쁨"은 다음과 같습니다.

션 앤더슨의 페이지에서 설명했듯이, 이 표현은((x & (x - 1)) == 0)이 20이 을 잘못 .그는 다음을 사용할 것을 제안합니다.

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

그 문제를 해결하기 위해.

return (i & -i) == i

승인된 답변의 다음 부록은 일부 사용자에게 유용할 수 있습니다.

2의 거듭제곱은 이진법으로 표현될 때 항상 1과 같으며, n이 0보다 크거나 같은 경우에는 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이 되고다음에 0이 되고 다시 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?

이 중 하나는 0과 정렬되고 모든 0은 1과 정렬되어 비트 AND가 0이 됩니다.그리고 그것이 우리가 위에서 언급한 단 한 줄의 답이 옳은 방법입니다.


위의 승인된 답변의 아름다움에 추가 -

이제 우리는 재산을 자유롭게 사용할 수 있습니다.

숫자에서 1을 빼면 이진수 표현에서 가장 오른쪽에 있는 1이 0이 되고 가장 왼쪽에 있는 모든 0이 이제 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예, 모양의 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
bool IsPowerOfTwo(ulong x)
{
    return x > 0 && (x & (x - 1)) == 0;
}

다음은 간단한 C++ 솔루션입니다.

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

질문을 올린 후 저는 다음과 같은 해결책을 생각했습니다.

우리는 이진수 중 정확히 하나가 1인지 확인해야 합니다.그래서 우리는 숫자를 한 번에 한 자리씩 오른쪽으로 옮기고, 다시 돌아갑니다.true 그것이 11 같다면과 같다면, 가 어떤 홀수로 (만약어시우홀수도면다달에한수의가리서에점느과)▁(▁if만면▁number▁come,다▁by▁if)(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;
}

물론, 그렉의 해결책이 훨씬 더 좋습니다.

    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;
    }
bool isPow2 = ((x & ~(x-1))==x)? !!x : 0;
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;
}
int isPowerOfTwo(unsigned int x)
{
    return ((x != 0) && ((x & (~x + 1)) == x));
}

이거 진짜 빨라요.2^32 정수를 모두 확인하는 데는 약 6분 43초가 소요됩니다.

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

한다면x이며, 는 위치 2에 .n이것은 의미합니다.x – 1가 위가입 0니다 0치다에 .n이유를 보려면 이진 빼기가 어떻게 작동하는지 기억해 보십시오.을 뺄 때x됩니다.n 트비n0Ω 되 모 이 하 1니 됩이 됩니다. 이제 터부이 때부터.x는 와공비는 1트없습다와 .x – 1,x & (x – 1)0며입니다.!(x & (x – 1))사실입니다.

2의 거듭제곱에 대하여, 다음도 성립합니다.

n&(-n)==n

. : n=0은 다음과 같습니다.
이 기능이 작동하는 이유는 다음과 같습니다.
n의 입니다. 에 설정된 -nnnnn은 nnnn의 2s 보어입니다.2의 거듭제곱에 대해서는 세트 비트가 하나뿐입니다.

주어진 숫자가 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;
}

숫자는 설정된 비트가 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

여기 제가 고안한 또 다른 방법이 있습니다. 이 경우에는 다음을 사용합니다.|&:

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));
}

에서는 아주 쉽습니다.넷 6.

using System.Numerics;

bool isPow2 = BitOperations.IsPow2(64); // sets true

여기 설명서가 있습니다.

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는 이진 .

.NET 6에는 라이너가 하나 있습니다.

// IsPow2 evaluates whether the specified Int32 value is a power of two.
Console.WriteLine(BitOperations.IsPow2(128));            // True

비트 연산 없이 @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)

.NET Core 3, System을 사용하는 경우 Mark gravell이 제안했습니다.런타임.본질적인 것들.X86.Popnt.팝 카운트

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

명령어, 일명령보빠름보다 (x != 0) && ((x & (x - 1)) == 0)휴대성이 떨어집니다.

이 접근 방식에서는 정수에 설정된 비트가 1개만 있고 정수가 > 0(c++)인지 확인할 수 있습니다.

bool is_pow_of_2(int n){
    int count = 0;
    for(int i = 0; i < 32; i++){
        count += (n>>i & 1);
    }
    return count == 1 && n > 0;
}

많은 답변들이 n&!(n&(n-1))을 반환할 것을 제안하고 있지만, 제 경험에 따르면 입력 값이 음수이면 잘못된 값을 반환합니다.2개의 숫자의 거듭제곱은 1개의 세트 비트만 가지고 있기 때문에 O(log N) 시간이 걸리는 세트 비트의 수를 간단히 셀 것이기 때문에 여기서 또 다른 간단한 접근법을 공유하겠습니다.

while (n > 0) {
    int count = 0;
    n = n & (n - 1);
    count++;
}
return count == 1;

설정 비트 수를 계산하려면 이 문서를 확인하십시오.

이것은 또한 그것을 하는 또 다른 방법입니다.

package javacore;

import java.util.Scanner;

public class Main_exercise5 {
    public static void main(String[] args) {
        // Local Declaration
        boolean ispoweroftwo = false;
        int n;
        Scanner input = new Scanner (System.in);
        System.out.println("Enter a number");
        n = input.nextInt();
        ispoweroftwo = checkNumber(n);
        System.out.println(ispoweroftwo);
    }
    
    public static boolean checkNumber(int n) {
        // Function declaration
        boolean ispoweroftwo= false;
        // if not divisible by 2, means isnotpoweroftwo
        if(n%2!=0){
            ispoweroftwo=false;
            return ispoweroftwo;
        }
        else {
            for(int power=1; power>0; power=power<<1) {
                if (power==n) {
                    return true;
                }
                else if (power>n) {
                    return false;
                }
            }
        }
        return ispoweroftwo;
    }
}

이 값은 숫자가 2의 거듭제곱에서 64까지의 값이면 반환됩니다(루프 조건에 대해 내부에서 변경할 수 있습니다("6"은 2^6에 대해 64).

const isPowerOfTwo = (number) => {
  let result = false;
  for (let i = 1; i <= 6; i++) {
    if (number === Math.pow(2, i)) {
      result = true;
    }
  }
  return result;
};

console.log(isPowerOfTwo(16));
console.log(isPowerOfTwo(10));

저는 Random.nextInt(int bound)에 대한 설명서를 읽고 매개 변수가 2의 거듭제곱인지 확인하는 이 멋진 코드 조각을 보았습니다. 코드의 일부는 다음과 같습니다.

if ((bound & -bound) == bound) // ie, bouns is a power of 2   

테스트해 보겠습니다.

for (int i=0; i<=8; i++) {
  System.out.println(i+" = " + Integer.toBinaryString(i));
}

>>
0 = 0
1 = 1
2 = 10
3 = 11
4 = 100
5 = 101
6 = 110
7 = 111
8 = 1000
// the left most 0 bits where cut out of the output

for (int i=-1; i>=-8; i--) {
  System.out.println(i+" = " + Integer.toBinaryString(i));
}

>>
-1 = 11111111111111111111111111111111
-2 = 11111111111111111111111111111110
-3 = 11111111111111111111111111111101
-4 = 11111111111111111111111111111100
-5 = 11111111111111111111111111111011
-6 = 11111111111111111111111111111010
-7 = 11111111111111111111111111111001
-8 = 11111111111111111111111111111000

당신은 무엇을 알아차렸습니까?
2표현과 표현에서 숫자가 됩니다 :) power 2 number, power 2 number는 양의 이진 표현과 음의 이진 표현이 같습니다 :)

for (int i=0; i<=8; i++) {
  System.out.println(i + " & " + (-i)+" = " + (i & (-i)));
}

>>
0 & 0 = 0
1 & -1 = 1
2 & -2 = 2
3 & -3 = 1
4 & -4 = 4
5 & -5 = 1
6 & -6 = 2
7 & -7 = 1
8 & -8 = 8

코틀린:

fun isPowerOfTwo(n: Int): Boolean {
    return (n > 0) && (n.and(n-1) == 0)
}

또는

fun isPowerOfTwo(n: Int): Boolean {
    if (n == 0) return false
    return (n and (n - 1).inv()) == n
}

inv는 이 값의 비트를 반전시킵니다.


참조:
log2 솔루션은 536870912와 같은 큰 숫자에는 작동하지 않습니다 ->

import kotlin.math.truncate
import kotlin.math.log2

fun isPowerOfTwo(n: Int): Boolean {
    return (n > 0) && (log2(n.toDouble())) == truncate(log2(n.toDouble()))
}

많은 답변들이 있었고 그 이유를 설명하는 링크들이 게시되었습니다.n & (n-1) == 02의 거듭제곱에는 효과가 있지만, 2의 거듭제곱이 아닌 경우에는 왜 효과가 없는지에 대한 설명을 찾을 수 없어서 완성도를 위해 추가합니다.

n = 1(2^0 = 1), 1 & 0 = 0의 경우에는 문제가 없습니다.

홀수 n > 1의 경우, 1의 최소 2비트(왼쪽 끝 및 오른쪽 끝 비트)가 있습니다.은 가장 에 그들의 &-은 가장 비트에서 입니다. n-1은 &-sum의 &-sum을 가지고 있습니다.n & (n-1) != 0:

n:          1xxxx1  for odd n > 1
n-1:        1xxxx0
            ------
n & (n-1):  1xxxx0 != 0

2의 거듭제곱이 아닌 짝수에 대해서도 1의 최소 2비트(왼쪽 끝과 오른쪽 끝이 아님)가 있습니다.여기서 n과 n-1은 오른쪽 끝의 1비트까지 다르므로 &-sum은 왼쪽 끝의 1비트 이상을 갖습니다.

        right-most 1 bit of n
                 v
n:          1xxxx100..00 for even n
n-1:        1xxxx011..11
            ------------
n & (n-1):  1xxxx000..00 != 0

저는 1이 2의 거듭제곱이라고 가정합니다. 즉, 2는 0의 거듭제곱입니다.

 bool IsPowerOfTwo(ulong testValue)
 {
  ulong bitTest = 1;
  while (bitTest != 0)
  {
    if (bitTest == testValue) return true;
    bitTest = bitTest << 1;
  }

  return false;
}

C에서, 나는 테스트했습니다.i && !(i & (i - 1)속임수를 써서 그것을 와 비교했습니다.__builtin_popcount(i) 명령을 사용합니다. -mpopcnt는 Linux의 gcc 명령어입니다. -mpopcnt는 CPU의 POPCNT 명령입니다.제 테스트 프로그램은 구간 [0, 2^31]에서 2의 거듭제곱인 정수의 수를 계산했습니다.

처음에 나는 생각했습니다.i && !(i & (i - 1)을 확인했음에도 더 빨랐습니다.__builtin_popcount.

하지만, 저는 제가 if 문을 포함했다는 것을 깨달았고, 지점 예측은 아마도 비트 트위들 버전에서 더 잘 하고 있을 것입니다.if를 제거했고 예상대로 POPCNT가 더 빨리 끝났습니다.

결과:

Intel(R) Core(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 라이젠 스레드리퍼 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

여기서 Intel CPU는 비트 트위들링을 사용하는 AMD보다 약간 느린 것처럼 보이지만 POPCNT가 훨씬 빠릅니다. AMD POPCNT는 그다지 많은 성능을 제공하지 않습니다.

popcnt_test.c:

#include "stdio.h"

// Count # of integers that are powers of 2 up to (not including) 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

언급URL : https://stackoverflow.com/questions/600293/how-to-check-if-a-number-is-a-power-of-2

반응형