Основы Computer Science Двоичная система, биты и байты: как всё становится числами
0%

Двоичная система, биты и байты: как всё становится числами

Двоичная система, биты и байты: как всё становится числами

Внутри любого компьютера — от микроконтроллера в чайнике до дата-центра — нет ни букв, ни картинок, ни звука, ни «файлов». Есть только миллиарды крошечных переключателей, каждый из которых в данный момент либо включён, либо выключен. Всё остальное — тексты этой статьи, ваша фотография, музыка, банковский баланс — это способ истолковать длинные вереницы этих «включено/выключено» как что-то осмысленное.

Эта статья — про самый нижний слой абстракции, на котором всё держится: про бит. Мы разберём, почему компьютеру удобнее считать двумя цифрами, а не десятью; как из битов собираются байты и числа; как переводить между двоичной, десятичной и шестнадцатеричной записью; и — что важнее всего для инженера — где эта простая на вид абстракция начинает протекать и бить по рукам. В предыдущей статье трека, https://courses.digitable.life/post/computer-science/01-what-is-computation/, мы говорили о том, что такое вычисление вообще; здесь спускаемся на уровень ниже — к материалу, из которого вычисление сделано.

1. Почему именно два: за абстракцию отвечает физика

Люди считают десятками не потому, что десятка чем-то математически особенная, а потому, что у нас десять пальцев. Компьютеру пальцы не нужны — ему нужна надёжность. И тут выясняется, что различать два состояния несопоставимо проще и дешевле, чем десять.

Внутри процессора информацию несёт напряжение на проводке. Если договориться, что «0 вольт — это цифра 0, а 5 вольт — это цифра 1», то между ними огромный запас: даже если сигнал просядет до 3,5 вольт из-за помех, нагрева или соседнего провода, его всё равно уверенно прочитают как «1». А вот если бы мы захотели закодировать десять цифр десятью уровнями напряжения (0; 0,5; 1,0; … вольта), любая помеха в полвольта превращала бы «3» в «4», и вычисления рассыпались бы. Двоичная система — это инженерный компромисс: мы жертвуем компактностью записи ради колоссального запаса помехоустойчивости.

Физический носитель бита может быть каким угодно — важно лишь, чтобы у него было два хорошо различимых устойчивых состояния:

  • транзистор открыт или закрыт (процессор, оперативная память);
  • участок намагничен в одну или другую сторону (жёсткий диск);
  • ячейка флеш-памяти заряжена или нет (SSD, флешка);
  • питы и лэнды на поверхности отражают лазер или нет (CD/DVD);
  • свет в оптоволокне есть или нет в данный такт (сеть).

Именно эта абстракция — «два устойчивых состояния = один бит» — позволяет всей остальной цифровой технике не зависеть от физики. Как из этих переключателей собирают логику и арифметику, мы разберём в https://courses.digitable.life/post/computer-science/04-boolean-logic-and-gates/, а как из логики — процессор, в https://courses.digitable.life/post/computer-science/05-how-cpu-works/.

Бит (bit, от binary digit) — минимальная единица информации: ответ на один вопрос «да или нет», выбор из двух вариантов. Один бит различает 2 состояния, два бита — 4, три — 8, а n битов — 2ⁿ разных значений. Это удвоение на каждом добавленном бите — самое важное число во всей информатике.

2. Как вообще работают числа: позиционные системы

Чтобы понять двоичную запись, нужно на секунду перестать воспринимать привычные числа как данность и увидеть в них механизм. Запись 2026 в десятичной системе — это не «две тысячи двадцать шесть» как единое имя, а компактная формула:

2026 = 2·10³ + 0·10² + 2·10¹ + 6·10⁰
     = 2000 +   0   +   20  +   6

Каждая позиция (разряд) стоит в 10 раз больше соседней справа. Число 10 здесь называется основанием системы счисления. В этом и весь фокус позиционной записи: имея всего 10 цифр-символов (0–9), мы записываем сколь угодно большие числа, а «вес» цифры задаётся её местом. Про историю самой идеи позиционной записи и почему её изобретение было переворотом — подробнее в математическом треке: https://courses.digitable.life/post/mathematics/00-overview/.

Ничто не заставляет нас брать основанием именно 10. Возьмём 2 — и получим ровно ту же механику, только каждая позиция стоит в два раза больше соседней, а цифр всего две: 0 и 1.

1101₂ = 1·2³ + 1·2² + 0·2¹ + 1·2⁰
      =  8   +  4   +  0   +  1   = 13₁₀

Нижний индекс (, ₁₀, ₁₆) — это указание основания; без него 1101 можно прочитать и как тринадцать (двоичное), и как тысячу сто один (десятичное). В коде для той же цели используют префиксы: 0b1101 — двоичное, 0x — шестнадцатеричное, обычная запись — десятичное.

3. Считаем на двух цифрах

Счёт в двоичной системе работает по тому же правилу, что и в десятичной, просто «перенос» случается не после 9, а после 1. Как только разряд переполняется, он обнуляется, а слева прибавляется единица:

Десятичное Двоичное Десятичное Двоичное
0 0000 5 0101
1 0001 6 0110
2 0010 7 0111
3 0011 8 1000
4 0100 9 1001

Обратите внимание на переход 0111 → 1000: три единицы справа переполнились и «сбросили» перенос через весь набор разрядов. Это ровно то же, что 999 → 1000 в десятичной. Ниже — тот же процесс как цикл состояний двухбитного счётчика: он проходит все 2² = 4 значения и на пятом шаге переполняется обратно в ноль.

Тот факт, что n битов «наматывают» ровно 2ⁿ значений и потом начинают заново с нуля, — не мелочь, а источник целого класса реальных багов. К нему мы вернёмся в разделе про переполнение.

4. Бит, байт и почему их именно восемь

Одним битом много не скажешь — всего два варианта. Поэтому биты группируют. Исторически прижилась группа из восьми битов — байт (byte). Восемь битов дают 2⁸ = 256 разных значений: как раз достаточно, чтобы закодировать все буквы латиницы, цифры, знаки препинания и управляющие символы (так родился ASCII), и при этом группа остаётся удобной степенью двойки. Полубайт (4 бита) называют ниблом (nibble) — он ровно соответствует одной шестнадцатеричной цифре, что нам скоро пригодится.

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

Анатомия байта: восемь битов и их веса как степени двойки

Байт — это минимальная адресуемая единица памяти: процессор не умеет дать вам «третий бит по такому-то адресу», он оперирует целыми байтами, а внутри уже вы вытаскиваете нужный бит операциями, которые разберём ниже. Как байты выстраиваются в иерархию памяти — от регистров до диска — тема отдельной статьи трека, https://courses.digitable.life/post/computer-science/06-memory-hierarchy/.

Само слово «байт» и восьмибитный стандарт устоялись не сразу — на заре вычислительной техники встречались машины с 6-, 7-, 9-битными «байтами». Вот короткая линия времени, как двоичная идея дошла от философии до стандарта индустрии:

5. Перевод между системами

Из двоичной в десятичную переводить просто: выпишите веса позиций, где стоят единицы, и сложите. Для 10110₂ единицы стоят на позициях 4, 2 и 1: 16 + 4 + 2 = 22.

Из десятичной в двоичную удобнее всего делить на 2 и собирать остатки снизу вверх. Остаток (0 или 1) на каждом шаге — это очередной бит, начиная с младшего:

Тот же алгоритм в коде — и заодно напоминание, что в большинстве языков перевод уже встроен, и «руками» его писать в проде не нужно:

def to_binary(n: int) -> str:
    """Перевод неотрицательного целого в двоичную строку — учебная реализация."""
    if n == 0:
        return "0"
    bits = []
    while n > 0:
        bits.append(str(n % 2))  # остаток от деления на 2 — очередной бит
        n //= 2                  # целочисленное деление сдвигает нас к старшим разрядам
    return "".join(reversed(bits))  # остатки собираем в обратном порядке

# На практике так писать не нужно — есть встроенные средства:
print(bin(13))          # 0b1101
print(f"{13:08b}")      # 00001101 — форматирование с ведущими нулями
print(int("1101", 2))   # 13 — обратный перевод

Сложность обоих переводов — O(log N) по числу разрядов результата: каждое деление отрезает один бит. Это, кстати, наглядный пример того, почему количество цифр в записи числа растёт логарифмически от самого числа.

6. Шестнадцатеричная система: стенография для битов

Двоичная запись честная, но нечитаемая: 11010010011110000101 глазом не охватить и не сравнить. Проблема в том, что битов много. Решение — сгруппировать их по четыре и каждую четвёрку записать одним символом. Четыре бита дают 2⁴ = 16 вариантов, поэтому и нужна система с основанием 16 — шестнадцатеричная (hex). Цифр в ней шестнадцать: 0–9, а дальше A B C D E F для значений 10–15.

Красота hex в том, что перевод в двоичную и обратно — чисто механический, без всякой арифметики: один шестнадцатеричный символ = ровно один нибл (4 бита).

Hex Двоичное Dec Hex Двоичное Dec
0 0000 0 8 1000 8
1 0001 1 9 1001 9
2 0010 2 A 1010 10
3 0011 3 B 1011 11
4 0100 4 C 1100 12
5 0101 5 D 1101 13
6 0110 6 E 1110 14
7 0111 7 F 1111 15

Поэтому байт (8 бит) — это всегда ровно две hex-цифры, от 00 до FF (0–255). Тот же нечитаемый пример разбивается по четвёрки и сворачивается мгновенно:

1101 0010 0111 1000 0101₂
  D    2    7    8    5   →  0xD2785

Именно поэтому hex повсюду, где важно видеть биты, но не хочется тонуть в них: цвета в вебе (#FF8800 — это байты красного, зелёного, синего), MAC- и IPv6-адреса, дампы памяти, машинный код, контрольные суммы, значения регистров. Когда в отладчике вы видите 0xDEADBEEF, это просто 32 бита, записанные восемью удобными символами.

7. Арифметика в двоичной: сложение и коварство переполнения

Складываются двоичные числа по школьному столбику, только таблица сложения короче некуда: 0+0=0, 0+1=1, 1+0=1, а 1+1=10 — ноль в разряд, единица в перенос. Вот 5 + 3:

  0101   (5)
+ 0011   (3)
------
  1000   (8)   ← перенос прокатился через три разряда

Процессор делает ровно это, только над 32 или 64 битами сразу, и сложение из таких «переносов» — одна из базовых операций его арифметико-логического устройства (об этом в https://courses.digitable.life/post/computer-science/05-how-cpu-works/). Но здесь же прячется фундаментальное ограничение. Регистр процессора имеет фиксированную ширину. Если результат не влезает в отведённые биты, старший перенос просто теряется — число «наматывается» обратно, как одометр, проехавший 999999 км.

# 8-битное беззнаковое число вмещает 0..255.
# Смоделируем регистр фиксированной ширины через маску:
a = 255            # 11111111
b = 1
res = (a + b) & 0xFF   # & 0xFF отбрасывает всё, что не влезло в 8 бит
print(res)             # 0, а не 256 — произошло переполнение (wrap-around)

Это не абстрактная страшилка. Со знаковыми числами (где старший бит отдан под знак — механику разберём в следующей статье) переполнение может внезапно превратить большое положительное число в отрицательное. На таких ошибках падали ракеты (Ariane 5, 1996 — переполнение при конвертации 64-битного числа в 16-битное), зависали игры и ломались банковские расчёты. Как именно кодируются отрицательные и дробные числа (дополнительный код, IEEE 754) — тема следующей статьи трека, https://courses.digitable.life/post/computer-science/03-data-representation/.

8. Биты как переключатели: битовые операции

Раз байт — это набор независимых «да/нет», часто удобно работать не с числом целиком, а с отдельными битами. Для этого есть побитовые операции. Они применяются к каждой паре битов независимо и стоят процессору буквально один такт:

Операция Символ Правило Зачем
AND (И) & 1 только если оба 1 погасить лишние биты (маска)
OR (ИЛИ) | 1 если хотя бы один 1 включить биты
XOR (искл. ИЛИ) ^ 1 если биты разные переключить биты, простое шифрование
NOT (НЕ) ~ инвертирует каждый бит инверсия
сдвиг влево << двигает биты влево, справа нули умножение на 2ⁿ
сдвиг вправо >> двигает биты вправо деление на 2ⁿ

Эти же операции — прямое отражение логических вентилей, из которых физически собран процессор; связь «побитовая операция ↔ логический вентиль» подробно разбирается в https://courses.digitable.life/post/computer-science/04-boolean-logic-and-gates/. Классический приём — битовые флаги: восемь настроек «включено/выключено» упаковывают в один байт вместо восьми переменных.

# Права доступа как отдельные биты внутри одного числа
READ    = 0b0001   # 1 — бит чтения
WRITE   = 0b0010   # 2 — бит записи
EXECUTE = 0b0100   # 4 — бит исполнения

perms = READ | WRITE          # включаем чтение и запись -> 0b0011 = 3

# Проверить, установлен ли бит: маской AND
can_write = perms & WRITE     # != 0, значит право есть
print(bool(can_write))        # True

# Снять бит: AND с инверсией маски
perms = perms & ~WRITE        # -> 0b0001, запись отозвана

# Переключить бит: XOR
perms = perms ^ EXECUTE       # был 0 -> стал 1

# Сдвиги = быстрое умножение/деление на степень двойки
print(1 << 4)   # 16 — единица, сдвинутая на 4 позиции, это 2⁴
print(48 >> 1)  # 24 — сдвиг вправо на 1 делит пополам

Именно так под капотом устроены права файлов в Unix (те самые chmod 755), наборы опций, компактные множества и десятки других вещей, где важна плотность и скорость. Восемь булевых полей в одном байте — это не только экономия памяти, но и одна операция вместо восьми проверок.

9. Как байты лежат в памяти: порядок байтов

Пока число занимает один байт, вопросов нет. Но 32-битное число — это четыре байта, и их нужно как-то разложить по последовательным адресам памяти. И тут два разумных инженерных ответа, оба используются до сих пор:

  • Big-endian — «тупоконечный»: старший байт кладут первым, по младшему адресу. Так пишут числа люди и так принято в сетевых протоколах.
  • Little-endian — «остроконечный»: первым идёт младший байт. Так работают процессоры x86 и (обычно) ARM.

Big-endian против little-endian: раскладка числа 0x0A0B0C0D по четырём байтам памяти

Названия — отсылка Дэнни Коэна к «Путешествиям Гулливера», где государства воевали из-за того, с какого конца разбивать яйцо; в его классической заметке «On Holy Wars and a Plea for Peace» (1980) прямо сказано, что сам выбор не важен — важно, чтобы все договорились. Проблема протекает ровно на границе: когда байты одной машины читает другая. Файл, записанный на little-endian ПК и прочитанный на big-endian системе побайтово, даст перевёрнутые числа. Поэтому в сетевом коде числа гоняют через «сетевой порядок» (big-endian) функциями htonl/ntohl, а форматы файлов явно фиксируют порядок в спецификации. Как это всплывает в сетях — увидим в https://courses.digitable.life/post/computer-science/11-networking-basics/.

10. Килобайт, который не килобайт

Когда байтов становятся тысячи и миллионы, нужны приставки. И здесь индустрия оставила нам ловушку. «Кило-» в физике — это ровно 1000. Но 1000 — не степень двойки, а компьютеру родные именно степени двойки, и ближайшая круглая — 2¹⁰ = 1024. Исторически «килобайт» стали употреблять в значении 1024 байта, и дальше пошла путаница:

Приставка (СИ, ×1000) Двоичная (IEC, ×1024) Разница
1 KB = 10³ = 1 000 1 KiB = 2¹⁰ = 1 024 +2,4 %
1 MB = 10⁶ 1 MiB = 2²⁰ = 1 048 576 +4,9 %
1 GB = 10⁹ 1 GiB = 2³⁰ +7,4 %
1 TB = 10¹² 1 TiB = 2⁴⁰ +10 %

Чтобы прекратить споры, стандарт IEC 80000-13 ввёл отдельные двоичные приставки: KiB, MiB, GiB (кибибайт, мебибайт, гибибайт) — строго степени 1024, тогда как KB, MB, GB по СИ означают степени 1000. На практике: производители дисков считают по СИ (диск «1 ТБ» = 10¹² байт), а операционная система (особенно Windows) показывает размер в гибибайтах, но подписывает его «ГБ» — вот почему «терабайтный» диск в системе выглядит как ~931 «ГБ». Никто вас не обманул: просто 10¹² / 2³⁰ ≈ 931. Расхождение растёт с размером — на терабайтах это уже почти 10 %.

11. Где абстракция протекает

«Всё это просто биты» — мощная и по большей части надёжная абстракция. Но, как всякая абстракция, она протекает, и хороший инженер знает где:

  • Конечная разрядность. Числа не бесконечны. int32 переполняется на ~2,1 млрд, и это переполнение по умолчанию молчаливое — программа не падает, а тихо считает неправильно. Проблема 2038 года (Unix-время в 32-битном знаковом счётчике секунд) — прямое следствие. Знать ширину типа, с которым работаешь, обязательно.
  • Дробные числа приблизительны. 0.1 + 0.2 != 0.3 во всех языках с IEEE 754 — не баг, а следствие того, что 0.1 невыразим конечным числом битов в двоичной дроби. Механику разберём в https://courses.digitable.life/post/computer-science/03-data-representation/; практический вывод — не хранить деньги во float.
  • Байт без контекста бессмыслен. Одни и те же 8 бит 0100 0001 — это и число 65, и буква A, и часть пикселя, и кусок инструкции процессора. Смысл придаёт только код, который их читает. Прочитать байты «не тем» типом (текст как число, число не в том порядке байтов) — классический источник «кракозябр» и повреждённых данных.
  • Приставки врут. «1 ГБ» от провайдера, от производителя диска и от вашей ОС могут означать три немного разных числа. При расчётах ёмкости и трафика уточняйте, о каких байтах речь.

Ни один из этих пунктов не отменяет пользы абстракции — они лишь очерчивают её границы. Понимание, что под текстом, картинкой и числом с плавающей точкой лежат одни и те же биты, — это то, что отличает инженера, который чинит баг за пять минут, от того, кто сутки смотрит на «необъяснимое» поведение.

Итог

  • Компьютер хранит и обрабатывает информацию как биты — два устойчивых физических состояния; выбор двоичной системы продиктован не математикой, а помехоустойчивостью.
  • Двоичная запись — обычная позиционная система с основанием 2; n битов кодируют 2ⁿ значений, и это удвоение — ключевое число информатики.
  • Байт (8 бит, 256 значений) — минимальная адресуемая единица; hex — компактная стенография, где один символ = один нибл.
  • Одни и те же биты означают число, текст, цвет или команду в зависимости от интерпретации — сами по себе они нейтральны.
  • Абстракция протекает на конечной разрядности (переполнение), приблизительности дробей, порядке байтов и путанице приставок — это и есть места, где инженеру нужно быть начеку.

Источники

Что дальше

Мы выяснили, что всё внутри компьютера — это биты, а байты — способ их группировать. Но как именно из битов получаются отрицательные и дробные числа, буквы всех алфавитов, цвета и звук? Об этом — следующая статья: Представление данных: целые, дробные (IEEE 754), текст (Unicode), цвет, звук.

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

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

Доска запросов