Позиционные системы счисления и перевод между основаниями

Откуда берётся «вес» цифры и почему двоичная система — родной язык компьютера.

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

Зачем это нужно

Вы каждый день пишете числа и не задумываетесь, что 25 и 52 — это одни и те же цифры, но разные числа. Разница в позиции. В старшей школе и на ЕГЭ системы счисления — это фундамент: без них не понять, как компьютер хранит числа, как работают цвета в вебе (#FF8800), маски подсети и логические схемы. Хорошая новость: за внешней «магией» стоит одна простая идея, которую вы освоите за этот урок раз и навсегда.

Интуиция: число как сумма «весов»

В привычной десятичной системе основание равно 10, а цифры — от 0 до 9. Число — это сумма цифр, умноженных на степени основания. Возьмём 2025:

2025 = 2·10³ + 0·10² + 2·10¹ + 5·10⁰
     = 2000 +    0  +   20  +   5

Самая правая цифра стоит в разряде единиц (10⁰ = 1), левее — десятки (10¹), сотни (10²) и так далее. Поменяйте систему — поменяется только основание. В двоичной системе основание 2, цифры лишь 0 и 1, а веса разрядов — степени двойки: 1, 2, 4, 8, 16, 32…

Почему компьютеру удобна именно двойка? Потому что внутри он оперирует двумя состояниями: есть напряжение или нет, ток течёт или не течёт. Два устойчивых состояния надёжнее, чем десять «оттенков», которые легко спутать из-за помех. Поэтому вся электроника считает в двоичной системе, а человеку показывает результат в десятичной.

Почему выбор основания — это выбор компромисса

Стоит на секунду задуматься: десятичная система не «естественнее» других, мы привыкли к ней лишь потому, что у человека десять пальцев. Существовали и другие системы: вавилоняне считали в шестидесятеричной (её следы — в 60 минутах и 360 градусах), у некоторых народов была двенадцатеричная (отсюда дюжина). С точки зрения математики все позиционные системы равноправны — любое число записывается в любой из них, и арифметика подчиняется одним и тем же законам. Меняется лишь основание, то есть «шаг», с которым мы переходим к следующему разряду.

У основания есть содержательный смысл-компромисс. Чем оно меньше, тем проще «алфавит» цифр (в двоичной их всего две), но тем длиннее запись числа. Чем оно больше, тем короче запись, но тем больше разных символов нужно различать. Двоичная система — крайность в сторону простоты алфавита: всего два знака, идеально ложащихся на «включено/выключено». Шестнадцатеричная — разумная середина для человека: запись вчетверо короче двоичной, а перевод в неё тривиален. Понимая этот компромисс, вы перестаёте воспринимать системы счисления как набор разрозненных правил и видите за ними единую идею.

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

Это самый простой переход: распишите число как сумму «цифра × основание в степени разряда». Переведём двоичное 101101 в десятичное. Нумеруем разряды справа налево с нуля:

1·2⁵ + 0·2⁴ + 1·2³ + 1·2² + 0·2¹ + 1·2⁰
= 32 + 0 + 8 + 4 + 0 + 1 = 45

Python умеет это «из коробки» — функция int(строка, основание) читает запись числа в заданной системе и возвращает обычное целое:

# int(строка, основание) переводит запись в десятичное число
print(int("101101", 2))   # двоичное
print(int("755", 8))      # восьмеричное
print(int("1F", 16))      # шестнадцатеричное
print(int("2025", 10))    # десятичное (для проверки)

Вывод:

45
493
31
2025

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

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

45 : 2 = 22, остаток 1   ←
22 : 2 = 11, остаток 0
11 : 2 =  5, остаток 1
 5 : 2 =  2, остаток 1
 2 : 2 =  1, остаток 0
 1 : 2 =  0, остаток 1   → читаем снизу вверх: 101101

Реализуем алгоритм сами, без встроенных функций — так понятнее, что внутри. Заодно проверим себя через стандартные bin, oct, hex (они добавляют префиксы 0b, 0o, 0x):

def to_base(n, base):
    if n == 0:
        return "0"
    digits = "0123456789ABCDEF"
    res = ""
    while n > 0:
        res = digits[n % base] + res   # приписываем остаток слева
        n //= base                      # целочисленное деление
    return res

print(to_base(45, 2))    # вручную
print(bin(45))           # встроенная проверка
print(to_base(493, 8))
print(to_base(255, 16))

Вывод:

101101
0b101101
755
FF

Магия троек и четвёрок: 8 и 16 системы

Почему программисты так любят восьмеричную и шестнадцатеричную системы? Потому что 8 = 2³, а 16 = 2⁴ — это степени двойки. Значит, перевод между двоичной и ними не требует деления вообще: достаточно резать двоичную запись на группы. Для восьмеричной — по 3 бита справа, для шестнадцатеричной — по 4 бита, и каждую группу заменить одной цифрой.

Двоичное:  1010 1111
По 4 бита: 1010=A  1111=F  →  AF (шестнадцатеричное)
По 3 бита: 010 101 111 → 2 5 7 → 257 (восьмеричное)

Поэтому один байт (8 бит) — это ровно две шестнадцатеричные цифры. Цвет #FF8800 — это три байта: красный 255, зелёный 136, синий 0. Проверим эту «нарезку» на Python:

b = "10101111"
hex_val = format(int(b, 2), "X")   # X — заглавные hex-цифры
print("Двоичное", b, "= шестнадцатеричное", hex_val)

# Разбор цвета #FF8800 на компоненты R, G, B
color = "FF8800"
r = int(color[0:2], 16)
g = int(color[2:4], 16)
b2 = int(color[4:6], 16)
print("R =", r, " G =", g, " B =", b2)

Вывод:

Двоичное 10101111 = шестнадцатеричное AF
R = 255  G = 136  B = 0

Попробуй сам

Запустите код и поэкспериментируйте: поменяйте число и основания, проверьте, что прямой и обратный перевод дают исходное значение. Это лучший способ убедиться, что вы всё поняли.

def to_base(n, base):
    if n == 0:
        return "0"
    digits = "0123456789ABCDEF"
    res = ""
    while n > 0:
        res = digits[n % base] + res
        n //= base
    return res

x = 100
for base in (2, 8, 16):
    s = to_base(x, base)
    back = int(s, base)          # переводим обратно
    print(f"{x} в системе {base:>2} = {s:>8}, обратно = {back}")

Вывод:

100 в системе  2 = 1100100, обратно = 100
100 в системе  8 =     144, обратно = 100
100 в системе 16 =      64, обратно = 100

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

  • Путают направление чтения остатков. При делении остатки читаются снизу вверх (от последнего к первому), а не в порядке получения.
  • Забывают про разряд 0. Самый правый разряд — это основание в степени 0, то есть всегда «×1», а не «×основание».
  • Считают, что A, B, C — это переменные. В шестнадцатеричной системе это цифры: A=10, B=11, …, F=15.
  • Группируют биты не с того конца. Резать на тройки/четвёрки нужно строго справа; недостающие биты слева дополняют нулями.

Итоги

  • В позиционной системе значение цифры = цифра × (основание)^(номер разряда), нумерация разрядов с нуля справа.
  • В десятичную переводят суммированием весов; из десятичной — делением с остатком (читать снизу вверх).
  • 8 и 16 системы — это «упакованная» двоичная: группы по 3 и по 4 бита соответственно.
  • Python: int(s, base) читает, bin/oct/hex/format записывают в нужной системе.
Проверьте себя
1. Чему равно двоичное число 11010 в десятичной системе?
A18
B26
C52
D13
2. Почему перевод между двоичной и шестнадцатеричной системами не требует деления?
AПотому что 16 — чётное число
BПотому что 16 = 2⁴, и каждые 4 бита кодируют ровно одну hex-цифру
CПотому что в hex всего 16 цифр
DЭто случайное совпадение
3. Что вернёт int("1F", 16) в Python?
A115
B31
C16
DОшибку: F не цифра
Поддержать проект