
В этой статье я постараюсь рассказать, как можно использовать биты и операции над ними. Я выбрал php для демонстрации, так как тем кто знает другие языки, понять примеры не должно составить труда.
Предыдущая статья: Биты и битовые операции.
Для чего можно использовать биты?
Так как в байте 8 бит, то 1 байт, в двоичной системе исчисления представлен как число от 0000 0000 до 1111 1111.
А использовать их можно как хранилище состояний. То есть что то вроде переключателей.
Можно сказать, что это 8 переменных типа boolean в 1 байте.
Принято считать 1 - истиной, 0 - ложью.
Многие компиляторы, сами приводят тип boolean к 1 биту, так что это не всегда позволит экономить место в памяти, но это и не единственная цель. Ещё это добавляет удобство, сгруппированность данных, проверки сразу нескольких состояний по так называемым маскам.
Битовые маски
Маской называют какой то набор бит, который используют для того что бы выбрать определённые биты из набора (переменной).
Например: 1 (0000 0001), 2 (0000 0010), 4 (0000 0100), 32 (0010 0000)...
Можно конечно напрямую использовать числа, и составлять условия, но гораздо удобней давать каждому биту имя.
Как установить бит в числе не затрагивая другие?
Очень просто, вспомним операцию OR из предыдущей статьи.
Она вернёт установленный бит, если хотя бы в одном из операндов бит установлен.
define('FLAG_ONE', 1); // 0000 0001 0x01 define('FLAG_TWO', 2); // 0000 0010 0x02 define('FLAG_THREE', 4); // 0000 0100 0x04 $MyVar = 0; // переменная для хранения флагов $MyVar |= FLAG_TWO; // то же самое что $MyVar = $MyVar | FLAG_TWO /* 0000 0000 $MyVar переменная | 0000 0010 FLAG_TWO маска = 0000 0010 $MyVar результат */
В результате в $MyVar мы получим установленный бит, из FLAG_TWO
Давайте установим ещё один бит
$MyVar |= FLAG_THREE; /* 0000 0010 $MyVar у нас уже не с нулём | 0000 0100 FLAG_THREE = 0000 0110 результат */
И так, устанавливая 1 флаг, мы не затрагиваем другие.
Как проверить установлен ли бит?
Теперь вспомним операцию &. Операция вернёт установленный бит только если он установлен в обоих операндах.
/* 0000 0110 $MyVar & 0000 0010 FLAG_TWO = 0000 0010 результат */
/* 0000 0110 $MyVar & 0000 0001 FLAG_ONE = 0000 0000 результат */
То есть, сколько бы не было установленных бит, в нашей переменной, у нас вернётся либо 1 установленный бит либо не одного.
Что нам это даёт?
Если бит не установлен, то мы получим 0. Во многих языках 0 в условии расцениваеться как false.
Установленный же бит наоборот, вернёт какое то, не нулевое число. А во многих языках это true.
Значит можно проверять просто
if ($MyVar & FLAG_TWO) { // то же что if (0000 0110 & 0000 0010) // или if (2) echo 'Установлен'; }
Как проверить сразу несколько бит
define('FLAG_ONE', 1); // 0000 0001 0x01 define('FLAG_TWO', 2); // 0000 0010 0x02 define('FLAG_THREE', 4); // 0000 0100 0x04 define('FLAGS_ONE_OR_TWO', FLAG_ONE | FLAG_THREE) // 0000 0101 0x05 $MyVar = 0; $MyVar |= FLAG_ONE; // 0000 0001 $MyVar |= FLAG_TWO; // 0000 0011
Здесь нужно понимать очень важный момент. Проверка с помощью & может сработать не так как Вы ожидаете.
/* 0000 0011 $MyVar & 0000 0101 FLAGS_ONE_OR_THREE = 0000 0001 результат */ if ($MyVar & FLAGS_ONE_OR_THREE) { echo 'Хотя бы один из битов маски установлен'; }
Условие выполнится если хотя бы один из битов маски установлен.
А что делать, если нужно проверить что бы все биты были установлены?
Нужно просто сравнить результат с маской.
/* 0000 0011 $MyVar & 0000 0101 FLAGS_ONE_OR_THREE = 0000 0001 == 0000 0101 Не равны */
if (($MyVar & FLAGS_ONE_OR_THREE) == FLAGS_ONE_OR_THREE) { echo 'Все биты маски установлены'; }
$MyVar |= FLAG_THREE; // 0000 0111 /* 0000 0111 $MyVar & 0000 0101 FLAGS_ONE_OR_THREE = 0000 0101 == 0000 0101 равны */
Условие выполнится если все биты маски установлены.
Возможно, Вам понадобится проверить что только биты маски установлены, и больше не какие.
Тогда наоборот, сравните результат с самой переменной. Но особого смысла в этом нет, так как можно просто сравнить переменную и маску на равенство между собой.
Многие могли заметить, что после операции OR мы просто получаем сложившиеся числа. И часто это совпадает:
0000 0001 1 | 0000 0100 + 4 = 0000 0101 = 5
Но это не так.
Рассмотрим пример ниже
0000 0001 1 | 0000 0001 + 1 = 0000 0001 = 1 - не верно
0000 0011 3 | 0000 0101 + 5 = 0000 0111 = 7 - не верно
Так что не нужно путать OR и +.
Как сбросить бит?
Здесь немного сложнее.
Нам нужно получить все биты которые установлены в числе, кроме указанных.
Операция & вернёт биты установленные в обоих числах.
То есть если мы сделаем так
0100 0011 & 1111 1111 = 0100 0011
Мы получим то же самое число, так как в маске у нас все биты установлены, а значит если и в переменной бит установлен, то мы получим его установленным в результате.
Что бы отбросить в результате бит TWO, нам нужно просто сделать маску, в которой этот бит сброшен. То есть вот такую 1111 1101
0100 0011 & 1111 1101 = 0100 0001
И мы получим, что бит TWO в результате у нас сброшен.
Смотрим на разницу, между флагом, который нужно сбросить и маской, по которой сбрасываем
0000 0010 FLAG_TWO 1111 1101 маска для сброса
И видим что маска, это просто инверсия нашего бита, а команда инверсии у нас ~.
0100 0011 & ~0000 0010
0100 0011 & 1111 1101
То есть
$MyVar &= ~FLAG_TWO;
Эта статья немного затянулась, поэтому на сегодня всё
Комментарии к статье: Использование битовых флагов и масок
Классная статья. Была полезной. Спасибо