Avatar ·

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

Всем привет! Я новичок в C# и столкнулся с задачей: нужно написать метод, который проверяет, является ли целое число степенью двойки (1, 2, 4, 8, 16, 32 и т.д.). Кажется, что задача простая, но я не могу придумать элегантное решение. Пока я написал вот такой код: ```csharp public static bool IsPowerOfTwo(int n) { if (n <= 0) return false; while (n > 1) { if (n % 2 != 0) return false; n = n / 2; } return true; } ``` Он вроде работает, но мне кажется, что это слишком громоздко и неэффективно. Я слышал, что есть какой-то хитрый способ с битовыми операциями, но не понимаю, как это работает. Можете подсказать более правильное и быстрое решение? Особенно интересуют: 1. Как сделать проверку без циклов? 2. Что такое битовые операции и как их применить? 3. Есть ли встроенные методы в .NET для этого? 4. Как обрабатывать отрицательные числа и ноль? Заранее спасибо за помощь! Код должен работать на .NET Core, если это важно. <<>>

Элегантное решение с битовой маской

Ваш код рабочий, но действительно неоптимальный. Лучший способ проверить, является ли число степенью двойки — использовать битовую операцию AND. Математически степень двойки в двоичной системе имеет ровно один установленный бит. Например:

  • 1 (2^0) = 0001
  • 2 (2^1) = 0010
  • 4 (2^2) = 0100
  • 8 (2^3) = 1000

Ключевое наблюдение: если вычесть 1 из такого числа, все младшие биты станут единицами, а старший — нулем:

8 = 1000
7 = 0111
8 & 7 = 0000 (ноль)

Для не-степеней двойки этот трюк не сработает:

6 = 0110
5 = 0101
6 & 5 = 0100 (не ноль)

Итоговый метод:

public static bool IsPowerOfTwo(int n)
{
    return n > 0 && (n & (n - 1)) == 0;
}

Разбор по шагам:

  1. Проверка n > 0 — отсекает ноль и отрицательные числа (степени двойки всегда положительны).
  2. (n & (n - 1)) == 0 — проверяет, что в двоичном представлении только один бит равен 1.

Примеры работы:

IsPowerOfTwo(1);   // true  (1 & 0 = 0)
IsPowerOfTwo(2);   // true  (2 & 1 = 0)
IsPowerOfTwo(3);   // false (3 & 2 = 2)
IsPowerOfTwo(16);  // true  (16 & 15 = 0)
IsPowerOfTwo(-4);  // false (отрицательное)
IsPowerOfTwo(0);   // false (ноль)

Почему это работает?

Битовое представление числа n, являющегося степенью двойки, всегда выглядит как 1000...0. Вычитание единицы даёт 0111...1. Побитовое AND этих двух чисел даёт ноль, так как ни один бит не совпадает. Для всех остальных чисел хотя бы один бит будет общим.

Встроенные методы в .NET

Начиная с .NET Core 3.0, появился метод BitOperations.IsPow2():

using System.Numerics;

public static bool IsPowerOfTwo(int n)
{
    return BitOperations.IsPow2(n);
}

Он работает для int, long, uint и ulong. Внутри он использует ту же битовую оптимизацию.

Обработка граничных случаев

Для int.MaxValue (2^31 - 1) — false. Для int.MinValue (-2^31) — false (отрицательное). Для больших чисел используйте long или BigInteger.

<<>>

Альтернативный подход: проверка через логарифм

Хотя битовый метод — самый быстрый, для понимания можно использовать математический подход. Число является степенью двойки, если его логарифм по основанию 2 — целое число.

public static bool IsPowerOfTwo(int n)
{
    if (n <= 0) return false;
    double log = Math.Log(n, 2);
    return log == Math.Floor(log);
}

Недостатки этого метода:

  • Погрешности вычислений с плавающей точкой — для больших чисел (например, 2^31) может дать ложный результат из-за округления.
  • Медленнее — вызов Math.Log требует больше процессорного времени, чем битовая операция.
  • Не подходит для очень больших чисел (например, BigInteger).

Поэтому рекомендуется использовать битовый метод или BitOperations.IsPow2. Логарифмический подход полезен только для обучения или когда нужно проверить степень двойки в контексте других математических операций.