От чисел к данным: кодировки и адреса

Внутри компьютера нет ни букв, ни картинок, ни адресов сайтов — только числа. Разберёмся, как одни и те же числа превращаются то в символы текста, то в адрес в сети.

Кодировка — это таблица соответствия «число ↔ символ»: каждому символу присвоен номер (код), и текст в памяти хранится как последовательность этих номеров, записанных в двоичной системе.

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

Как числа становятся символами

Исторически первой массовой таблицей стала ASCII. Она присваивает код каждому символу английского алфавита, цифрам и знакам препинания. Кодов всего 128, то есть номера от 0 до 127 — это ровно 7 бит, потому что $2^7 = 128$. Несколько опорных значений стоит запомнить:

СимволКод (10)Код (16)Код (2)
пробел32200100000
'0'48300110000
'A'65411000001
'a'97611100001

Заметь закономерность: цифры идут подряд начиная с 48, заглавные буквы — с 65, строчные — с 97. Поэтому буква 'C' имеет код $65 + 2 = 67$, а между 'A' и 'a' ровно $97 - 65 = 32$ — это пригодится для смены регистра.

ord и chr в Python

В Python есть две парные функции: ord(символ) возвращает номер символа, а chr(номер) — наоборот, символ по номеру. Это прямой мост между числом и буквой. Посмотрим:

# символ -> номер
print("Код 'A':", ord("A"))
print("Код 'a':", ord("a"))
print("Код '0':", ord("0"))

# номер -> символ
print("chr(66):", chr(66))
print("chr(97):", chr(97))

# слово как список кодов
word = "Hi!"
codes = [ord(c) for c in word]
print("Коды слова Hi!:", codes)

Вывод:

Код 'A': 65
Код 'a': 97
Код '0': 48
chr(66): B
chr(97): a
Коды слова Hi!: [72, 105, 33]

Слово Hi! превратилось в три числа: 72, 105, 33. Именно так оно и лежит в памяти — как байты со значениями этих кодов. Раз код заглавной отличается от строчной ровно на 32, перевести букву в нижний регистр можно арифметикой:

letter = "H"
lower = chr(ord(letter) + 32)
print("H в нижнем регистре:", lower)

Вывод:

H в нижнем регистре: h

От ASCII к Unicode

128 символов хватает для английского, но в мире есть кириллица, иероглифы, эмодзи. Поэтому появился Unicode — огромная таблица, где номер (его называют «кодовая точка») есть у каждого символа любого языка. Русская 'А' имеет код 1040, а 'Я' — 1071. Тот же ord работает и с ними:

$$ \text{ord}(\text{'А'}) = 1040, \qquad \text{ord}(\text{'Я'}) = 1040 + 31 = 1071 $$

ASCII при этом не выбросили: первые 128 кодов Unicode совпадают с ASCII один в один. Поэтому ord("A") и там, и там равно 65 — старые тексты остаются читаемыми. Меняется лишь то, что под большие коды нужно больше битов: одного байта (максимум 255) на номер 1040 уже не хватает, и кодировки вроде UTF-8 хранят такой символ несколькими байтами.

Как это работает: IP-адрес как 4 байта

Числа становятся не только текстом. Возьми адрес сайта в сети — например, 192.168.1.10. Это формат IPv4, и за точками скрываются ровно 4 байта. Каждое число между точками — один байт, поэтому каждое из них лежит в диапазоне 0–255 (вспомни: байт хранит как раз 0–255). Адрес 192.168.1.10 — это четыре байта со значениями 192, 168, 1 и 10.

Сколько всего адресов даёт такая схема? Четыре байта — это 32 бита, значит:

$$ 256^4 = 2^{32} = 4\,294\,967\,296 $$

Около 4,3 миллиарда — и этого, кстати, уже не хватает на все устройства планеты, поэтому придумали IPv6 с 128 битами. Весь адрес можно свернуть в одно большое число, разложив байты по разрядам системы с основанием 256:

parts = [192, 168, 1, 10]

# собираем 4 байта в одно 32-битное число
value = 0
for p in parts:
    value = value * 256 + p
print("Адрес как число:", value)
print("В шестнадцатеричной:", hex(value))

# и обратно: достаём байты делением с остатком
back = []
n = value
for _ in range(4):
    back.append(n % 256)
    n = n // 256
back.reverse()
print("Обратно в байты:", back)

Вывод:

Адрес как число: 3232235786
В шестнадцатеричной: 0xc0a8010a
Обратно в байты: [192, 168, 1, 10]

Видишь связь? Чтобы собрать байты в число, мы умножаем на 256 и прибавляем следующий — это та же позиционная запись, что и обычное число, только основание не 10, а 256. А чтобы разобрать обратно, делим с остатком на 256. Те же действия, что при переводе между системами счисления — просто разряд тут размером в байт. И заметь: в hex адрес 0xc0a8010a распадается на пары c0 a8 01 0a, и это ровно 192, 168, 1, 10 — снова видно, что байт = две hex-цифры.

Частые ошибки

  • Путать символ-цифру и само число. ord("0") равно 48, а не 0. Символ '0' — это просто значок с кодом 48, а не число ноль.
  • Думать, что ASCII покрывает кириллицу. Русские буквы — это уже Unicode (коды больше 127), в чистый ASCII они не помещаются.
  • Превышать диапазон байта в IP. Записи вроде 192.300.1.1 не бывает: 300 больше 255 и в один байт не влезает.
  • Считать, что один символ — всегда один байт. В UTF-8 английская буква занимает 1 байт, а русская или эмодзи — несколько. Число символов и число байтов могут не совпадать.

Итоги

  • Текст в памяти — это коды символов; кодировка задаёт таблицу «число ↔ символ».
  • ASCII покрывает 128 символов (7 бит, $2^7$); цифры идут с 48, заглавные с 65, строчные с 97.
  • ord даёт код символа, chr — символ по коду; разница регистров латиницы ровно 32.
  • Unicode расширяет таблицу до всех языков, сохраняя первые 128 кодов совместимыми с ASCII.
  • IPv4-адрес — это 4 байта (числа 0–255), всего $2^{32} \approx 4{,}3$ млрд адресов; сборка и разбор — позиционная запись по основанию 256.
Проверьте себя
1. Чему равно ord("0") в Python?
A0
B30
C48
Dошибка, ord не работает с цифрами
2. Почему каждое число в IPv4-адресе (например, 192.168.1.10) не может быть больше 255?
AТак договорились авторы интернета, ограничение можно снять
BКаждое число — это один байт, а байт хранит значения только от 0 до 255
CПотому что 255 — это максимум для шестнадцатеричной системы
DЧтобы адрес помещался в десятичную запись