Использование битовых флагов и масок

Использование битовых флагов и масок

В этой статье я постараюсь рассказать, как можно использовать биты и операции над ними. Я выбрал 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;

 

Эта статья немного затянулась, поэтому на сегодня всё

При копировании материалов ссылка на https://terraideas.ru/ обязательна

Комментарии к статье: Использование битовых флагов и масок

Нет ни одного комментария. Будьте первым!