, : files, : Top


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

Скалярными типами данных называются все типы, принимающие целочисленные значения: char, short int, int, long int, long long, а также их signed и unsigned модификации.

Для хранения каждого из этих типов в памяти отводится определенное количество байт. Для того, чтобы узнать размер памяти, отводимый для хранения той или иной переменной можно использовать оператор sizeof: например, sizeof(int) возвращает количество байт, необходимых для хранения переменной типа int, а sizeof(A), где A – идентификатор переменной, возвращает количество байт, необходимой для хранения переменной A.

Каждую переменную скалярного типа будем представлять в виде последовательности бит, нумеруя их от 0, биты будем записывать справа налево (то есть бит с номером 0 будет записан самым правым, а самый старший бит – самым левым).

Например, если переменная a объявлена, как unsigned char, то ее можно записать в виде последовательности из 8 бит:

     unsigned char a;
     a=0    ; // 00000000
     a=1    ; // 00000001
     a=2    ; // 00000010
     a=10   ; // 00001010
     a=255  ; // 11111111

Например, если a=10, то в битовой записи a биты с номерами 1 и 3 равны 1, а остальные биты равны 0.

Для двух переменных одинакового скалярного типа определены битовые операции:

& битовое И (AND)

| битовое ИЛИ (OR)

^ битовое ИСКЛЮЧАЮЩЕЕ ИЛИ (XOR)

~ битовое ОТРИЦАНИЕ (NOT) - унарный оператор

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

     unsigned char a, b, c, d, e, f;
     a = 5     ; // 00000101
     b = 6     ; // 00000110
     
     c = a & b ; // 00000100 == 4
     d = a | b ; // 00000111 == 7
     e = a ^ b ; // 00000011 == 3
     f =  ~ a  ; // 11111010 == 250

Битовое отрицание числа (величина f в последнем примере) – это число, полученное из исходного заменой всех нулей на единицы и наоборот.

Будьте аккуратны, не путайте логические и битовые операции. Например, 2 && 1 == 1, поскольку применение логического "И" к двум значениям 2 и 1, то есть к двум "истинам", это истина, но 2 & 1 == 0!

Есть еще две операции, работающие с битами: это битовые сдвиги. Их две: сдвиг влево и вправо. Оператор a>>n возвращает число, которое получается из a сдвигом всех бит на n позиций вправо, при этом самые правые n бит отбрасываются. Например:

     unsigned char a, b, c, d, e;
     a = 43      ; // 00101011
     b = a >> 1  ; // 00010101 == 21
     c = a >> 2  ; // 00001010 == 10
     d = a >> 3  ; // 00000101 == 5
     e = a >> 5  ; // 00000001 == 1

Понятно, что для положительных чисел битовый сдвиг числа вправо на n равносилен целочисленному делению на 2n.

Аналогично, битовый сдвиг влево на n бит равносилен (для положительных чисел) умножению на 2n и осуществляется при помощи оператора <<:

     unsigned char a;
     a = 5       ; // 00000101
     b = a << 1  ; // 00001010 == 10
     c = a << 2  ; // 00010100 == 20
     d = 2 << 3  ; // 00101000 == 40

Упражнения

Во всех упражнениях нельзя использовать арифметические операторы сложения, умножения, вычитания, деления. Вместо них используем побитовые операторы &, |, ~, ^, <<, >>.

Входное число A имеет тип unsigned int (за исключением последней задачи). Номера битов всегда задаются корректно, то есть принимают значения от 0 до 31.

  1. (A) Дано число n<32. Запишите число 2n, то есть число, у которого n-й бит равен 1, а остальные – нули.
  2. (B) Даны два неравных числа: n и m, не превосходящие 31. Вычислите 2n+2m.
  3. (C) Дано целое число A и натуральное число i. Обнулите у числа A его последние i бит и выведите результат.
  4. (D) Дано целое число A и натуральное число i. Выведите число, которое получается из числа A установкой значения i-го бита равному 1.
  5. (E) Дано целое число A и натуральное число i. Выведите число, которое получается из числа A инвертированием i-го бита.
  6. (F) Дано целое число A и натуральное число i. Выведите число, которое получается из числа A установкой значения i-го бита равному 0.
  7. (G) Дано целое число A и натуральное число n. Выведите число, которое состоит только из n последних бит числа A (то есть обнулите все биты числа A, кроме последних n).
  8. (H) Дано целое число A и натуральное число i. Выведите значение i-го бита числа A, то есть 0 или 1.
  9. (I) Дано число типа unsigned char, то есть от 0 до 255. Выведите его в битовой форме: 8 бит, старшие биты слева, младшие – справа.

Самостоятельная работа

Напишите функцию printbyte(unsigned char x), печатающую данный байт побитово (как в последней задаче). Теперь реализуйте шаблон template <typename T> print (T A), который печатает переменную A данного типа T побитно. В шаблоне print объявим переменную p типа unsigned char * и сделаем так, чтобы она указывала на переменную A, для чего потребуется сделать явное преобразование типов:

     unsigned char * p = (unsigned char *) &A;

Теперь, p[0] будет первым байтом переменной A, p[1] – следующим байтом и т.д. Значение каждого байта необходимо напечатать при помощи функции printbyte. Ну а общее количество байт в переменной A равно sizeof(A).

Теперь напишите функцию main, которая будет для некоторого типа считывать значение переменной данного типа и выводить его на экран побайтно при помощи шаблона print. Например, print( (short) 1) должен вывести 00000001 00000000, а print( (int) 1) должен вывести 00000001 00000000 00000000 00000000.

Теперь проведите эксперименты с различными типами данных с целью выяснения, как они хранятся в памяти. Начните с простых: unsigned char, unsigned short, unsigned int. Как записываются числа 0, 1, 2, 3? Какое наибольшее число можно записать в этих типах?

Разобрались с беззнаковыми числами? Переходим ко знаковым: как храняться отрицательные значения в типах signed char, signed short, signed int? Какие значения может принимать переменная этих типов? Попробуйте разобраться, как работают битовые сдвиги для отрицательных чисел.

Если удалось разобраться со знаковыми целыми – попробуйте перейти к дейтсвительным числам типов float и double. Это уже довольно трудная задача.

Результаты своего исследования оформите в виде реферата.