Цитата мудреца

Голосование

Система Orphus. Если вы заметили ошибку на сайте, нажмите сюда.
Загружается, подождите...
Начало сайта Материалы сайта Программы Статьи для начинающих программистов
Версия для слабовидящих
Версия для печати

Битовые операции

С января 2004 года журнал "Магия ПК" начал публиковать мои статьи для начинающих программистов. В них я рассказываю как прописные истины (применение различных операторов языка Паскаль), так и делюсь "секретами" (различными подходами к решению тех или иных задач).

Идея начать цикл статей посвящённых азам и секретам программирования родилась спонтанно. Один мой знакомый начал изучать программирование. Выучив несколько языков, которые требовали в институте, он так и не смог понять принцип, как же писать программы, как подойти к решению тех или иных задач. Именно для помощи в таких ситуациях, я и решил начать писать. Для этого пригодился и опыт преподавания языков программирования на компьютерных курсах (1990 г.).

Эти статьи предназначены для начинающих программистов, точнее для тех, кто решил попробовать себя в программировании. Самое первое препятствие на этом пути — страх перед трудностями, перед кажущейся сложностью процесса составления программ. Для того, чтобы помочь упростить сложные задачи — эти статьи.
  Нет сообщений • Страница 1 из 1

Битовые операции

Биты — эти нолики и единички - основа всего, что связано с компьютером, это начало программирования, начало хранения и использования информации. Это только потом люди придумали, как при помощи битов (двоичных чисел) записывать символы, кодировать команды для процессора. Изначально же "да" и "нет", Истина и Ложь и операции с ними были сутью науки логики. Мы не можем обойтись без логики при решении задач на компьютере. Для этого мы используем логические переменные типа boolean, которые могут принимать значение True или False. Проверяя значение таких переменных, например, командой if, мы направляем работу программы по тому или иному пути. Но таких переменных может быть очень много, а занимает логическая переменная не меньше одного байта (8 бит), хотя используется в ней только один бит. Почему "не меньше", потому что в разных операционных системах, в разных версиях языка логическими переменными становятся целочисленные переменные, где нулевое значение воспринимается как False, а любое ненулевое — True. Иногда это удобно, но мы рассмотрим этот вопрос с другой стороны. Представьте себе базу данных, в которой несколько тысяч записей, и каждая запись содержит в себе несколько логических полей. Если мы будем пользоваться стандартными логическими полями-переменными, то размер базы данных будет неоправданно увеличен. В тех случаях, когда эта проблема становится критичной, стоит оптимизировать логические переменные, отведя по одному биту под каждую. Таким образом, один байт сможет сохранить в себе 8 логических переменных, а, например, тип Cardinal (беззнаковое целое число) разместит в себе 32 логические переменные, что часто оказывается достаточным. Если таких переменных ещё больше, то можно разбить их на группы и хранить в нескольких целочисленных переменных.


Как же мы с этими переменными будем работать? Мы создадим одну или несколько переменных, в которых будут храниться битовые логические значения:


Код: Выделить всё
var
  BooleanBox: Cardinal;

А затем, или, если быть точнее то перед этим, в самом начале, в секции описания констант, мы должны описать маски для различных переменных:


Код: Выделить всё
const
  mskWorking    = 00000001H;  //Программа/процедура работает
  mskWasPressed = 00000002H;  //Клавиша была нажата
  mskWasRemoved = 00000004H;  //Объект был перемещён
  mskDemo       = 00000008H;  //Демо-версия
  mskLog        = 00000010H;  //Делать записи в лог-файле
  mskPrint      = 00000020H;  //Выдавать результаты на принтер
  mskDispley    = 00000040H;  //Выдавать результаты на монитор

Эти константы — пример использования различных логических переменных, которые мы можем использовать в программе для хранения настроек или сигнализации определённых этапов работы программы.


Что за числа стоят после знака "="? Это шестнадцатеричные числа, в которых установлен один бит, а все остальные сброшены:


Код: Выделить всё
00000001H = 00000000000000000000000000000001B
00000002H = 00000000000000000000000000000010B
00000004H = 00000000000000000000000000000100B
. . .
00000040H = 00000000000000000000000001000000B

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


Код: Выделить всё
procedure BIS(var Value: Cardinal; Mask: Cardinal);
begin
  Value := Value or Mask;
end;

procedure BIC(var Value: Cardinal; Mask: Cardinal);
begin
  Value := Value and not Mask;
end;

function BIT(Value: Cardinal; Mask: Cardinal): boolean;
begin
  result := (Value and Mask) = Mask;
end;

Названия этих процедур взяты из языка Ассемблер: BIS — Bit Set — установка битов; BIC — Bit Clear — очистка битов; BIT — Bit Test — проверка битов.


Как всё это работает?


Всем процедурам передаётся два параметра: Value — переменная, хранящая биты; и Mask — маска интересующего нас бита (или нескольких бит). Заметьте, что в первых двух процедурах параметр Value передаётся как переменная, а не как значение, потому что эта переменная изменится после отработки процедуры. Как я уже говорил, маска может содержать не один бит, а несколько, и все биты, присутствующие в ней будут установлены. Для того чтобы использовать сразу несколько масок в одной операции, мы должны применить операцию or:


Код: Выделить всё
ComplexMask := mskPrint or mskDispley or mskLog;

После этого переменная ComplexMask будет иметь значение: 00000070H или 00000000000000000000000001110000B. Стоит заметить, что одни и те же операторы: or, and, not, xor — используются как логические операторы, действующие с логическими переменными, так и бинарные (битовые), которые оперируют с целочисленными переменными и рассматривают их как набор битов.


Установка битов маски в переменной-хранилище также происходит оператором or (или). Все биты переменной, совпадающие с установленными битами маски, будут установлены, остальные же останутся без изменений. Использование процедуры BIS:


Код: Выделить всё
ComplexMask := mskPrint or mskDispley or mskLog;
BIS(BooleanBox, ComplexMask);

эквивалентно присвоению ряду логических переменных значений True:


Код: Выделить всё
bPrint := True;
bDispley := True;
bLog := True;

Следующая процедура — BIC — сбрасывает в переменной биты, установленные в маске. Для этого сначала маска инвертируется при помощи оператора not, т.е. все установленные биты сбрасываются, а сброшенные ранее — устанавливаются. Именно оператор not выполняется первым, потому что он унарный (требует только одного операнда, стоящего после него), в отличие от оператора and, который производит действие над двумя операндами. Таким образом, интересующие нас биты становятся равными 0. После этого начинает работу оператор and. Он оставляет "в живых" только те биты, которые присутствуют и в одном и в другом операнде. Как вы помните, интересующие нас биты в маске сброшены, следовательно, в результате работы оператора and, в этих местах непременно будут стоять нули, не зависимо от того, что в них было раньше. А вот во всех остальных местах биты будут установлены только там, где они стояли раньше:


Код: Выделить всё
Value    = 00000000000000000000000000100011B
not Mask = 11111111111111111111111110001111B
result   = 00000000000000000000000000000011B

Как и в прошлом случае, вызов функции BIC эквивалентен присвоению одной или нескольким логическим переменным значения False.


Последняя же функция — BIT — проверяет, установлены ли интересующие нас биты в переменной-хранилище. Как она это делает? Счала мы при помощи оператора and получаем те биты, которые установлены и в переменной и в маске, а потом сравниваем полученный результат с самой маской. Если эти два числа равны, то все биты, переданные через параметр Mask, установлены в переменной Value. Согласитесь, что если мы передаём сложную маску, состоящую из нескольких установленных битов, то при проверке между ними начинает работать условие "И", т.е., результат функции BIT будет истинным, только если все биты маски будут установлены в проверяемой переменной. Если же нам нужно между ними условие "ИЛИ", т.е. будет достаточно, хотя бы одного бита из набора маски, установленного в переменной, то единственную строку кода в функции BIT нужно переписать следующим образом:


Код: Выделить всё
result := (Value and Mask) > 0;

В этом случае, хотя бы один установленный бит в результате операции and сделает число отличным от нуля, что должно вернуть положительный результат функции. Но практика показывает, что такая проверка не пригодится.


Как же пользоваться этой функцией? Да точно так же, как бы мы пользовались логическими переменными. Представим ситуацию, что нам нужно выполнить печать на принтере лог файла (запустить некую процедуру PrintLog), если это не демо-версия, и если объект был перемещён (условия взяты из нашего примера выше). Мы пишем такое условие:


Код: Выделить всё
if not bDemo and bWasRemoved and bLog and bPrint
  then PrintLog;

То же самое получится при использовании процедуры BIT:


Код: Выделить всё
if not BIT(BooleanBox, mskDemo) and
  BIT(BooleanBox, (mskWasRemoved or mskLog or mskPrint))
  then PrintLog;

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


На заре изучения мной программирования один опытный программист сказал мне: "Не стоит заставлять компьютер делать лишние действия и нагружать его лишней информацией". Если вы будете использовать вместо обычных логических переменных битовые операторы, то в 95% случаев это вам не повредит, а в 5% случаев очень поможет.

Ответить

  Нет сообщений • Страница 1 из 1

Вернуться в Статьи для начинающих программистов



Кто сейчас на сайте

Зарегистрированные пользователи: нет зарегистрированных пользователей