Системы счисления: двоичная, восьмеричная, шестнадцатеричная и переводы между ними

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

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

Ниже разберем, чем позиционная система отличается от непозиционной, как устроены двоичная, восьмеричная и шестнадцатеричная системы и почему именно они прижились в вычислительной технике. Главное — научимся переводить числа из одной системы в другую руками и на Python. Все примеры кода в статье запущены, вывод под ними реальный.

Позиционные и непозиционные системы

Системы счисления делятся на два класса по тому, влияет ли позиция цифры на ее вклад в число.

Непозиционная система — значение цифры не зависит от ее места в записи. Пример — римская: в записи XXX каждый X всегда означает 10, а число получается сложением (XXX = 30). Такие системы наглядны для мелких чисел, но неудобны для арифметики и записи больших величин.

Позиционная система — вклад цифры зависит от ее позиции (разряда). В числе 555 три одинаковые цифры означают разное: пять сотен, пять десятков и пять единиц. Именно позиционные системы используют в математике и технике, потому что в них просто считать по разрядам.

Дальше речь только о позиционных системах — к ним относятся десятичная, двоичная, восьмеричная и шестнадцатеричная.

Основание и разряд: как устроена позиционная запись

У любой позиционной системы есть основание — количество разных цифр в ней. В десятичной основание 10 и цифры 0-9, в двоичной основание 2 и цифры 0 и 1.

Значение числа — это сумма цифр, умноженных на основание в степени номера разряда. Разряды нумеруют справа налево, начиная с нуля. Для десятичного 463 это читается так:

463 = 4*10^2 + 6*10^1 + 3*10^0 = 400 + 60 + 3

Тот же принцип работает в любой системе — меняется только основание. Двоичное число 1011 раскрывается по степеням двойки:

1011 = 1*2^3 + 0*2^2 + 1*2^1 + 1*2^0 = 8 + 0 + 2 + 1 = 11

Эта формула — разложение по степеням основания — и есть ключ к переводу любого числа в десятичную систему.

Четыре системы, которые встречаются в IT

В программировании постоянно попадаются четыре системы. Ниже — что это за системы и зачем каждая нужна.

Система Основание Цифры Где встречается
Двоичная 2 0, 1 внутреннее представление данных, биты и флаги, побитовые операции
Восьмеричная 8 0-7 права доступа к файлам в Unix (chmod 755)
Десятичная 10 0-9 обычная запись чисел, ввод и вывод для человека
Шестнадцатеричная 16 0-9, A-F цвета (#ff8800), адреса памяти, байты, хеши

Компьютер хранит и обрабатывает данные в двоичном виде: у транзистора удобно различать два устойчивых состояния, их и кодируют как 0 и 1. Но длинные цепочки битов человеку читать тяжело, поэтому их сокращают.

Здесь помогают восьмеричная и шестнадцатеричная системы: их основания — степени двойки (8 = 2^3, 16 = 2^4), поэтому группы битов переводятся в их цифры без арифметики. Одна шестнадцатеричная цифра заменяет ровно четыре бита — об этом ниже отдельный раздел.

В шестнадцатеричной не хватает обычных цифр, поэтому значения 10-15 записывают буквами A-F: A = 10, B = 11, C = 12, D = 13, E = 14, F = 15. Регистр букв роли не играет.

Как перевести число в десятичную систему

Перевод в десятичную — это прямое применение разложения по степеням основания. Берем каждую цифру, умножаем на основание в степени ее разряда и складываем.

Разберем шестнадцатеричное 2BC. Разряды справа налево: C (разряд 0), B (разряд 1), 2 (разряд 2). Подставляем значения букв (C = 12, B = 11):

2BC = 2*16^2 + 11*16^1 + 12*16^0 = 512 + 176 + 12 = 700

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

def to_decimal(text, base):
    digits = "0123456789ABCDEF"
    value = 0
    for ch in text.upper():
        value = value * base + digits.index(ch)   # сдвигаем разряд и добавляем цифру
    return value

print("10011100 (2) ->", to_decimal("10011100", 2))
print("234 (8) ->", to_decimal("234", 8))
print("2BC (16) ->", to_decimal("2BC", 16))

Вывод программы:

10011100 (2) -> 156
234 (8) -> 156
2BC (16) -> 700

Первые два числа неспроста дали одинаковый результат: 10011100 в двоичной, 234 в восьмеричной и 156 в десятичной — это одна и та же величина, записанная по-разному.

Как перевести десятичное число в другую систему

Обратный перевод делают делением с остатком. Число многократно делят на основание целочисленно, а остатки от деления и есть цифры новой записи — но их собирают снизу вверх, от последнего к первому.

Переведем 156 в двоичную. Делим на 2, пока не дойдем до нуля, и выписываем остатки:

156 : 2 = 78, остаток 0
 78 : 2 = 39, остаток 0
 39 : 2 = 19, остаток 1
 19 : 2 = 9,  остаток 1
  9 : 2 = 4,  остаток 1
  4 : 2 = 2,  остаток 0
  2 : 2 = 1,  остаток 0
  1 : 2 = 0,  остаток 1

Читаем остатки снизу вверх: 10011100. Тот же алгоритм в коде — для любого основания до 16:

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

for base in (2, 8, 16):
    print(f"156 в основании {base}:", from_decimal(156, base))

Вывод программы:

156 в основании 2: 10011100
156 в основании 8: 234
156 в основании 16: 9C

Оговорка о границе: функция работает с неотрицательными целыми и основаниями до 16 (цифр в строке digits ровно 16). Для оснований больше 16 нужен более длинный алфавит цифр, а для отрицательных чисел — отдельная обработка знака.

Встроенные средства Python

Свои функции полезны, чтобы понять механику, но в реальном коде переводы уже встроены в язык. Python переводит десятичное число в строку другой системы функциями bin, oct, hex, а обратно — функцией int со вторым аргументом-основанием.

n = 156
print("bin:", bin(n))   # префикс 0b - двоичная
print("oct:", oct(n))   # префикс 0o - восьмеричная
print("hex:", hex(n))   # префикс 0x - шестнадцатеричная

# обратно: строка записи + основание -> десятичное число
print(int("10011100", 2))
print(int("234", 8))
print(int("9c", 16))

Вывод программы:

bin: 0b10011100
oct: 0o234
hex: 0x9c
156
156
156

Префиксы 0b, 0o, 0x — это не часть числа, а пометка системы счисления. Их же можно писать прямо в исходном коде: 0b10011100, 0o234 и 0x9c — три способа записать одну константу 156, и Python хранит их одинаково.

Двоичная, восьмеричная и шестнадцатеричная: перевод без деления

Между двоичной и двумя другими системами перевод идет быстрее, чем через десятичную, потому что 8 и 16 — степени двойки. Одна восьмеричная цифра — это ровно 3 бита, одна шестнадцатеричная — ровно 4 бита.

Правило простое: разбиваем двоичную запись на группы (по 4 бита для hex, по 3 для oct), считая справа, и каждую группу заменяем одной цифрой. Если слева группа неполная, дополняем ее нулями. Проверим это кодом:

n = 0b1010111100          # двоичное число, записанное прямо в коде
print("hex:", hex(n))
print("oct:", oct(n))

bits = "1010111100"
pad = (-len(bits)) % 4                 # сколько нулей добить слева до кратности 4
bits4 = "0" * pad + bits
groups = [bits4[i:i+4] for i in range(0, len(bits4), 4)]
hex_digits = "".join("0123456789abcdef"[int(g, 2)] for g in groups)
print("группы по 4 бита:", groups, "->", hex_digits)

Вывод программы:

hex: 0x2bc
oct: 0o1274
группы по 4 бита: ['0010', '1011', '1100'] -> 2bc

Группировка вручную дала тот же результат 2bc, что и встроенный hex. Именно поэтому шестнадцатеричная запись так удобна для байтов: один байт (8 бит) — это всегда ровно две hex-цифры, от 00 до ff.

Выводы

  • Система счисления — это правило записи чисел цифрами; позиционные (значение зависит от разряда) вытеснили непозиционные в математике и технике.
  • В IT главные системы — двоичная (данные в компьютере), шестнадцатеричная и восьмеричная (компактная запись битов), десятичная (ввод-вывод для человека).
  • Перевод в десятичную — разложение по степеням основания; из десятичной — деление с остатком, остатки читаются снизу вверх.
  • Восьмеричная и шестнадцатеричная переводятся из двоичной группами по 3 и 4 бита без арифметики, потому что 8 и 16 — степени двойки.
  • В Python переводы встроены: bin, oct, hex из числа в строку и int(s, base) обратно.

Где применяется и что учить дальше

Системы счисления — это фундамент, на который опирается почти все в программировании: побитовые операции и маски, цвета в вебе (#ff8800), права доступа chmod 755, чтение дампов памяти и адресов, разбор сетевых пакетов и хешей. Понимание переводов помогает не заучивать, а видеть, что за компактной hex-записью стоят обычные биты.

Освойте тему на практике

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

Смежные темы: Двоичная система счисления.

FAQ

Почему в компьютерах используют именно двоичную систему? Электронной схеме проще надежно различать два состояния (есть сигнал или нет), чем десять уровней. Два состояния кодируют как 0 и 1, поэтому вся обработка данных идет в двоичном виде.

Зачем нужна шестнадцатеричная, если есть двоичная? Двоичная запись длинная и в ней легко ошибиться при чтении. Шестнадцатеричная короче в четыре раза и переводится в биты без вычислений, поэтому ей удобно записывать байты, цвета и адреса.

Что означают буквы A-F в шестнадцатеричном числе? Это цифры от 10 до 15, которым не хватило обычных знаков: A = 10, B = 11, C = 12, D = 13, E = 14, F = 15. Регистр не важен, 9c и 9C — одно и то же.

OTUS Журнал