Двоичный код: что это такое, как работает и где применяется

Двоичный код: что это такое, как работает и где применяется Полезное

Двоичный код — это способ записи информации с помощью всего двух символов, 0 и 1. Одна такая позиция называется битом, а сам процесс представления чисел, текста, картинок и звука цепочками из нулей и единиц — двоичным кодированием. Синоним — бинарный код (от латинского binarius, «двойной»). В статье разберем, почему компьютеры устроены именно так, что такое байт и разряды, как перевести число из десятичной системы в двоичную и обратно, как кодируются буквы (ASCII, UTF-8), как с двоичным кодом работают в Python и какие ошибки встречаются чаще всего.

Почему компьютеры используют два состояния

Причина в физике, а не в математике. Внутри процессора и памяти информация хранится уровнями напряжения, зарядом ячейки, намагниченностью участка диска. Различить два состояния («выше порога» / «ниже порога») просто и надежно: небольшая помеха не превратит 0 в 1. Если бы уровней было десять, как в десятичной записи, границы между ними были бы узкими, а ошибки чтения — частыми.

Второй аргумент — простота схем. Транзистор работает как ключ: открыт или закрыт. Из ключей собирают логические элементы (И, ИЛИ, НЕ), из них — сумматоры, регистры и весь процессор, а математика для двух состояний уже готова: булева алгебра.

Это не единственно возможный путь: в 1958 году в МГУ построили рабочий троичный компьютер «Сетунь». Но двоичная схема оказалась дешевле в массовом производстве, и сегодня вся серийная вычислительная техника — двоичная.

Бит, байт и разряды

Бит — минимальная единица информации: одна позиция, в которой стоит либо 0, либо 1. Один бит различает два значения. Каждый добавленный бит удваивает число комбинаций, поэтому n битов дают 2^n различных значений, а не n и не 2n.

Байт — группа из 8 битов, минимальная адресуемая единица памяти в подавляющем большинстве архитектур: процессор читает и пишет память байтами, а не отдельными битами. Один байт принимает 2^8 = 256 значений — от 00000000 до 11111111, то есть от 0 до 255.

Разрядов (бит) Комбинаций Диапазон без знака Где встречается
1 2 0..1 флаг, логическое значение
4 16 0..15 одна шестнадцатеричная цифра
8 256 0..255 байт, канал цвета, символ ASCII
16 65 536 0..65 535 короткое целое, отсчет звука на CD
32 4 294 967 296 0..4 294 967 295 целое int во многих языках, адрес IPv4
64 около 1.8·10^19 0..2^64 — 1 целое long, адрес памяти в 64-битной системе

Разряд — позиция в записи числа. В двоичной записи каждый разряд имеет вес, равный степени двойки: справа налево 1, 2, 4, 8, 16 и так далее. Самый правый разряд — младший, самый левый — старший. На весах разрядов и строится перевод между системами счисления.

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

Десятичное число в двоичное: деление на 2

Число делят на 2 нацело и записывают остаток (0 или 1). Частное снова делят на 2, пока оно не станет нулем. Двоичная запись — остатки, прочитанные снизу вверх (от последнего к первому).

Переведем 13:

Шаг Делим Частное Остаток
1 13 : 2 6 1
2 6 : 2 3 0
3 3 : 2 1 1
4 1 : 2 0 1

Читаем остатки снизу вверх: 1101. Итак, 13 = 1101 в двоичной записи. Частая ошибка — прочитать остатки сверху вниз и получить 1011 (это 11, а не 13).

Для маленьких чисел быстрее не делить, а раскладывать по степеням двойки: 13 = 8 + 4 + 1, значит единицы стоят в разрядах с весами 8, 4 и 1, а в разряде с весом 2 — ноль: 1101. Деление на 2 удобнее, когда число большое и на глаз не раскладывается.

Двоичное число в десятичное: сумма весов

Каждую единицу умножают на вес ее разряда и складывают. Для 1101 (веса справа налево 1, 2, 4, 8):

1·8 + 1·4 + 0·2 + 1·1 = 8 + 4 + 0 + 1 = 13.

Еще пример: 10110 = 1·16 + 0·8 + 1·4 + 1·2 + 0·1 = 22.

Восьмеричная и шестнадцатеричная запись

Длинные цепочки нулей и единиц неудобно читать, поэтому используют компактные формы. В шестнадцатеричной системе одна цифра (0-9, A-F) ровно соответствует четырем битам, в восьмеричной (0-7) — трем. Это не другой код, а сокращенная запись того же двоичного: 1101 = D (hex) = 15 (oct), байт 11111111 = FF = 255. Поэтому цвета в CSS пишут как #FF0000, а адреса памяти — как 0x7FFE: двоичные данные видны «сжато», без перевода в десятичную систему.

Двоичный алфавит и кодирование символов

Двоичный алфавит — алфавит из двух символов, 0 и 1; его мощность (число символов) равна двум. Чтобы записать таким алфавитом буквы, каждой букве назначают номер и хранят его в двоичном виде. Договоренность «символ — номер» называется кодировкой.

ASCII — историческая 7-битная кодировка: 128 символов с номерами от 0 до 127 (латиница, цифры, знаки препинания, управляющие символы). Заглавная латинская A имеет номер 65, в двоичной записи 01000001 (старший, восьмой бит байта остается нулем). Кириллицы в ASCII нет: для нее использовали 8-битные кодировки (CP1251, KOI8-R), которые заполняли верхнюю половину байта, 128-255, каждая по-своему. Отсюда «кракозябры», когда текст открывали не в той кодировке.

Unicode решает проблему единым списком номеров для всех письменностей — в нем более 150 тысяч символов. UTF-8 — самый распространенный способ записать эти номера в байты, кодировка переменной длины: символ занимает от 1 до 4 байтов в зависимости от номера.

Символ Номер в Unicode Байты в UTF-8 Двоичный вид
A U+0041 (65) 1 байт: 41 01000001
а (кириллица) U+0430 (1072) 2 байта: D0 B0 11010000 10110000
U+20AC 3 байта: E2 82 AC 11100010 10000010 10101100
😀 U+1F600 4 байта: F0 9F 98 80 11110000 10011111 10011000 10000000

Первые 128 символов UTF-8 совпадают с ASCII байт в байт, поэтому английский текст читается без изменений. А одна кириллическая буква в UTF-8 занимает 2 байта: слово «привет» из 6 букв весит 12 байтов.

Как кодируют числа, текст, изображения и звук

Один и тот же байт 01000001 — это число 65, буква A или яркость канала цвета. Смысл битам придает не запись, а формат, в котором программа их читает.

Тип данных Что кодируется Как Типичный размер
Целые числа значение позиционная двоичная запись; отрицательные — в дополнительном коде 8, 16, 32, 64 бит
Дробные числа знак, мантисса, порядок стандарт IEEE 754 32 бит (float), 64 бит (double)
Текст номер каждого символа кодировка: ASCII, UTF-8, UTF-16 1-4 байта на символ в UTF-8
Изображение (растр) цвет каждого пикселя три канала R, G, B по 8 бит; красный = 11111111 00000000 00000000 24 бит на пиксель, около 16.7 млн цветов
Звук амплитуда сигнала через равные промежутки времени дискретизация: на CD 44 100 отсчетов в секунду по 16 бит на каждый канал 1411 кбит/с для стерео без сжатия

Общий принцип: объект сначала измеряют или нумеруют (пиксель — цветом, звук — амплитудой, букву — номером), а полученные числа записывают в двоичном виде. Сжатие (JPEG, MP3, ZIP) добавляется поверх и меняет объем, а не идею.

Двоичный код в программировании

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

bin(13)                  # '0b1101'  - число в двоичную строку
int('1101', 2)           # 13        - двоичная строка в число
0b1101                   # 13        - двоичный литерал прямо в коде
format(13, '08b')        # '00001101' - с ведущими нулями до байта
hex(13), oct(13)         # ('0xd', '0o15')

ord('A')                 # 65
format(ord('A'), '08b')  # '01000001'
'а'.encode('utf-8')      # b'\xd0\xb0' - кириллическая буква = 2 байта
len('привет'), len('привет'.encode('utf-8'))   # (6, 12)

Побитовые операции работают с каждым разрядом отдельно. Возьмем 13 = 1101 и 6 = 0110:

13 & 6    # 4   - И:   1101 & 0110 = 0100 (единица там, где она в обоих)
13 | 6    # 15  - ИЛИ: 1101 | 0110 = 1111 (единица там, где хотя бы в одном)
13 ^ 6    # 11  - исключающее ИЛИ: 1101 ^ 0110 = 1011 (где биты различаются)
13 << 2   # 52  - сдвиг влево на 2 = умножение на 4
13 >> 1   # 6   - сдвиг вправо на 1 = целочисленное деление на 2
~13       # -14 - инверсия; в Python дает -(x + 1), так как int не ограничен байтом

Признак, когда побитовые операции уместны: у объекта есть набор независимых «да/нет» свойств. Их удобно хранить в одном числе, где каждый бит — отдельный флаг: проверка — value & FLAG, установка — value | FLAG. Так устроены права доступа к файлам в Unix (rwx = три бита) и многие системные API.

Типовые ошибки

Путать 2^n и n. Из «байт — 8 бит» делают вывод, что байт хранит 8 значений, или что для 256 значений нужно 256 бит. Правильно: n бит дают 2^n значений, 8 бит — 256. Родственная ошибка в коде — считать длину двоичной записи через len(bin(x)):

len(bin(255))            # 10 - неверно: посчитан и префикс '0b'
(255).bit_length()       # 8  - верно: 255 помещается в 8 бит
(256).bit_length()       # 9  - а 256 уже нет: это 257-е значение

Считать, что байт равен символу. Это верно только для ASCII. В UTF-8 кириллица занимает 2 байта, поэтому резать байтовую строку «по символам» нельзя:

s = 'привет'
s.encode('utf-8')[:3].decode('utf-8')
# UnicodeDecodeError: 'utf-8' codec can't decode byte 0xd1 in position 2: unexpected end of data
s[:2].encode('utf-8')    # b'\xd0\xbf\xd1\x80' - верно: сначала режем строку, потом кодируем

Забыть основание при переводе строки в число.

int('1101')              # 1101 - строка прочитана как десятичное число
int('1101', 2)           # 13   - верно: указано основание 2

Путать двоичный код и «код программы». Двоичный код — форма записи любых данных нулями и единицами. Код программы — текст на языке программирования, который пишет и читает человек. Между ними компилятор или интерпретатор: он превращает исходный код в машинный — двоичные команды процессора. Фраза «программист пишет на двоичном коде» неверна: вручную такие команды не набирают, разве что в учебных целях.

Выводы

  • Двоичный код — это способ записи информации с помощью двух символов, 0 и 1; синоним — бинарный код, а представление чисел, текста, картинок и звука такими цепочками называется двоичным кодированием.
  • Компьютеры используют два состояния из-за физики: различить два уровня напряжения просто и надежно, транзистор работает как ключ, а математика для двух состояний — булева алгебра.
  • Бит — одна позиция со значением 0 или 1, байт — группа из 8 битов и минимальная адресуемая единица памяти; n битов дают 2^n значений, байт — 256 значений от 0 до 255.
  • Перевод из десятичной системы в двоичную — деление на 2 с чтением остатков снизу вверх (13 = 1101), обратный перевод — сумма весов разрядов (1·8 + 1·4 + 0·2 + 1·1 = 13).
  • Текст кодируется через номера символов: ASCII — 7-битная кодировка на 128 символов, UTF-8 записывает номера Unicode переменной длиной от 1 до 4 байтов, одна кириллическая буква занимает 2 байта.
  • В Python двоичную запись дают bin(), int(s, 2) и format(x, '08b'), а побитовые операции &, |, ^, <<, >> работают с каждым разрядом отдельно и удобны для флагов и масок.

Где применяется / связь с практикой

Понимание двоичного кода нужно не для чтения нулей и единиц глазами, а чтобы объяснять поведение программ: почему 0.1 + 0.2 не равно 0.3, почему строка из 6 букв весит 12 байтов, почему 32-битный int переполняется на 2 147 483 647, как читать маску подсети 255.255.255.0 и права доступа 755. На этот слой опираются алгоритмы, сети, базы данных и системное программирование.

Если хотите двигаться от основ к разработке, посмотрите каталог курсов по программированию Otus: там есть программы по Python, C, Go и другим языкам, где представление данных и побитовые операции разбираются на практических задачах. Познакомиться с форматом заранее можно на открытых уроках Otus — они бесплатные и проходят регулярно.

Смежные темы: Двоичная система счисления и бинарный код — что нужно знать новичку, Кодирование символов и UTF, Введение в кодирование информации.

FAQ

Как записывают отрицательные числа? В дополнительном коде: старший бит отводят под знак, отрицательное число получают инверсией битов положительного и прибавлением 1. В 8 битах помещается диапазон от -128 до 127, а сложение работает одной схемой для чисел любого знака.

Почему дробные числа «не сходятся»? Дробь 0.1 в двоичной записи бесконечна (как 1/3 в десятичной), а под мантиссу отведено конечное число бит, поэтому 0.1 + 0.2 в Python дает 0.30000000000000004. Для денег используют Decimal или целые в копейках.

Килобайт — это 1000 или 1024 байта? По стандарту 1 КБ = 1000 байт, а 1024 байта — кибибайт (КиБ). Производители дисков считают тысячами, операционные системы часто степенями двойки, поэтому диск «на 500 ГБ» показывает около 465 ГБ.

OTUS Журнал
Скидка 10% 7-13 сентября на курсы из спецкаталога (pop-up)