Двоичная и шестнадцатеричная системы

Урок объясняет, как читать и переводить числа между двоичной, десятичной и шестнадцатеричной системами.

Позиционная система — запись числа, где вклад цифры зависит от её позиции (разряда): значение разряда есть степень основания системы.

Зачем три системы вместо одной

Человек считает в десятичной системе (основание 10) по историческим причинам — десять пальцев. Машина работает в двоичной (основание 2), потому что у транзистора два состояния. Шестнадцатеричная (основание 16) — это удобный «человеческий» способ записывать двоичные данные коротко: каждые 4 бита ровно соответствуют одной hex-цифре. Программист постоянно прыгает между этими тремя представлениями, поэтому переводы надо чувствовать.

Как устроена позиционная запись

В числе каждый разряд умножается на степень основания. Для двоичного 1011:

разряд:    3    2    1    0
вес:      2^3  2^2  2^1  2^0
          = 8  = 4  = 2  = 1
бит:       1    0    1    1
вклад:     8 +  0 +  2 +  1  = 11 (десятичное)

Шестнадцатеричные цифры: 0–9, затем A=10, B=11, C=12, D=13, E=14, F=15. Поскольку 16 = 2^4, каждая hex-цифра кодирует ровно 4 бита (полубайт, nibble). Поэтому байт (8 бит) — это всегда две hex-цифры: 1011 0011 = B3.

Таблица соответствий

ДесятичноеДвоичноеHex
000000
501015
101010A
151111F
2551111 1111FF

Как работает под капотом: алгоритм перевода

В десятичную из двоичной — суммируем веса единичных битов. Из десятичной в двоичную — делим на 2 и собираем остатки снизу вверх. Проверим всё кодом на чистом Python (без сторонних библиотек):

def to_binary(n):
    if n == 0:
        return "0"
    bits = ""
    while n > 0:
        bits = str(n % 2) + bits   # остаток от деления на 2
        n //= 2
    return bits

def from_binary(s):
    value = 0
    for ch in s:
        value = value * 2 + int(ch)  # схема Горнера
    return value

for n in (11, 211, 255):
    b = to_binary(n)
    print(f"{n:>3} -> двоичное {b:>8} -> hex {n:02X} -> назад {from_binary(b)}")

Вывод:

 11 -> двоичное     1011 -> hex 0B -> назад 11
211 -> двоичное 11010011 -> hex D3 -> назад 211
255 -> двоичное 11111111 -> hex FF -> назад 255

Группировка битов в hex

def bin_to_hex_grouped(n, width=8):
    b = format(n, f"0{width}b")
    # бьём на группы по 4 бита и переводим каждую
    groups = [b[i:i+4] for i in range(0, len(b), 4)]
    hexd = [format(int(g, 2), "X") for g in groups]
    return " ".join(groups), "".join(hexd)

bits, hx = bin_to_hex_grouped(211)
print(f"211 = {bits} = 0x{hx}")

Вывод:

211 = 1101 0011 = 0xD3

Зачем это нужно на практике

Шестнадцатеричная запись — не учебная экзотика, а повседневный язык, на котором железо разговаривает с программистом. Цвета на веб-странице задают как #FF8800 — это три байта (красный, зелёный, синий), каждый парой hex-цифр. Адреса в памяти, дампы файлов, MAC-адреса сетевых карт, ключи шифрования, коды символов Unicode (U+1F423) — всё это пишут в hex именно потому, что одна цифра ровно соответствует четырём битам, и опытный глаз мгновенно «видит» за ней биты. Когда отладчик показывает 0x7FFE0000, программист читает не «огромное непонятное число», а аккуратную битовую структуру адреса.

Восьмеричная система (основание 8) встречается реже, но по той же логике: 8 = 2^3, поэтому одна восьмеричная цифра кодирует три бита. Её до сих пор используют, например, для прав доступа в Unix (chmod 755): три цифры — три тройки бит для владельца, группы и остальных. Понимая, что hex группирует биты по 4, а восьмеричная — по 3, вы перестаёте зубрить и начинаете видеть систему.

Историческая справка: почему именно эти основания

Выбор оснований не случаен и тесно связан с размером машинного слова. Ранние компьютеры часто имели слова, кратные трём битам, и тогда естественной была восьмеричная запись — так считали на PDP-8 и многих машинах 1960-х. Когда индустрия сошлась на 8-битном байте (во многом благодаря IBM System/360), победила шестнадцатеричная: байт делится на две ровные hex-цифры без остатка, а восьмеричная байт разбивала некрасиво (8 не делится на 3). Так размер байта продиктовал, какой «человеческий» язык для битов станет стандартом, — и им стал hex.

Любопытно, что десятичная система, родная человеку, для машины как раз неудобна: 10 не есть степень двойки, поэтому перевод между десятичной и двоичной требует деления с остатком, а не простой группировки. В этом и причина «странных» границ вроде 255 или 1024: они круглые в двоичном мире (2^8, 2^10) и некруглые в нашем.

Глубже: схема Горнера и почему она эффективна

Метод from_binary из примера выше — это знаменитая схема Горнера, и стоит понять, почему она так устроена. Вместо того чтобы вычислять каждую степень основания отдельно и потом складывать (что требует возведения в степень), мы идём слева направо, на каждом шаге умножая накопленный результат на основание и добавляя очередную цифру. Для числа 1011 это разворачивается как ((1·2 + 0)·2 + 1)·2 + 1. Тот же приём работает для любого основания — замените 2 на 16, и получите перевод из hex. Эта экономность не случайна: ровно так переводят числа внутри компиляторов и калькуляторов, потому что умножение и сложение дёшевы, а возведение в степень — дорого.

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

  • Путать вес разрядов. Самый правый бит — это 2^0 = 1, а не 2^1.
  • Забывать про знак. Эти переводы пока про неотрицательные числа; отрицательные — отдельная тема (дополнительный код).
  • Считать hex «другой системой чисел». Это та же величина, просто записанная компактнее; 0xFF и 255 — одно число.

Итог

  • Позиционная система: вклад цифры = цифра × (основание в степени разряда).
  • Hex удобен, потому что 1 цифра = 4 бита, а 1 байт = 2 hex-цифры.
  • Перевод dec→bin — деление с остатком; bin→dec — суммирование весов (схема Горнера).
Проверьте себя
1. Чему равно двоичное число 1101 в десятичной системе?
A11
B13
C14
D26
2. Почему шестнадцатеричная система удобна программистам?
AОна быстрее вычисляется процессором
BКаждая hex-цифра кодирует ровно 4 бита, а байт — две цифры
CHex-числа занимают меньше памяти
DПроцессор хранит числа в hex
3. Как перевести десятичное число в двоичное вручную?
AУмножать на 2 и собирать целые части
BДелить на 2, собирая остатки снизу вверх
CВычитать степени тройки
DСкладывать все цифры