Как проверить, является ли число степенью двойки на C#?
Элегантное решение с битовой маской
Ваш код рабочий, но действительно неоптимальный. Лучший способ проверить, является ли число степенью двойки — использовать битовую операцию 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;
}
Разбор по шагам:
- Проверка n > 0 — отсекает ноль и отрицательные числа (степени двойки всегда положительны).
- (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. Логарифмический подход полезен только для обучения или когда нужно проверить степень двойки в контексте других математических операций.