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

Два взгляда на компьютерные «кирпичики»

Регистры построены из триггеров – элементарных ячеек памяти, способных хранить один бит информации. В регистре может быть 8, 16, 32 или 64 триггера, что соответствует 1, 2, 4 или 8 байтам. Так видят устройство компьютера инженеры-электроники.

А программисты? Они видят то же самое, только называют иначе (рис. 108). То, что электроники именуют триггерами, программисты называют битами, а регистры нам видны как байты, слова и т.д. Так, в Паскале 8-битовый регистр соответствует типу Byte, 16-битовый – типу Word, а 32-битовый – типу Longint.

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

Рис.108 – Устройство процессора глазами электроника и программиста

Логические операции в регистрах

Взгляните на эту, на первый взгляд бессмысленную программу.

var A, B, C : integer;

begin

      A:= 5;       B:=16;       C:= A or B;

      Writeln( C );

end.

Здесь в переменную C заносится логическая сумма двух других числовых переменных. Но ведь логические операции применяют к булевым данным, причем здесь числа? Так вспомните о регистрах, где эти числа хранятся. Ведь это массивы битов! Содержимое битов можно трактовать и как числа 0 и 1, и как логические значения FALSE и TRUE. Именно так поступает Паскаль, выполняя логические действия с числами. В данном примере логически складываются шестнадцать независимых булевых пар с получением 16 битов результата. Похоже выполняются и другие логические операции с числами.

Известно, что переменная типа BOOLEAN занимает байт целиком, но использует лишь один из восьми битов, – расточительно, не так ли? Тогда как в байте можно хранить 8 булевых значений, в целом числе – 16, а в длинном целом – 32. Но экономия – не самое главное в жизни. Логические операции с числами дают интересные возможности для шифрования данных, их используют при обработке изображений и в иных случаях.

«Ладно, – скажете, – теперь бы увидеть это наяву». Легко! Наша следующая программа исследует булевы операции с числами. Самая серьезная её часть – функция преобразования байта в строку символов, то есть в двоичное представление этого числа. В программе «P_47_1» нечто похожее выполняла функция ConvertFromNumber. Сейчас мы облегчим эту функцию, избавившись от одного параметра – основания системы счисления. К тому же теперь нам надо показать все восемь двоичных разрядов числа, включая незначащие нули. В результате этих изменений появилась на свет функция ConvertTo2, которую мы видим в программе «P_48_1».

{ P_48_1 – исследование логических операций с числами }

function ConvertTo2(aNumber : integer): string;

var n, i : integer; c : char; S : string;

begin

S:=''; { Накопитель цифр }

for i:=1 to 8 do begin

n:= aNumber mod 2;       { остаток от деления }

c:= Char(n + Ord('0')); { преобразуем в цифру }

S:= c + S;       { вставляем цифру слева }

aNumber:= aNumber div 2; { частное }

end;

ConvertTo2:= S;

end;

var A, B, C : byte; { Операнды и результат }

begin {=== Главная программа ===}

repeat

      Write('A= '); Readln(A);

      Write('B= '); Readln(B);

      C:= A or B;       { логическое сложение (объединение) }

      Writeln;

      Writeln('C= A OR B');

      Writeln('A= ',ConvertTo2(A), A:5);

      Writeln('B= ',ConvertTo2(B), B:5);

      Writeln('C= ',ConvertTo2(C), C:5);

      C:= A and B; { логическое умножение (пересечение) }

      Writeln;

      Writeln('C= A AND B');

      Writeln('A= ',ConvertTo2(A), A:5);

      Writeln('B= ',ConvertTo2(B), B:5);

      Writeln('C= ',ConvertTo2(C), C:5);

      C:= not A;       { логическое отрицание (инверсия) }

      Writeln;

      Writeln('C= NOT A');

      Writeln('A= ',ConvertTo2(A), A:5);

      Writeln('C= ',ConvertTo2(C), C:5);

until A=0;

end.

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

      C= A OR B

A= 00001101 13

B= 00001011 11

C= 00001111 15

      C= A AND B

A= 00001101 13

B= 00001011 11

C= 00001001 9

      C= A XOR B

A= 00001101 13

B= 00001011 11

C= 00000110 6

      C= NOT A

A= 00001101 13

C= 11110010 242

По результатам этих опытов выведены правила для логических операций (табл. 12). Логическое отрицание «НЕ» отличается от прочих тем, что применяется к одному операнду.

Табл. 12 – Правила выполнения логических операций с битами

Логическая операция Пример Правило
«ИЛИ» (сложение) 1010 OR11001110 Результат единица, если ХОТЯ БЫ ОДИН из операндов равен единице.
«И» (умножение) 1010 AND1100 1 00 0 Результат единица, если ОБА операнда равны единице.
«Исключающее ИЛИ» (сравнение) 1010 XOR1100 0 110 Результат единица, если операнды ОТЛИЧАЮТСЯ.
«НЕ» (отрицание) 1010 NOT0101 Результат единица, если операнд РАВЕН НУЛЮ.

Заменив в этих правилах единицу на TRUE, а ноль на FALSE, вы получите правила для булевых данных.

Сдвиги влево и вправо

Сдвиг – одна из тех операций обработки регистров, которые выполняют все процессоры. В Паскале тоже предусмотрены две такие операции с числами: сдвиг влево (SHL) и сдвиг вправо (SHR).

Операция левого сдвига (рис. 109) перемещает все биты слова на заданное число позиций влево, при этом младшие биты заполняются нулями, а старшие теряются, например:

N:= 3;       { 3 = 00000011 }

Writeln (N shl 1);       { 6 = 00000110 }

Writeln (N shl 2);       { 12 = 00001100 }

Рис.109 – Сдвиг байта на один разряд влево

Операция правого сдвига (рис. 110) перемещает все биты слова на заданное число позиций вправо. При этом старшие биты заполняются нулями, а младшие теряются.

N:= 3;       { 3 = 00000011 }

Writeln (N shr 1);       { 1 = 00000001 }

Writeln (N shr 2);       { 0 = 00000000 }

Рис.110 – Сдвиг байта на один разряд вправо

Совместив сдвиг с логическими операциями, можно исследовать отдельные биты слова. Перед вами булева функция TestBit, принимающая два параметра: ARG – число, в котором проверяется состояние некоторого бита, и BIT – номер этого бита. Функция возвращает TRUE, если проверяемый бит содержит единицу, и FALSE в противном случае.

function TestBit (arg: longint; bit : byte): Boolean;

begin

      TestBit := (arg and (1 shl bit)) <> 0

end;

Итоги

• Процессоры построены из триггеров. Триггер – это элемент с двумя устойчивыми состояниями, которые можно трактовать либо как булевы значения TRUE и FALSE, либо как числа 0 и 1.

• Для хранения чисел и других данных, триггеры соединены в регистры. Обычно регистр состоит из 8, 16, 32 или 64 битов.

• В Паскале есть средства для работы с регистрами – это логические операции и сдвиги. Они трактуют числа как массивы битов.

А слабо?

А) Напишите программу для исследования операций сдвига (подобную программе «P_48_1»).

Б) Наряду с рассмотренными здесь обычными сдвигами, в процессорах заложены операции циклического (кругового) сдвига. При таком сдвиге (рис. 111) выдвигаемый бит не теряется, а попадает соответственно в младший бит (при сдвиге влево) или в старший бит (при сдвиге вправо).

Рис.111 – Циклический сдвиг влево

В Паскале нет операций циклического сдвига. Напишите функции для циклического сдвига слова влево и вправо. Подсказка: перед сдвигом каждого бита проверяйте состояние теряемого бита, а затем восстанавливайте его в младшем или старшем разряде.