Побитовые операции — различия между версиями
Penguinni (обсуждение | вклад) (→Подсчет количества единичных битов) |
Penguinni (обсуждение | вклад) (→Источники информации) |
||
Строка 208: | Строка 208: | ||
* [https://graphics.stanford.edu/~seander/bithacks.html Bit Twiddling Hacks by Sean Eron Anderson] | * [https://graphics.stanford.edu/~seander/bithacks.html Bit Twiddling Hacks by Sean Eron Anderson] | ||
* [https://habrahabr.ru/post/93172/ Habrahabr {{---}} Алгоритмы поиска старшего бита] | * [https://habrahabr.ru/post/93172/ Habrahabr {{---}} Алгоритмы поиска старшего бита] | ||
− | + | * [https://yesteapea.wordpress.com/2013/03/03/counting-the-number-of-set-bits-in-an-integer/ STP's blog {{---}} Counting the number of set bits in an integer] | |
[[Категория: Дискретная математика и алгоритмы]] | [[Категория: Дискретная математика и алгоритмы]] | ||
[[Категория: Булевы функции ]] | [[Категория: Булевы функции ]] |
Версия 22:01, 20 марта 2016
Побитовые операции (англ. bitwise operations) — операции, производимые над цепочками битов. Выделяют два типа побитовых операций: логические операции и побитовые сдвиги.
Содержание
- 1 Принцип работы
- 2 Применение
- 2.1 Сложные операции
- 2.1.1 Определение знака числа
- 2.1.2 Вычисление модуля числа без использования условного оператора
- 2.1.3 Нахождение минимума и максимума из двух чисел без использования условного оператора
- 2.1.4 Проверка на то, является ли число степенью двойки
- 2.1.5 Нахождение младшего единичного бита
- 2.1.6 Нахождение старшего единичного бита
- 2.1.7 Подсчет количества единичных битов
- 2.1.8 Циклический сдвиг влево
- 2.1.9 Разворот битов
- 2.2 Применение для решения задач
- 2.1 Сложные операции
- 3 Примечания
- 4 Источники информации
Принцип работы
Логические побитовые операции
Битовые операторы И
, ИЛИ , НЕ и исключающее ИЛИ используют те же таблицы истинности, что и их логические эквиваленты.Побитовое И
Побитовое И используется для выключения битов. Любой бит, установленный в
, вызывает установку соответствующего бита результата также в .& | |
---|---|
11001010 11100010 | |
11000010 |
Побитовое ИЛИ
Побитовое ИЛИ используется для включения битов. Любой бит, установленный в
, вызывает установку соответствующего бита результата также в .| | |
---|---|
11001010 11100010 | |
11101010 |
Побитовое НЕ
Побитовое НЕ инвертирует состояние каждого бита исходной переменной.
~ | |
---|---|
11001010 | |
00110101 |
Побитовое исключающее ИЛИ
Исключающее ИЛИ устанавливает значение бита результата в
, если значения в соответствующих битах исходных переменных различны.^ | |
---|---|
11001010 11100010 | |
00101000 |
Побитовые сдвиги
Операторы сдвига двоичном дополнительном коде и необходимо поддерживать знаковый бит).
и сдвигают биты в переменной влево или вправо на указанное число. При этом на освободившиеся позиции устанавливаются нули (кроме сдвига вправо отрицательного числа, в этом случае на свободные позиции устанавливаются единицы, так как числа представляются вСдвиг влево может применяться для умножения числа на два, сдвиг вправо — для деления.
x = 7 // 00000111 (7) x = x >> 1 // 00000011 (3) x = x << 1 // 00000110 (6) x = x << 5 // 11000000 (-64) x = x >> 2 // 11110000 (-16)
В языке программирования Java существует также оператор беззнакового битового сдвига вправо
. При использовании этого оператора на освободившиеся позиции всегда устанавливаются нули.
x = 7 // 00000111 (7) x = x << 5 // 11100000 (-32) x = x >>> 2 // 00111000 (56)
Применение
Сложные операции
Определение знака числа
Пусть дано число
// n — разрядность числа if x != 0 mask = 1 else mask = 0 sign = mask | (x >> (n - 1)) // результатом будет -1, 0, или +1 // для отрицательного, равного нулю и положительного числа x соответственно
Используя побитовые операции можно также узнать, различны ли знаки двух переменных
и . Если числа имеют различный знак, то результат операции XOR, произведенной над их знаковыми битами, будет единицей. Поэтому неравенство будет верно в том случае, если числа и разного знака.Вычисление модуля числа без использования условного оператора
Пусть дано число
коде со сдвигом с тем отличием, что у нас знаковый бит принимает значение для отрицательных чисел, а — для положительных.
// n — разрядность числа mask = x >> n - 1 abs = (x + mask)mask // другой способ сделать то же самое: abs = (x mask) - mask
Нахождение минимума и максимума из двух чисел без использования условного оператора
Этот способ корректен только если можно утверждать, что величина
лежит между граничными значениями типа int.Пусть даны числа
// n — разрядность чисел min = y + ((x - y) & ((x - y) >> (n - 1))) max = x - ((x - y) & ((x - y) >> (n - 1)))
Проверка на то, является ли число степенью двойки
Пусть дано число
. Тогда, если результатом выражения является единица, то число — степень двойки.Правая часть выражения
будет равна единице только если число равно или является степенью двойки. Если число является степенью двойки, то в двоичной системе счисления оно представляется следующим образом: , где — показатель степени. Соответственно, выражение будет иметь вид , и равно .Операция логического И в данном выражении отсекает тот случай, когда
и не является степенью двойки, но при этом правая часть равна единице.Нахождение младшего единичного бита
Пусть дано число
и необходимо узнать его младший единичный бит.Применим к числу
побитовое отрицание, чтобы инвертировать значения всех его бит, а затем прибавим к полученному числу единицу. У результата первая часть (до младшего единичного бита) не совпадает с исходным числом , а вторая часть совпадает. Применив побитовое И к этим двум числам, получим степень двойки, соответствующую младшему единичному биту исходного числа .К такому же результату можно прийти, если сначала отнять от числа
единицу, чтобы обнулить его младший единичный бит, а все последующие разряды обратить в , затем инвертировать результат и применить побитовое И с исходным числом .Нахождение старшего единичного бита
Пусть дано число
и необходимо узнать его старший единичный бит.Рассмотрим некоторое число, представим его как
int power = 1
for i = 1..
: // n — разрядность числа
x |= x >> power
power <<= 1
result = x - (x >> 1)
Подсчет количества единичных битов
Для подсчета количества единичных битов в числе
// Пример приведен для 16-ти битных чисел. // Для чисел большей разрядности необходимо использовать соответствующие константы. x = x - ((x >>> 1) & 0x5555) x = (x & 0x3333) + ((x >>> 2) & 0x3333) x = (x + (x >>> 4)) & 0x0F0F answer = (x * 0x0101) >>> 8
Поскольку
равно , результатом операции является число, в котором все нечетные биты соответствуют нечетным битам числа . Аналогично, результатом операции является число, в котором все нечетные биты соответствуют четным битам . Четные биты результата в обоих случаях равны нулю.Мысленно разобьем двоичную запись нашего числа
на группы по бита. Результатом операции будет такое число, что если разбить его двоичную запись на группы по два бита, значение каждой группы соответствует количеству единичных битов в соответствующей паре битов числа .Аналогично, число
равно и операция , примененная к результату, полученному на первом этапе, выполняет подсчет количества единичных битов в блоках по . В свою очередь, число равно и операция позволяет подсчитать число единичных бит в блоках по .Теперь необходимо просуммировать числа, записанные в блоках по
битов, чтобы получить искомую величину. Это можно сделать, домножив результат на . Ответ на задачу будет находиться в первых восьми битах произведения. Выполнив сдвиг вправо на (для шестнадцатибитных чисел), мы получим долгожданный ответ.Подведем итог:
x = (x & 0x5555) + ((x >>> 1) & 0x5555) x = (x & 0x3333) + ((x >>> 2) & 0x3333) x = (x & 0x0F0F) + ((x >>> 4) & 0x0F0F) answer = (x * 0x0101) >>> 8
Заметим, что операция
равносильна операции , в чем легко убедиться, рассмотрев все числа из двух бит.В свою очередь, операцию
можно заменить на . Эта замена не повлияет на результат, так как максимальное значение в любой группе из четырех битов данного числа равно четырем, то есть требует только трех битов для записи, и выполнение суммирования не повлечет за собой переполнения и выхода за пределы четверок.Таким образом, мы получили код, приведенный в начале раздела.
Циклический сдвиг влево
Пусть дано число
и надо совершить циклический сдвиг его битов влево. Желаемый результат можно получить, если объединить числа, полученные при выполнении обычного битового сдвига влево на необходимую величину и вправо на разность между разрядностью числа и величиной сдвига. Таким образом, мы сможем поменять местами начальную и конечную части числа.
int head, tail head = x << a // x — изменяемое число // a — число позиций, на которое хотим выполнить сдвиг if a != 0: tail = x >> (n - a) // n — разрядность числа x else: tail = 0 result = head | tail
Разворот битов
Чтобы получить биты числа
, записанные в обратном порядке, будем снимать значение младшего бита, умножать его на старшую незаписанную степень двойки и сдвигать число вправо на единицу для обработки следующих бит.
result = 0 position = 1 << (n - 1) // n — разрядность числа x while x != 0: result += (x & 1) * position position >>= 1 x >>= 1
Применение для решения задач
Работа с битовыми масками
Для работы с подмножествами удобно использовать битовые маски. Применяя побитовые операции легко сделать следующее: найти дополнение
, пересечение , объединение множеств, установить бит по номеру , снять бит по номеру .Битовые маски используются, например, при решении некоторых задач[1] динамического программирования.