Двоичная арифметика и системы счисления в программировании
Теория систем счисления оживает, когда начинаешь применять её в коде. В этой статье посчитаем в двоичной арифметике вручную, а затем посмотрим, какие инструменты для работы с системами счисления встроены в Python и JavaScript.
Двоичное сложение
Правил всего четыре, и три из них очевидны:
| Пример | Результат |
|---|---|
| 0 + 0 | 0 |
| 0 + 1 | 1 |
| 1 + 0 | 1 |
| 1 + 1 | 10 (ноль пишем, единицу переносим) |
Сложим столбиком 1011₂ (11) и 110₂ (6). Идём справа налево, как в обычном сложении:
| Разряд | 8 | 4 | 2 | 1 |
|---|---|---|---|---|
| Перенос | 1 | 1 | — | — |
| Первое число | 1 | 0 | 1 | 1 |
| Второе число | 0 | 1 | 1 | 0 |
| Сумма | 0 | 0 | 0 | 1 |
В разряде двоек 1 + 1 = 10: пишем 0, переносим 1. В разряде четвёрок 0 + 1 + перенос = 10: снова пишем 0 и переносим. В итоге сумма выходит за четыре разряда: 10001₂ = 17₁₀. Проверка: 11 + 6 = 17.
Переполнение — не учебная страшилка. Если результат не помещается в отведённое число бит, старший разряд теряется. Знаменитые баги с «отрицательными» счётчиками очков в играх — именно отсюда.
Умножение и сдвиги
Умножение на 2 в двоичной системе — это приписывание нуля справа, точно как умножение на 10 в десятичной: 101₂ × 2 = 1010₂ (5 × 2 = 10). Деление на 2 — отбрасывание последней цифры.
В программировании эти операции называются битовыми сдвигами: x << 1 сдвигает биты влево (умножает на 2), x >> 1 — вправо (делит на 2 с округлением вниз).
Системы счисления в Python
Python понимает литералы с префиксами и умеет переводить числа в обе стороны:
x = 0b1101 # двоичный литерал
y = 0o755 # восьмеричный
z = 0xFF # шестнадцатеричный
print(x, y, z) # 13 493 255
# Из числа — в строку с записью в нужной системе
print(bin(13)) # '0b1101'
print(oct(13)) # '0o15'
print(hex(255)) # '0xff'
Обратный перевод — функция int() со вторым аргументом, основанием системы:
print(int('1101', 2)) # 13
print(int('ff', 16)) # 255
print(int('0xff', 16)) # 255 — префикс не мешает
print(int('zz', 36)) # 1295 — основание может быть до 36
# Срезом убираем префикс '0b', если нужна «чистая» запись
print(bin(13)[2:]) # '1101'
# Или форматируем сразу без префикса и с ведущими нулями
print(format(13, '08b')) # '00001101'
Битовые операции выглядят так:
a = 0b1100
b = 0b1010
print(bin(a & b)) # 0b1000 — И: единицы там, где они у обоих
print(bin(a | b)) # 0b1110 — ИЛИ: хотя бы у одного
print(bin(a ^ b)) # 0b110 — исключающее ИЛИ
print(bin(a << 2)) # 0b110000 — сдвиг влево, умножение на 4
Системы счисления в JavaScript
В JavaScript за перевод отвечают parseInt() и метод toString() с аргументом-основанием:
// Из строки в число: parseInt(строка, основание)
console.log(parseInt('1101', 2)); // 13
console.log(parseInt('ff', 16)); // 255
console.log(parseInt('755', 8)); // 493
// Из числа в строку: число.toString(основание)
console.log((13).toString(2)); // '1101'
console.log((255).toString(16)); // 'ff'
console.log((493).toString(8)); // '755'
// Литералы с префиксами тоже работают
console.log(0b1101, 0o755, 0xff); // 13 493 255
Всегда передавайте основание в
parseInt()явно. ВызовparseInt('0x10')вернёт 16, потому что строка с префиксом0xтрактуется как шестнадцатеричная — и это может стать неожиданностью.
Дополнить строку нулями слева помогает padStart():
const byte = (13).toString(2).padStart(8, '0');
console.log(byte); // '00001101'
Мини-практика: цвет из RGB в hex
Соберём шестнадцатеричный код цвета из трёх каналов — типичная задача фронтендера:
function rgbToHex(r, g, b) {
const toHex = (n) => n.toString(16).padStart(2, '0');
return '#' + toHex(r) + toHex(g) + toHex(b);
}
console.log(rgbToHex(255, 102, 0)); // '#ff6600'
А на Python — обратную: разберём hex-цвет на каналы.
def hex_to_rgb(color):
color = color.lstrip('#')
return tuple(int(color[i:i + 2], 16) for i in (0, 2, 4))
print(hex_to_rgb('#ff6600')) # (255, 102, 0)
Что мы узнали
- Двоичное сложение держится на одном правиле переноса: 1 + 1 = 10.
- Умножение и деление на 2 — это сдвиги разрядов:
<<и>>. - В Python переводами занимаются
bin(),oct(),hex()иint(строка, основание). - В JavaScript —
parseInt(строка, основание)ичисло.toString(основание). - Оба языка понимают литералы
0b…,0o…и0x…. - Знание систем счисления — это практика: hex-цвета, права доступа, битовые флаги и маски.