Як перевірити, чи число є потужністю 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 через округлення. Тому я перевірив, чи 2 до потужності 63 дорівнює початковому числу - і це так, тому що обчислення проводиться в 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))може повернути помилкові позитиви, коли Xце сума повноважень двох, наприклад 8 + 16.
Джо Браун

32
Всі числа можна записати як суму потужностей двох, тому ми можемо представляти будь-яке число у двійковій формі. Крім того, ваш приклад не повертає хибний позитив, тому що 11000 & 10111 = 10000! = 0.
vlsd

1
@JoeBrown У ньому немає помилок. Насправді вираз повертає більший з будь-якої суми двох потужностей двох.
Самі Бенчеріф

Відповіді:


1219

Для цієї проблеми є проста хитрість:

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 & обчислює логічний І його операндів; тобто результат істинний тоді і лише тоді, коли обидва його операнди є істинними.

Тепер давайте розглянемо, як це все відбувається:

Функція повертає булеву (true / false) і приймає один вхідний параметр типу 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 (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. Цього не відбудеться для нежитлових двох, оскільки 1010100, наприклад, стане 1010011, що призведе до (продовження ...)
конфігуратора

47
... У результаті 1010000 після двійкового та. Єдиним хибним позитивом було б 0, тому я б використав: return (x! = 0) && ((x & (x - 1)) == 0);
конфігуратор

6
Крипп, поміркуй (2: 1, 10: 1) (4: 3, 100: 11) (8: 7, 1000: 111) (16:15, 10000: 1111) Бачиш шаблон?
Thomas L Holaday

13
@ShuggyCoUk: доповненням двох є те, як представлені негативні числа. Оскільки це непідписане ціле число, подання від'ємних чисел не має значення. Ця методика покладається лише на двійкове представлення негативних цілих чисел.
Грег Хьюгілл

4
@SoapBox - що є більш поширеним? Нулі або ненульові числа, які не мають сили двох? Це питання, на яке ви не можете відповісти без іншого контексту. І це справді, насправді не має значення.
конфігуратор

97

Деякі сайти, які документують та пояснюють це та інші хакерські хакі:

І онука з них, книга "Хакерське захоплення" Генрі Уоррена, молодшого :

Як пояснює сторінка Шона Андерсона , вираз ((x & (x - 1)) == 0)неправильно вказує на те, що 0 є силою 2. Він пропонує використовувати:

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

щоб виправити цю проблему.


4
0 - потужність 2 ... 2 ^ -inf = 0.;););)
Майкл Брей

4
Оскільки це посилання з тегом C # , то слід зазначити, що останній вираз (Шона Андерсона) є незаконним у C #, оскільки !може застосовуватися лише до булевих типів, а &&також вимагає, щоб обидва операнди були булевими (за винятком операторів, визначених користувачем) зробити інші речі можливими, але це не актуально ulong.)
Jeppe Stig Nielsen

40

return (i & -i) == i


2
будь-який натяк, чому це не спрацює чи не буде? Я перевірив її правильність лише у java, де є лише підписані вступи / longs. якщо це правильно, це була б найкраща відповідь. швидше + менше
Andreas Petersson

7
Скористається одним із властивостей нотації двох доповнення: для обчислення від'ємного значення числа ви виконуєте побітове заперечення і додаєте 1 до результату. Найменш значущий біт, iякий встановлений, також буде встановлений -i. Біти нижче, які будуть 0 (в обох значеннях), тоді як біти над ним будуть перевернуті відносно один одного. Отже, значення i & -iволі буде найменш значущим встановленим бітом i(що є потужністю два). Якщо iмає однакове значення, то це був єдиний набір бітів. Він не працює, коли iдорівнює 0 з тієї ж причини, що i & (i - 1) == 0і.
Майкл Карман

6
Якщо iце непідписаний тип, доповнення двійок не має нічого спільного. Ви просто скористаєтеся властивостями модульної арифметики та порозрядних і.
R .. GitHub ЗАСТОСУЄТЬСЯ ДОПОМОГАТИ

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).
Стівен

Чому в цьому випадку десятковий прохід є потужністю двох?
Кріс Фрізіна


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

Наступний додаток до прийнятої відповіді може бути корисним для деяких людей:

Потужність двох, виражена у двійковій формі, завжди буде виглядати як 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 як приклад і зробивши цю математику всередині цього бінарного подання. Це створює плутанину.
Мічі

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


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));
конфігуратор

4
Зламаний. Страждає від основних питань округлення плаваючої точки. Використовуйте, frexpа не бридкі logречі, якщо ви хочете використовувати плаваючу крапку.
R .. GitHub ЗАСТОСУЄТЬСЯ ДОПОМОГА ICE

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потужність дві, його самотній 1 біт знаходиться в положенні n. Це означає, що x – 1має 0 n. Щоб зрозуміти, чому, згадайте, як працює двійкове віднімання. Віднімаючи 1 з x, запозичення поширюється повністю до позиції n; біт nстає 0, а всі нижчі біти стають 1. Тепер, оскільки xне має спільного 1 біта x – 1, x & (x – 1)це 0, і !(x & (x – 1))це правда.


3

Число є потужністю 2, якщо воно містить лише 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 є потужністю 2, оскільки він також повертає 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

повернення c як 'int', коли функція має тип повернення 'ulong'? Використання whileзамість if? Я особисто не бачу причини, але, здавалося б, це працює. EDIT: - ні ... він поверне 1 за щось більше, ніж 0!?
Джеймс Хоурі

@JamesKhoury Я писав програму c ++, тому я помилково повернув інт. Однак це були невеликі помилки і не заслужили скоромовки. Але я не розумію міркування для решти ваших коментарів "використовуючи, а не", "а" поверне 1 за будь-що більше, ніж 0 ". Я додав основну заглушку, щоб перевірити вихід. 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, так, інакше is_power_of_2 (0) повернеться правдою
Четвер,

3

для будь-якої потужності 2, також виконується наступне

n & (- n) == n

ПРИМІТКА: не вдається n = 0, тому потрібно перевірити це.
Причина, чому це працює:
-n є 2s доповненням n. -n матиме кожен біт ліворуч від правого встановленого біта n перевернутого в порівнянні з n. Для потужностей 2 існує лише один встановлений біт.


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

Марк Гравелл запропонував це, якщо у вас є .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)але менш портативна.


ти впевнений, що швидше, ніж (x != 0) && ((x & (x - 1)) == 0)? Сумніваюсь у цьому, особливо. на старих системах, де popcnt недоступний
phuclv

Це не швидше. Я щойно перевірив це на сучасному процесорі Intel і перевірив POPCNT, який використовується при розбиранні (надано, в коді С, не .NET). POPCNT швидше підраховує біти в цілому, але для одинарного випадку біт-трюк трюк все ще швидший на 10%.
eraoul

На жаль, я беру його назад. Я тестував у циклі, чи я вважаю, що передбачення галузей "обман". POPCNT - це дійсно одна інструкція, яка працює за один тактовий цикл і швидша, якщо у вас є.
ераол

0

У С я випробував i && !(i & (i - 1)трюк і порівняв його__builtin_popcount(i) , використовуючи gcc на Linux, із прапорцем -mpopcnt, щоб обов'язково використовувати інструкцію CPOP POPCNT. Моя тестова програма підраховувала число цілих чисел від 0 до 2 ^ 31, які були потужністю два.

Спочатку я подумав, що i && !(i & (i - 1)це на 10% швидше, хоча я перевірив, що POPCNT використовувався в розбиранні, де я використовував__builtin_popcount .

Однак я зрозумів, що я включив оператор 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

AMD Ryzen Threadripper 2950X 16-ядерний процесор макс. 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

0

Я бачу, що багато відповідей пропонують повернути n&&! (N & (n - 1)), але, на мій досвід, якщо вхідні значення негативні, вони повертають помилкові значення. Я поділюсь тут ще одним простим підходом, оскільки ми знаємо, що потужність двох чисел має лише один набір бітів, тому просто ми будемо рахувати кількість встановлених бітів, це займе час O (log N).

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

Перевірте цю статтю, щоб порахувати ні. заданих бітів


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

Спробуйте це для номера 9223372036854775809. Це працює? Думаю, ні, через помилки округлення.
конфігуратор

1
@configurator 922337203685477580_9_ не схоже на мене потужністю 2;)
Кіршштайн

1
@ Кіршштайн: це число дало йому хибний позитив.
Еріх Мірабал

7
Кіршштайн: Не схожий і на мене. Це схоже на функцію, хоча ...
конфігуратор

-2

Ця програма в Java повертає "true", якщо число є потужністю 2, і повертає "false", якщо це не сила 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

1
це питання позначено C #, і ваше рішення також дуже повільне порівняно з попередніми рішеннями
phuclv
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.