12 октября 2021
Представление чисел в компьютере
Двоичная система счисления особенно важна в программировании. Любая информация в компьютере хранится в виде последовательности битов. Каждый бит может быть равен нулю либо единице. Процессор выполняет различные операции с двоичными числами.
Дополнительный код
Представление беззнаковых целых чисел в памяти не вызывает вопросов. Для представления знаковых целых чисел чаще всего используется дополнительный код (two’s complement).
| Десятичное представление | Прямой | Обратный | Дополнительный |
|---|---|---|---|
| 127 | 0111 1111 | 0111 1111 | 0111 1111 |
| 1 | 0000 0001 | 0000 0001 | 0000 0001 |
| 0 | 0000 0000 | 0000 0000 | 0000 0000 |
| -0 | 1000 0000 | 1111 1111 | 0000 0000 |
| -1 | 1000 0001 | 1111 1110 | 1111 1111 |
| -2 | 1000 0010 | 1111 1101 | 1111 1110 |
| -3 | 1000 0011 | 1111 1100 | 1111 1101 |
| -4 | 1000 0100 | 1111 1011 | 1111 1100 |
| -5 | 1000 0101 | 1111 1010 | 1111 1011 |
| -6 | 1000 0110 | 1111 1001 | 1111 1010 |
| -7 | 1000 0111 | 1111 1000 | 1111 1001 |
| -8 | 1000 1000 | 1111 0111 | 1111 1000 |
| -9 | 1000 1001 | 1111 0110 | 1111 0111 |
| -10 | 1000 1010 | 1111 0101 | 1111 0110 |
| -11 | 1000 1011 | 1111 0100 | 1111 0101 |
| -127 | 1111 1111 | 1000 0000 | 1000 0001 |
| -128 | — | — | 1000 0000 |
Дополнительный код для отрицательного числа можно получить инвертированием его двоичного модуля и прибавлением к инверсии единицы, либо вычитанием числа из нуля.
Дополнительный код двоичного числа определяется как величина, полученная вычитанием числа из наибольшей степени двойки.
Дополнительный код позволяет сделать операции сложения и вычитания одинаковыми для знаковых и беззнаковых чисел.
Стандарт IEEE-754
Числа одинарной точности с плавающей точкой описывают, используя стандарт IEEE-754.
Следующая схема (источник) показывает способ представления переменной float в языке Си:

float a = -1.045e-12; // - 1.045 x 10^{-12}
В 32 битовом числе один бит хранит знак, 8 битов хранят (смещенный на 127) показатель экспоненты, оставшиеся биты хранят мантиссу - значение числа без учета порядка.
На этом сайте есть интерактивная иллюстрация для представления чисел с плавающей точкой.
Битовые операции в Си
Побитовое И: &
Бинарый оператор:
| 0 | 1 | |
|---|---|---|
| 0 | 0 | 0 |
| 1 | 0 | 1 |
int a = 5; // 101
int b = 3; // 011
int c = a & b; // 001
Побитовое ИЛИ: |
Бинарый оператор:
| 0 | 1 | |
|---|---|---|
| 0 | 0 | 1 |
| 1 | 1 | 1 |
int a = 5; // 101
int b = 3; // 011
int c = a | b; // 111
Побитовое исключающее ИЛИ (XOR): ^
Бинарый оператор:
| 0 | 1 | |
|---|---|---|
| 0 | 0 | 1 |
| 1 | 1 | 0 |
int a = 5; // 101
int b = 3; // 011
int c = a ^ b; // 110
Побитовое НЕ: ~
Унарный оператор:
| 0 | 1 |
|---|---|
| 1 | 0 |
int a = 5; // 101
int c = ~b; // 010
Битовые сдвиги
Унарные операторы << и >>:
int a = 20; // 0010100
int b = a << 2; // 1010000
int b = a >> 2; // 0000101
Сдвиг влево на 1 эквивалентен умножению на 2, сдвиг вправо - делению на 2.
Примеры
Битовые операторы позволяют очень гибко манипулировать значениями переменных.
Пример 1. Манипуляции с одним битом:
- Установка
n-го бита в значение1:x | (1 << n); - Установка
n-го бита в значение0:x & ~(1 << n); - Переключение значения
n-го бита:x ^ (1 << n);
Пример 2. Красивый метод подсчета количества единиц в двоичной записи числа:
size_t bitcount(unsigned int n) {
size_t count = 0;
while (n) {
++count;
n = n & (n - 1);
}
return count;
}
Проверьте, что эта программа верна. Подумайте почему она работает.
Пример 3. Другой интересный пример - обмен значений двух переменных с использованием операции XOR:
void xor_swap(int* a, int* b) {
*a = *a ^ *b;
*b = *b ^ *a;
*a = *a ^ *b;
}
Схема иллюстрирует работу алгоритма:
Свойста битовых операций
Для каждого из рассмотренных битовых операторов выполняются следующие соотношения (на примере оператора И):
- Коммутативность
a & b == b & a; - Ассоциативность
(a & b) & c == a & (b & c); - Существование единицы. Для любого
aтипаintследующие выражения истинны:a & ~(1 << 31) == a; // предполагая, что int имеет размер 4 байта a | 0 == a; a ^ 0 == a;
Выражение ~(1 << 31) позволяет получить максимальное знечение переменной типа int.
Заметим также, что:
a & a == a;
a | a == a;
a ^ a == 0;
a & ~a == 0;
a | ~a == ~0;
a ^ ~a == ~0;