Как проверить, является ли число степенью двойки

Сегодня мне понадобился простой алгоритм для проверки того, является ли число степенью двойки.

Алгоритм должен быть:

  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 из-за округления. Итак, я проверил, равно ли 2 в степени 63 исходному числу - и это так, потому что расчет выполняется в s, а не в точных числах: log2 x Math.Log double

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 .

Есть лучший алгоритм?

Ответов (25)

Решение

Есть простой прием для решения этой проблемы:

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

Обратите внимание, что эта функция будет отчитываться true за 0, что не сила 2 . Если вы хотите исключить это, вот как:

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

Объяснение

Прежде всего, побитовый двоичный оператор & из определения MSDN:

Двоичные операторы & предопределены для целочисленных типов и bool. Для целочисленных типов & вычисляет логическое побитовое И для своих операндов. Для операндов типа bool & вычисляет логическое И для своих операндов; то есть результат будет истинным тогда и только тогда, когда оба его операнда истинны.

Теперь давайте посмотрим, как все это разыграется:

Функция возвращает логическое значение (истина / ложь) и принимает один входящий параметр типа unsigned long (в данном случае 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 (помните, что & принимает двоичное представление этих чисел). Итак, у нас есть:

100 = 4
011 = 3

Представьте, что эти значения складываются подобно элементарному сложению. & Оператор говорит , что , если оба значения равны 1 , то результат будет 1, в противном случае он равен 0. Таким образом 1 & 1 = 1, 1 & 0 = 0, 0 & 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 - это степень двойки.

Число является степенью двойки, если оно содержит только 1 установленный бит. Мы можем использовать это свойство и универсальную функцию, 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 степенью двойки, так как она также возвращает False для 0.

ВЫХОД

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
private static bool IsPowerOfTwo(ulong x)
{
    var l = Math.Log(x, 2);
    return (l == Math.Floor(l));
}
bool IsPowerOfTwo(ulong x)
{
    return x > 0 && (x & (x - 1)) == 0;
}
int isPowerOfTwo(unsigned int x)
{
    return ((x != 0) && ((x & (~x + 1)) == x));
}

Это действительно быстро. Проверка всех 2 ^ 32 целых чисел занимает около 6 минут 43 секунды.

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

Если x это степень двойки, его единственный бит 1 находится в позиции n . Это означает x – 1, что в позиции стоит 0 n . Чтобы понять, почему, вспомните, как работает двоичное вычитание. При вычитании 1 из x заимствования распространяется на всю позицию n ; бит n становится 0, а все младшие биты становятся 1. Теперь, поскольку x не имеет общих битов 1 x – 1, x & (x – 1) это 0 и !(x & (x – 1)) истинно.

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

Пример

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, за которой следует 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, которое является степенью двойки, и x - 1?

Один из xвыравнивается с нулем, x - 1а все нули xвыравниваются с единицами из x - 1, в результате чего побитовое И приводит к нулю. И именно поэтому мы получаем правильный ответ в одну строку, упомянутый выше.


Дальнейшее добавление красоты принятого ответа выше -

Итак, теперь в нашем распоряжении есть недвижимость:

Когда мы вычитаем 1 из любого числа, тогда в двоичном представлении крайняя правая 1 станет 0, а все нули слева от этой крайней правой единицы теперь станут 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. Для любого числа возьмите его двоичное представление. Пример: Возьмите число 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 is_power_of_2(ulong x) {
    if(x ==  (1 << (sizeof(ulong)*8 -1) ) return true;
    return (x > 0) && (x<<1 == (x|(x-1)) +1));
}

для любой степени двойки также верно следующее.

п & (- п) == п

ПРИМЕЧАНИЕ: сбой для n = 0, поэтому необходимо проверить его.
Причина, по которой это работает:
-n - это 2-е дополнение к n. -n будет иметь перевернутый каждый бит слева от крайнего правого установленного бита n по сравнению с n. Для степеней двойки есть только один установленный бит.

Эта программа на Java возвращает «истину», если число является степенью 2, и возвращает «ложь», если это не степень двойки.

// 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

Марк Гравелл предложил это, если у вас есть .NET Core 3, System.Runtime.Intrinsics.X86.Popcnt.PopCount

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

Единая инструкция, быстрее, чем (x != 0) && ((x & (x - 1)) == 0) переносимая.

В C я протестировал i && !(i & (i - 1) трюк и сравнил его с __builtin_popcount(i) использованием gcc в Linux с флагом -mpopcnt, чтобы убедиться, что используется инструкция POPCNT процессора. Моя тестовая программа подсчитала количество целых чисел от 0 до 2 ^ 31, которые были степенью двойки.

Сначала я подумал, что i && !(i & (i - 1) это на 10% быстрее, хотя я подтвердил, что POPCNT использовался при разборке, которую я использовал __builtin_popcount .

Тем не менее, я понял, что включил оператор if, и предсказание ветвлений, вероятно, лучше работает в версии с вращением битов. Я удалил if, и POPCNT, как и ожидалось, оказался быстрее.

Полученные результаты:

Intel (R) Core (TM) i7-4771 CPU макс. 3,90 ГГц

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

16-ядерный процессор AMD Ryzen Threadripper 2950X с тактовой частотой не более 3,50 ГГц

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 кажется немного медленнее, чем AMD с битовым тидлингом, но имеет гораздо более быстрый 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

Я вижу, что многие ответы предлагают вернуть n &&! (N & (n - 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;
    }
}

в этом подходе вы можете проверить, есть ли только 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;
}

Улучшение ответа @ 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)

После публикации вопроса я подумал о следующем решении:

Нам нужно проверить, совпадает ли ровно одна из двоичных цифр с единицей. Поэтому мы просто сдвигаем число вправо на одну цифру за раз и возвращаем, 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;
}

Конечно, решение Грега намного лучше.

Вот некоторые сайты, которые документируют и объясняют этот и другие хитрости:

И их дедушка, книга Генри Уоррена-младшего «Хакерское наслаждение» :

Как объясняется на странице Шона Андерсона , выражение ((x & (x - 1)) == 0) неверно указывает, что 0 является степенью 2. Он предлагает использовать:

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

чтобы исправить эту проблему.

return (i & -i) == i

Найдите, является ли данное число степенью двойки.

#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++ :

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