Лекция 7. Битовые поля и побитовые операции

Системы счисления

Теория

Системы счисления

В настоящее время применяются позиционные системы счисления, изобретённые в Древней Индии.

Основные используемые системы счисления:

  1. Двоичная (алфавит \(A_2={0,1}\) ).
  2. Десятичная (алфавит \(A_{10}={0,1,2,3,4,5,6,7,8,9}\) ).
  3. Шестнадцатеричная (алфавит \(A_{16}={0,1,2,3,4,5,6,7,8,9,a,b,c,d,e,f}\) ).

Принцип формирования числа:

\(n = a_{m-1}(o)^{m-1}+...+a_1(o)+a_0\)

\(o\) - основание системы счисления (2,10,16).

\(a\) - значение разряда.

\(m\) - номер разряда.

Пример:

\(234 = 2*10^2+3*10^1+4*10^0\)

Перевод из двоичной в десятичную

Пример

Имеем двоичное число \(n_{2}=00101001\) .

\(o=2, m=8\)

Ему соответствует десятичное \(n_{10}=0*(2)^7+0*(2)^6+1*(2)^5+0*(2)^4+1*(2)^3+0*(2)^2+0*(2)^1+1*(2)^0=32+8+1=41\)

Числа, содержащие по одной единице легко запоминаются:

\(00000001\) \(1\)
\(00000010\) \(2\)
\(00000100\) \(4\)
\(00001000\) \(8\)
\(00010000\) \(16\)
\(00100000\) \(32\)
\(01000000\) \(64\)
\(10000000\) \(128\)

Несложно выполнить перевод, если числа содержат несколько идущих подряд единиц:

\(00000011\) \(4-1=3\)
\(00000111\) \(8-1=7\)
\(00001111\) \(16-1=15\)
\(00011111\) \(32-1=31\)
\(00111111\) \(64-1=63\)
\(01111111\) \(128-1=127\)
\(11111111\) \(256-1=255\)

Перевод из десятичной в двоичную

Пусть дано \(n_{10}=57\)

  1. Разделить 57 на 2: 28 - 1 (с остатком)
  2. Разделить 28 на 2: 14 - 0 (без остатка)
  3. Разделить 14 на 2: 7 - 0 (без остатка)
  4. Разделить 7 на 2: 3 - 1 (с остатком)
  5. Разделить 3 на 2: 1 - 1 (с остатком)
  6. Разделить 1 на 2: 0 - 1 (с остатком)

Пишем значения разрядов в обратном порядке.

В результате получаем \(n_2=111001\) .

Перевод из шестнадцатиричной в десятичную

Пример

\(2a = 2*16^1+a = 32+10 = 42\)

Пример

\(ff = f*16^1 +f = 15*16+15 = 255\)

Перевод из шестнадцатиричной в двоичную

Удобно переводить шестнадцатиричное число в двоичное по тетрадам:

Пример

Рассмотрим число \(8a\)

\(8\) \(a\)
\(1000\) \(1010\)

Ответ: \(10001010\)

Арифметика

Сложение

_images/add.png

Вычитание

_images/sub.png

Знак числа

_images/nums.png

Порядок байтов

Little Endian vs. Big Endian

_images/endian.png

Используется два порядка расположения байтов в многобайтной величине: от старшего к младшему big endian и от младшего к старшему little endian.

_images/5.png

BE используют: IBM 360/370/390, Motorola 68000, SPARC

LE используют: Intel x86

Достоинства LE

Существенным достоинством little-endian по сравнению с big-endian порядком записи считается возможность ‘’неявной типизации’’ целых чисел при чтении меньшего объёма байт.

Так, если в ячейке памяти содержится число 0x00000022, то прочитав его как int16 (два байта) мы получим число 0x0022, прочитав один байт — число 0x22. Однако, это же может считаться и недостатком, потому что провоцирует ошибки потери данных.

Проверка

Следующие функции позволяют проверить, какой порядок байт принят в вашей системе:

int endian1() {
  int one = 1;
  char *ptr;
  ptr = (char *)&one;
  return (*ptr);
}
int endian2() {
  union {
    int one;
    char ch;
  } endn;
  endn.one = 1;
  return endn.ch;
}

Использование полей битов

Очень полезное с практической точки зрения представление памяти в 1 байт, которое можно использовать как unsigned char или как набор значений отдельных битов.

union CODE
{
   unsigned char ch;
   struct BYTE
   {
      unsigned b1:1;
      unsigned b2:1;
      unsigned b3:1;
      unsigned b4:1;
      unsigned b5:1;
      unsigned b6:1;
      unsigned b7:1;
      unsigned b8:1;
   } byte;
};

Функция, переводящая байт из 10-тичной системы в 2-ичную с использованием поля битов:

void bin(unsigned char c)
{
   union CODE code;
   code.ch=c;
   printf("Bit numbers: 8 7 6 5 4 3 2 1 \n");
   printf("Bit values:  %d %d %d %d %d %d %d %d    ",
         code.byte.b8,
         code.byte.b7,
         code.byte.b6,
         code.byte.b5,
         code.byte.b4,
         code.byte.b3,
         code.byte.b2,
         code.byte.b1);
    printf("Number: %d Symbol: %c\n",c,c);
}

Побитовые операции

Обзор

Побитовые операции

Побитовые операции применяются к значениям отдельных разрядов числа и их не следует путать с обычными логическими операциями (&&, ||,!), которые действуют на значение в целом.

  • & - умножение (конъюнкция)
  • \(|\) - сложение (дизъюнкция)
  • ~ - отрицание
  • \(>>\) - побитовый сдвиг вправо
  • \(<<\) - побитовый сдвиг влево
  • \(^ \) - исключающее “или” (xor)

Вместе с этими операциями может использоваться присваивание, например a>>=2

Отрицание

Поразрядное отрицание alert{~{ меняет значение битов на противоположное

unsigned char a=4;  (00000100)
unsigned char b=~a; (11111011)

Следующая функция возвращает максимальное значение unsigned int в вашей системе:

typedef unsigned int ui;

ui getMaxUIValue()
{
   return ~(ui)0;
}

Поразрядное ‘’И’‘

Операция & ‘’И’’ является бинарной и выставляет результирующий бит в 1, если оба бита операндов равны 1.

_images/lcd1.png

Поразрядное И

Преобразование числа в бинарную форму:

void binary(ui n)
{
   for(int i = 256; i > 0; i /=2) {
      if(n & i)
         putchar('1');
      else
         putchar('0');
   }
   putchar('\n');
}

Поразрядное ‘’ИЛИ’‘

Операция | ‘’ИЛИ’’ является бинарной и выставляет результирующий бит в 1, если хотя бы один бит у операндов равен 1.

_images/lcd2.png

Поразрядное ИСКЛЮЧАЮЩЕЕ ИЛИ

Операция ** \(\widehat{**\) } ‘’ИСКЛЮЧАЮЩЕЕ ИЛИ’’ является бинарной и выставляет результирующий бит в 1, если первый (или второй) бит операнда равен 1, но не оба одновременно.

_images/lcd3.png

XOR

В следующем примере XOR используется для обмена значений двух целочисленных переменных:

void swap(int *a,int *b)
{
   *a=*a^*b;
   *b=*a^*b;
   *a=*a^*b;
}

Полезные мелочи

Проверка, выставлен ли данный бит:

int getBit(int n, int index)
{
   return ( (n & (1 << index) ) > 0);
}

Подсчёт количества выставленных битов:

int bitCountA(int n)
{
  int count = 0;
  while(n) {
    if(n & 1) count++;
    n = n >> 1;
  }
  return count;
}

Очистка заданного бита:

unsigned char b &= ~(1 << n);

Переключение бита:

unsigned char c ^= (1 << n);

Выделение первого и второго байтов:

unsigned char right = val & 0xff;
unsigned char left = (val>>8) & 0xff;

Выделение знакового разряда:

char sign = val & 0x8000;

Маски

Маска - это битовый шаблон с установленными значениями разрядов.

Если найти результат операции ‘’И’’ между величиной и маской, то в результате получим только те значения разрядов, у которых маска содержала единицы:

_images/lcd4.png

В данном примере из 24-битной величины выделяется первый байт.

При необходимости можно включить специфические биты, не изменяя оставшиеся. Это произойдет, если наложить маску с помощью операции |.

flags |= MASK;

Аналогично можно отключить избранные разряды:

flags &= ~MASK;

Приведем пример с переключением значения битов (установкой в ноль единиц):

flags ^= MASK;

Для сравнения значения разряда необходимо сначала маскировать другие биты

if((flags & MASK)==MASK) {
...
}

Вопросы для самоконтроля

  • Опишите принцип формирования числа в позиционной системе счисления.
  • Перечислите наиболее распространенные системы счисления.
  • Как перевести число из двоичной системы в десятичную?
  • Как перевести число из десятичной системы в двоичную?
  • Как перевести шестнадцатиричное число в десятичное?
  • Как перевести шестнадцатиричное число в двоичное?
  • Как узнать, какой порядок байт в вашей системе?
  • Как можно перевести число в двоичную систему без деления?
  • Перечислите побитовые операции.
  • Чему равен результат поразрядного отрицания?
  • Как узнать максимальное значение целого беззнакового типа в системе?
  • Чему равен результат поразрядного И?
  • Чему равен результат поразрядного ИЛИ?
  • Чему равен результат поразрядного исключающего ИЛИ?
  • Как реализовать обмен двух целых без вспомогательной ячейки?
  • Что такое маска? Для чего могут использоваться маски?
  • Как включить и выключить специфические разряды?
  • Как можно переключить значение бита? Сравнить значение бита?