Побитовые операции

Иногда требуется обрабатывать числа на уровне битов. Для этих целей существует группа побитовых операций, которые применимы только к целочисленным типам.

В таблице, в порядке убывания приоритетов, приведены все символы и описания побитовых операторов с указанием ассоциативности.

П

Символы

Описание

Пример

А

2

~

Побитовое отрицание (инверсия)

~e1

R

5

<<

Сдвиг влево

e1 << e2

L

5

>>

Сдвиг вправо

e1 >> e2

L

8

&

Побитовое И

e1 & e2

L

9

^

Побитовое исключающее ИЛИ

e1 ^ e2

L

10

|

Побитовое ИЛИ

e1 | e2

L

Из всей группы только операция побитового отрицания '~' является унарной, все остальные — бинарные.

Во всех случаях, если операнд по размеру меньше int/uint, то он предварительно расширяется до int/uint-а, путем добавления 0-битов в старшие разряды. В зависимости от знаковости типа операнда старший бит может влиять на знак.

Понять представление чисел на битовом уровне может помочь стандартное приложение Windows Калькулятор. Если в меню Вид выбрать режим работы Программист, в программе появляются группы переключателей для выбора отображения числа в шестнадцатеричной (Hex), десятичной (Dec), восьмеричной (Oct) и бинарной (Bin) форме. Последняя как раз показывает биты. Кроме того, можно выбрать размер числа: 1, 2, 4 или 8 байт. Кнопки позволяют выполнять все рассматриваемые операции: Not ('~'), And ('&'), Or ('|'), Xor ('^'), Lsh ('<<'), Rsh ('>>').
 
Поскольку Калькулятор использует знаковые числа, при переключении в десятичный режим возможно появление отрицательных значений (напомним, старший бит интерпретируется как знак). Для удобства анализа имеет смысл исключить появление минуса, для чего необходимо выбирать размер в байтах на одну градацию больше. Например, для проверки значений в диапазоне до 255 (uchar, беззнаковое однобайтовое целое), следует выбрать 2 байта (иначе положительными будут только десятичные значения до 127 включительно, а остальные отобразятся в отрицательную область).

Побитовое отрицание создает значение, в котором на месте всех 1-битов стоит 0-бит, а на месте 0-битов — 1-бит. Например, отрицание байта со всеми нулевыми битами, дает байт со всеми единичными битами. Число 50 в побитовом формате выглядит как '00110010' (байт). Его инверсия дает '11001101'.

Единица в шестнадцатеричном представлении — это 0x0001 (для short). Инверсия этих битов дает 0xFFFE (см. скрипт ExprBitwise.mq5).

short v = ~1;  // 0xfffe = -2
ushort w = ~1// 0xfffe = 65534

Побитовое И проверяет каждый бит в обоих операндах, и в тех позициях, где обнаруживаются два взведенных бита (1), сохраняет в результат 1-бит. Во всех остальных случаях (когда взведенный бит есть только в одном операнде или они сброшены и там, и там) в результат записывается 0-бит.

Побитовое ИЛИ записывает в результат 1-биты на тех позициях, на которых взведенный бит есть хотя бы в одном из двух операндов.

Побитовое исключающее ИЛИ записывает в результат 1-биты на тех позициях, на которых взведенный бит есть либо в первом, либо во втором операнде, но не одновременно. Ниже показано бинарное представление двух чисел X и Y и результаты побитовых операций с ними.

X       10011010   154
Y       00110111    55
 
X & Y   00010010    18
X | Y   10111111   191
X ^ Y   10101101   173

При написании сложных выражений из нескольких разных операторов используйте группировку с помощью круглых скобок, чтобы не путаться с приоритетами.

Операции сдвига перемещают биты влево ('<<') или вправо ('>>') на количество битов, заданное во втором операнде, который должен быть неотрицательным целым числом. В результате левые биты (для '<<') или правые биты (для '>>') отбрасываются, так как выходят за границы ячейки памяти. При сдвиге влево, справа добавляется соответствующее количество 0-битов. При сдвиге вправо, слева добавляются либо 0-биты (если операнд беззнаковый), либо размножается бит знака (если операнд знаковый). Во втором случае для положительных чисел слева добавляются 0-биты, а для отрицательных — 1-биты, то есть, знак сохраняется.

short q = v << 5;  // 0xffc0 = -64
ushort p = w << 5// 0xffc0 = 65472
short r = q >> 5;  // 0xfffe = -2
ushort s = p >> 5// 0x07fe = 2046

На примере выше первоначальный сдвиг влево "уничтожил" старшие биты переменной p, а последующий сдвиг вправо на то же количество битов заполнило их нулями, в результате чего значение уменьшилось с 0xffc0 до 0x07fe.

Размер сдвига (количество битов) должен быть меньше размера типа операнда (с учетом возможного расширения). В противном случае все исходные биты будут потеряны.

Сдвиг на 0 битов оставляет число неизменным.

Не следует путать побитовые операции '&' и '|' с логическими операциями '&&' и '||' (рассмотрены в предыдущем разделе).