Побитовые операции — различия между версиями
Penguinni (обсуждение | вклад) (→Побитовые сдвиги) |
Penguinni (обсуждение | вклад) |
||
Строка 65: | Строка 65: | ||
==Применение== | ==Применение== | ||
===Сложные операции=== | ===Сложные операции=== | ||
− | ==== | + | ====Определение знака числа==== |
− | Пусть дано число <tex>x</tex> и | + | Пусть дано число <tex>x</tex>. Поскольку при сдвиге вправо на освобождающиеся позиции устанавливается бит знака, знак числа <tex>x</tex> можно определить, выполнив сдвиг вправо на всю длину переменной: |
+ | <code> | ||
+ | <font color = green>// n {{---}} разрядность числа</font> | ||
+ | |||
+ | '''if''' x != 0 | ||
+ | mask = 1 | ||
+ | '''else''' | ||
+ | mask = 0 | ||
+ | |||
+ | sign = mask | (x >> (n - 1)) <font color = green>// результатом будет -1, 0, или +1 | ||
+ | // для отрицательного, равного нулю и положительного числа x соответственно</font> | ||
+ | </code> | ||
+ | Используя побитовые операции можно также узнать, различны ли знаки двух переменных <tex>x</tex> и <tex>y</tex>. Если числа имеют различный знак, то результат операции XOR, произведенной над их знаковыми битами, будет единицей. Поэтому неравенство <tex>((x \oplus y) < 0</tex> будет верно в том случае, если числа <tex>x</tex> и <tex>y</tex> разного знака. | ||
− | + | ====Вычисление модуля числа без использования условного оператора==== | |
− | + | Пусть дано число <tex>x</tex>. Если <tex>x</tex> положительно, то <tex>mask = 0</tex>, и <tex>(x + mask) \oplus mask = x</tex>. В случае, если <tex>x</tex> отрицательно, <tex>mask = -1</tex>. Тогда получается, что мы работаем с числом <tex>x</tex> так, как будто оно представлено в [[Представление целых чисел: прямой код, код со сдвигом, дополнительный код #Код со сдвигом | | |
− | + | коде со сдвигом]] с тем отличием, что у нас знаковый бит принимает значение <tex>1</tex> для отрицательных чисел, а <tex>0</tex> {{---}} для положительных. | |
<code> | <code> | ||
− | + | <font color = green>// n {{---}} разрядность числа</font> | |
− | + | mask = x >> n - 1 | |
− | + | ||
− | + | abs = (x + mask) <tex>\oplus</tex> mask | |
− | + | <font color = green>// другой способ сделать то же самое:</font> | |
+ | abs = (x <tex>\oplus</tex> mask) - mask | ||
</code> | </code> | ||
− | + | ====Нахождение минимума и максимума из двух чисел без использования условного оператора==== | |
− | + | Этот способ корректен только если можно утверждать, что величина <tex>(x - y)</tex> лежит между граничными значениями типа int. | |
− | |||
+ | Пусть даны числа <tex>x</tex> и <tex>y</tex>. Тогда, если <tex>x < y</tex>, то <tex>((x - y) \texttt{>>} (n - 1)) = -1</tex> а если <tex>x \geqslant y</tex>, то <tex>((x - y) \texttt{>>} (n - 1)) = 0</tex>. Выражение <tex>((x - y) \& ((x - y) \texttt{>>} (n - 1))</tex> принимает значение <tex>0</tex>, если <tex>x \geqslant y</tex> и <tex>(x - y)</tex>, если <tex>x < y</tex>. | ||
<code> | <code> | ||
− | + | <font color = green>// n {{---}} разрядность чисел</font> | |
− | + | min = y + ((x - y) & ((x - y) >> (n - 1))) | |
− | + | max = x - ((x - y) & ((x - y) >> (n - 1))) | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
</code> | </code> | ||
+ | |||
+ | ====Проверка на то, является ли число степенью двойки==== | ||
+ | Пусть дано число <tex>x</tex>. Тогда, если результатом выражения <tex>(x\ \&\&\ !(x\ \&\ (x - 1)))</tex> является единица, то число <tex>x</tex> {{---}} степень двойки. | ||
+ | |||
+ | Правая часть выражения <tex>(!(x\ \&\ (x - 1)))</tex> будет равна единице только если число <tex>x</tex> равно <tex>0</tex> или является степенью двойки. Если число <tex>x</tex> является степенью двойки, то в двоичной системе счисления оно представляется следующим образом: <tex>1\underbrace{0\dots0}_{n}</tex>, где <tex>n</tex> {{---}} показатель степени. Соответственно, выражение <tex>(x - 1)</tex> будет иметь вид <tex>\underbrace{1\dots1}_{n}</tex>, и <tex>x\ \&\ (x - 1)</tex> равно <tex>0</tex>. | ||
+ | |||
+ | Операция логического И в данном выражении отсекает тот случай, когда <tex>(x = 0)</tex> и не является степенью двойки, но при этом правая часть <tex>(!(x\ \&\ (x - 1)))</tex> равна единице. | ||
====Нахождение младшего единичного бита==== | ====Нахождение младшего единичного бита==== | ||
− | Пусть дано число <tex>x</tex> и необходимо узнать его младший единичный бит. | + | Пусть дано число <tex>x</tex> и необходимо узнать его младший единичный бит. |
− | |||
− | |||
Применим к числу <tex>x</tex> побитовое отрицание, чтобы инвертировать значения всех его бит, а затем прибавим к полученному числу единицу. У результата первая часть (до младшего единичного бита) не совпадает с исходным числом <tex>x</tex>, а вторая часть совпадает. Применив побитовое И к этим двум числам, получим степень двойки, соответствующую младшему единичному биту исходного числа <tex>(x\ \&\ (\sim x + 1))</tex>. | Применим к числу <tex>x</tex> побитовое отрицание, чтобы инвертировать значения всех его бит, а затем прибавим к полученному числу единицу. У результата первая часть (до младшего единичного бита) не совпадает с исходным числом <tex>x</tex>, а вторая часть совпадает. Применив побитовое И к этим двум числам, получим степень двойки, соответствующую младшему единичному биту исходного числа <tex>(x\ \&\ (\sim x + 1))</tex>. | ||
Строка 105: | Строка 116: | ||
К такому же результату можно прийти, если сначала отнять от числа <tex>x</tex> единицу, чтобы обнулить его младший единичный бит, а все последующие разряды обратить в <tex>1</tex>, затем инвертировать результат и применить побитовое И с исходным числом <tex>(x\ \&\ \sim (x - 1))</tex>. | К такому же результату можно прийти, если сначала отнять от числа <tex>x</tex> единицу, чтобы обнулить его младший единичный бит, а все последующие разряды обратить в <tex>1</tex>, затем инвертировать результат и применить побитовое И с исходным числом <tex>(x\ \&\ \sim (x - 1))</tex>. | ||
− | + | ====Нахождение старшего единичного бита==== | |
− | + | Пусть дано число <tex>x</tex> и необходимо узнать его старший единичный бит. | |
− | |||
+ | Рассмотрим некоторое число, представим его как <tex>0\dots01b \dots b</tex>, где <tex>b</tex> {{---}} любое значение бита. Тогда, если совершить битовый сдвиг этого числа вправо на <tex>1</tex> и произвести побитовое ИЛИ результата сдвига и исходного числа, мы получим результат <tex>0\dots011b \dots b</tex>. Если мы повторим эту последовательность действий над полученным числом, но устроим сдвиг на <tex>2</tex>, то получим <tex>0\dots01111b \dots b</tex>. При каждой следующей операции будем увеличивать модуль сдвига до следующей степени двойки. После некоторого количества таких операций (зависит от разрядности числа) мы получим число вида <tex>0\dots01\dots1</tex>. Тогда результатом выполнения действий <tex>x - (x \texttt{ >> }1)</tex> будет число, состоящее только из старшего бита исходного числа. | ||
<code> | <code> | ||
− | '''int''' | + | '''int''' power = 1 |
− | ''' | + | '''for''' i = 1..<tex>\log_2{n}</tex>: <font color = green>// n {{---}} разрядность числа</font> |
− | + | x |= x >> power | |
− | + | power <<= 1 | |
− | + | result = x - (x >> 1) | |
− | |||
− | |||
− | |||
− | |||
− | result = | ||
</code> | </code> | ||
Строка 168: | Строка 174: | ||
position >>= 1 | position >>= 1 | ||
x >>= 1 | x >>= 1 | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
</code> | </code> | ||
Версия 21:14, 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)
Применение
Сложные операции
Определение знака числа
Пусть дано число
// 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)
Подсчет количества единичных битов
Способ 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
Применение для решения задач
Работа с битовыми масками
Для работы с подмножествами удобно использовать битовые маски. Применяя побитовые операции легко сделать следующее: найти дополнение
, пересечение , объединение множеств, установить бит по номеру , снять бит по номеру .Битовые маски используются, например, при решении некоторых задач[1] динамического программирования.