Побитовые операции — различия между версиями
Penguinni (обсуждение | вклад) (→Нахождение минимума и максимума из двух чисел без использования условного оператора) |
Penguinni (обсуждение | вклад) (→Сложные операции) |
||
| Строка 66: | Строка 66: | ||
===Сложные операции=== | ===Сложные операции=== | ||
====Нахождение старшего единичного бита==== | ====Нахождение старшего единичного бита==== | ||
| + | Пусть дано число <tex>x</tex> и необходимо узнать его старший единичный бит. Рассмотрим два способа, с помощью которых можно решить эту задачу. | ||
| + | |||
'''Способ 1''' | '''Способ 1''' | ||
| Строка 95: | Строка 97: | ||
====Нахождение младшего единичного бита==== | ====Нахождение младшего единичного бита==== | ||
| + | Пусть дано число <tex>x</tex> и необходимо узнать его младший единичный бит. Задачу можно решить несколькими способами. | ||
| + | |||
'''Способ 1''' | '''Способ 1''' | ||
| Строка 140: | Строка 144: | ||
====Циклический сдвиг влево==== | ====Циклический сдвиг влево==== | ||
| − | + | Пусть дано число <tex>x</tex> и надо совершить циклический сдвиг его битов влево. | |
| + | Желаемый результат можно получить, если объединить числа, полученные при выполнении обычного битового сдвига влево на необходимую величину и вправо на разность между разрядностью числа и величиной сдвига. Таким образом, мы сможем поменять местами начальную и конечную части числа. | ||
<code> | <code> | ||
Версия 20:35, 13 марта 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)
Применение
Сложные операции
Нахождение старшего единичного бита
Пусть дано число и необходимо узнать его старший единичный бит. Рассмотрим два способа, с помощью которых можно решить эту задачу.
Способ 1
Рассмотрим некоторое число, представим его как , где — любое значение бита. Тогда, если совершить битовый сдвиг этого числа вправо на и произвести побитовое ИЛИ результата сдвига и исходного числа, мы получим результат . Если мы повторим эту последовательность действий над полученным числом, но устроим сдвиг на , то получим . При каждой следующей операции будем увеличивать модуль сдвига до следующей степени двойки. После некоторого количества таких операций (зависит от разрядности числа) мы получим число вида . Тогда результатом выполнения действий будет число, состоящее только из старшего бита исходного числа.
int power = 1
for i = 1..: // n — разрядность числа
x |= x >> power
power <<= 1
result = x - (x >> 1)
Способ 2
Способ основан на бинпоиске. Будем искать максимальную степень двойки, меньшую числа .
int x, m // x — исходное число
int l = n // n — разрядность числа
int r = -1
while r < l - 1:
m = (l + r) / 2
if (1 << m) x:
r = m
else:
l = m
result = r
Нахождение младшего единичного бита
Пусть дано число и необходимо узнать его младший единичный бит. Задачу можно решить несколькими способами.
Способ 1
Применим к числу побитовое отрицание, чтобы инвертировать значения всех его бит, а затем прибавим к полученному числу единицу. У результата первая часть (до младшего единичного бита) не совпадает с исходным числом , а вторая часть совпадает. Применив побитовое И к этим двум числам, получим степень двойки, соответствующую младшему единичному биту исходного числа .
К такому же результату можно прийти, если сначала отнять от числа единицу, чтобы обнулить его младший единичный бит, а все последующие разряды обратить в , затем инвертировать результат и применить побитовое И с исходным числом .
Способ 2
Алгоритм аналогичен описанному выше способу нахождения старшего единичного бита и также основан на бинпоиске. Будем искать максимальное число вида такое, что его побитовое И с числом дает .
int x, m // x — исходное число
int l = n // n — разрядность числа
int r = -1
while r < l - 1:
m = (l + r) / 2
if ((1 << m) - 1) & x == 0:
r = m
else:
l = m
result = r
Подсчет количества единичных битов
Способ 1
Самый простой способ посчитать количество единичных битов в числе — сдвигать его на вправо до тех пор, пока оно не станет равно нулю и в процессе смотреть на последний бит.
int answer = 0
while x != 0:
answer += x & 1
x >> 1
Способ 2
Пусть мы хотим посчитать количество единичных битов в числе . Если вычесть из единицу, то его младший единичный бит обнулится, а все последующие за ним биты примут значение . Если произвести операцию побитового И между и , то мы получим число, побитово равное во всех позициях, кроме младшего единичного бита (в результирующем числе он будет нулевым). Таким образом, количество совершенных операций до того момента, как исходное число обратится в ноль, будет равно количеству единичных битов в нём.
int answer = 0
while x != 0:
x &= x - 1
answer++
Циклический сдвиг влево
Пусть дано число и надо совершить циклический сдвиг его битов влево. Желаемый результат можно получить, если объединить числа, полученные при выполнении обычного битового сдвига влево на необходимую величину и вправо на разность между разрядностью числа и величиной сдвига. Таким образом, мы сможем поменять местами начальную и конечную части числа.
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
Проверка на то, является ли число степенью двойки
Пусть дано число . Тогда, если результатом выражения является единица, то число — степень двойки.
Правая часть выражения будет равна единице только если число равно или является степенью двойки. Если число является степенью двойки, то в двоичной системе счисления оно представляется следующим образом: , где — показатель степени. Соответственно, выражение будет иметь вид , и равно .
Операция логического И в данном выражении отсекает тот случай, когда и не является степенью двойки, но при этом правая часть равна единице.
Определение знака числа
Пусть дано число . Поскольку при сдвиге вправо на освобождающиеся позиции устанавливается бит знака, знак числа можно определить, выполнив сдвиг вправо на всю длину переменной:
// 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)))
Применение для решения задач
Работа с битовыми масками
Для работы с подмножествами удобно использовать битовые маски. Применяя побитовые операции легко сделать следующее: найти дополнение , пересечение , объединение множеств, установить бит по номеру , снять бит по номеру .
Битовые маски используются, например, при решении некоторых задач[1] динамического программирования.