Сегодня мне нужен был простой алгоритм проверки, является ли число степенью 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
.
Есть ли лучший алгоритм?
(x & (x - 1))
может возвращать ложные срабатывания, когдаX
это сумма степеней двух, например8 + 16
.Ответы:
Для этой проблемы есть простой трюк:
Обратите внимание, что эта функция будет отчитываться
true
за0
, что не сила2
. Если вы хотите исключить это, вот как:объяснение
Прежде всего, побитовый двоичный оператор & из определения MSDN:
Теперь давайте посмотрим, как все это закончится:
Функция возвращает логическое значение (true / false) и принимает один входящий параметр типа unsigned long (в данном случае x). Давайте для простоты предположим, что кто-то передал значение 4 и вызвал функцию следующим образом:
Теперь мы заменим каждое вхождение x на 4:
Ну, мы уже знаем, что 4! = 0 соответствует истине, пока все хорошо. Но что насчет:
Это переводит к этому конечно:
Но что именно
4&3
?Двоичное представление 4 равно 100, а двоичное представление 3 - 011 (помните, что & принимает двоичное представление этих чисел). Итак, мы имеем:
Представьте, что эти значения складываются во многом как элементарное сложение.
&
Оператор говорит , что , если оба значения равны 1 , то результат будет 1, в противном случае он равен 0. Таким образом1 & 1 = 1
,1 & 0 = 0
,0 & 0 = 0
и0 & 1 = 0
. Итак, мы делаем математику:Результат равен 0. Итак, мы вернемся и посмотрим, что теперь означает в нашем операторе возврата:
Что теперь переводится как:
Мы все знаем, что
true && true
это простоtrue
, и это показывает, что для нашего примера 4 - это степень 2.источник
Некоторые сайты, которые документируют и объясняют этот и другие хитрые взломы:
( http://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 )
( http://bits.stephan-brumme.com/isPowerOfTwo.html )
И их дедушка, книга «Восхищение Хакера» Генри Уоррена-младшего :
Как объясняет страница Шона Андерсона , выражение
((x & (x - 1)) == 0)
неправильно указывает, что 0 - это степень 2. Он предлагает использовать:чтобы исправить эту проблему.
источник
!
может применяться только к логическим типам, а&&
также требует, чтобы оба операнда были логическими (за исключением пользовательских операторов) сделать другие вещи возможными, но это не имеет отношения кulong
.)return (i & -i) == i
источник
i
который установлен, также будет установлен в-i
. Биты ниже этого будут 0 (в обоих значениях), в то время как биты выше этого будут инвертированы относительно друг друга. Следовательно, значениеi & -i
будет самым младшим установленным битомi
(который является степенью двойки). Еслиi
имеет то же значение, то это был единственный установленный бит. Это не удается, когдаi
0 по той же причине, чтоi & (i - 1) == 0
и.i
это тип без знака, дополнение к двойке не имеет к нему никакого отношения. Вы просто используете в своих интересах свойства модульной арифметики и побитовых и.i==0
(возвращает,(0&0==0)
который естьtrue
). Так и должно бытьreturn i && ( (i&-i)==i )
источник
Недавно я написал статью об этом на http://www.exploringbinary.com/ten-ways-to-check-if-an-integer-is-a-power-of-two-in-c/ . Он включает подсчет битов, как правильно использовать логарифмы, классическую проверку «x &&! (X & (x - 1))» и другие.
источник
Вот простое решение C ++ :
источник
__builtin_popcount
. К сожалению, в одном семействе процессоров пока нет ни одной инструкции по сборке для этого (x86), поэтому это самый быстрый метод подсчета битов. На любой другой архитектуре это отдельная инструкция по сборке.popcnt
Следующее дополнение к принятому ответу может быть полезно для некоторых людей:
Степень двойки, выраженная в двоичном виде, всегда будет выглядеть как 1, за которым следуют n нулей, где n больше или равно 0. Пример:
и так далее.
Когда мы вычитаем
1
из этих чисел, они становятся 0, за которыми следуют n и снова n такое же, как указано выше. Пример:и так далее.
Подойдя к сути
Один из
x
выровнен с нулем,x - 1
а все нулиx
выровнены с единицами изx - 1
, в результате чего побитовое И приводит к 0. И вот как мы имеем упомянутый выше однострочный ответ, который является правильным.Дальнейшее добавление к красоте принятого ответа выше -
Итак, теперь у нас есть недвижимость:
Одним из удивительных применений этого свойства является выяснение - сколько единиц присутствует в двоичном представлении данного числа? Краткий и приятный код для этого целого числа
x
:Другой аспект чисел, который может быть доказан из концепции, объясненной выше: «Может ли каждое положительное число быть представлено в виде суммы степеней 2?»,
Да, каждое положительное число может быть представлено как сумма степеней 2. Для любого числа возьмите его двоичное представление. Пример: взять номер
117
.источник
После публикации вопроса я подумал о следующем решении:
Нам нужно проверить, является ли ровно одна из двоичных цифр одной. Таким образом, мы просто сдвигаем число на одну цифру за раз и возвращаем,
true
если оно равно 1. Если в любой момент мы получаем нечетное число ((number & 1) == 1
), мы знаем, что результат равенfalse
. Это оказалось (с помощью эталона) немного быстрее, чем оригинальный метод для (больших) истинных значений и намного быстрее для ложных или малых значений.Конечно, решение Грега намного лучше.
источник
И вот общий алгоритм для определения, является ли число степенью другого числа.
источник
источник
c#
? Я предполагаю, что это так же,c++
какx
возвращается как bool.Найдите, является ли данное число степенью 2.
источник
frexp
а не противныеlog
вещи, если вы хотите использовать с плавающей запятой.источник
Это действительно быстро. Проверка всех 2 ^ 32 целых чисел занимает около 6 минут 43 секунды.
источник
Если
x
это степень двойки, то его одиночный 1 бит находится в позицииn
. Это означаетx – 1
, что в позиции 0n
. Чтобы понять почему, вспомните, как работает двоичное вычитание. Вычитая 1 изx
, заем распространяется до позицииn
; битn
становится равным 0, а все младшие биты становятся равными 1. Теперь, такx
как не имеет общего с 1 битомx – 1
, онx & (x – 1)
равен 0 и!(x & (x – 1))
является истинным.источник
Число является степенью 2, если оно содержит только 1 установленный бит. Мы можем использовать это свойство и универсальную функцию,
countSetBits
чтобы определить, является ли число степенью 2 или нет.Это программа на C ++:
Нам не нужно явно проверять, что 0 является степенью 2, поскольку он также возвращает False для 0.
ВЫВОД
источник
while
вместоif
? Я лично не вижу причины, но это, кажется, работает. РЕДАКТИРОВАТЬ: - нет ... он вернет 1 для чего-то большего, чем0
!?Вот другой метод, который я разработал, в данном случае
|
вместо&
:источник
(x > 0)
немного здесь?для любой степени 2 справедливо также следующее.
п & (- п) == п
ПРИМЕЧАНИЕ: не работает при n = 0, поэтому необходимо проверить это.
Причина, по которой это работает:
-n является дополнением 2s для n. -n будет иметь каждый бит слева от крайнего правого установленного бита n по сравнению с n. Для степеней 2 есть только один установленный бит.
источник
пример
Алгоритм
Используя битовую маску, разделите
NUM
переменную в двоичном видеIF R > 0 AND L > 0: Return FALSE
Иначе
NUM
становится тот, который ненулевойIF NUM = 1: Return TRUE
В противном случае перейдите к шагу 1
сложность
Время ~
O(log(d))
гдеd
число двоичных цифристочник
Улучшение ответа @ user134548, без арифметики битов:
Это прекрасно работает для:
источник
Марк Гравелл предложил это, если у вас есть .NET Core 3, System.Runtime.Intrinsics.X86.Popcnt.PopCount
Отдельная инструкция, быстрее,
(x != 0) && ((x & (x - 1)) == 0)
но менее переносимая.источник
(x != 0) && ((x & (x - 1)) == 0)
? Я сомневаюсь, что, особенно на старых системах, где popcnt недоступенВ Си я проверил
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 с максимальной частотой 3,90 ГГц
16-ядерный процессор AMD Ryzen Threadripper 2950X, максимум 3,50 ГГц
Обратите внимание, что здесь процессор Intel выглядит немного медленнее, чем AMD, но немного быстрее POPCNT; AMD POPCNT не дает такой поддержки.
popcnt_test.c:
Запустите тесты:
источник
Я вижу, что многие ответы предлагают вернуть n &&! (N & (n - 1)), но по моему опыту, если входные значения отрицательны, он возвращает ложные значения. Здесь я поделюсь еще одним простым подходом, так как мы знаем, что у числа из двух чисел есть только один установленный бит, поэтому просто мы посчитаем количество установленных бит, это займет O (log N) времени.
Проверьте эту статью, чтобы рассчитывать нет. из установленных бит
источник
источник
Эта программа в Java возвращает «true», если число является степенью 2, и возвращает «false», если это не степень 2
источник