Двоичная арифметика и логика разрядов
Как процессор складывает и умножает, оперируя только нулями и единицами.
Перенос разряда — единица, которая «переезжает» в старший разряд, когда сумма в текущем разряде не помещается в основание системы.
Зачем это нужно
Когда вы пишете a + b в программе, процессор не «знает» десятичных чисел — он складывает двоичные разряды по простым правилам. Понимание двоичной арифметики объясняет вещи, которые иначе кажутся колдовством: почему сдвиг влево умножает на 2, как одной операцией проверить чётность, откуда берётся переполнение. На ЕГЭ это задания на двоичную арифметику и побитовые операции, а в реальном коде — быстрые приёмы и работа с флагами.
Есть и более глубокая причина изучать эту тему. Современная электроника устроена так, что простые операции над битами — сложение, сдвиг, логическое И — выполняются за один такт процессора, тогда как, например, деление или взятие остатка стоят в разы дороже. Поэтому опытный программист, увидев умножение на степень двойки, мысленно заменяет его сдвигом, а проверку x % 2 — выделением младшего бита. Это не «микрооптимизация ради красоты»: на больших объёмах данных или во встроенных системах с маломощным процессором такие приёмы решают, уложится программа в отведённое время или нет. Двоичная арифметика — это язык, на котором «думает» железо, и умение читать на нём даёт интуицию о стоимости операций.
Сложение «столбиком» в двоичной системе
Правила сложения битов проще таблицы умножения: 0+0=0, 0+1=1, 1+0=1, а 1+1=10 — то есть 0 в текущем разряде и перенос 1 в старший. Сложим 1011 (11) и 0110 (6):
1 0 1 1 (11)
+ 0 1 1 0 (6)
---------
переносы: 1 1
= 1 0 0 0 1 (17)
Разберём справа налево: 1+0=1; 1+1=10 (пишем 0, переносим 1); 0+1+перенос1=10 (пишем 0, переносим 1); 1+0+перенос1=10 (пишем 0, переносим 1); этот последний перенос даёт старший разряд 1. Итог 10001 = 17, что совпадает с 11+6. Проверим, что Python считает так же, просто показав результат в двоичном виде:
a, b = 11, 6
print("a =", bin(a), " b =", bin(b))
print("сумма =", bin(a + b), "=", a + b)
Вывод:
a = 0b1011 b = 0b110 сумма = 0b10001 = 17
Умножение и сдвиги: почему «×2» — это сдвиг влево
В десятичной системе приписать ноль справа — значит умножить на 10. В двоичной приписать ноль справа — умножить на 2, ведь основание теперь 2. Это и есть сдвиг влево (оператор <<). Сдвиг вправо (>>) — деление на 2 с отбрасыванием остатка.
x = 5 # 101
print(bin(x), "=", x)
print(bin(x << 1), "=", x << 1) # сдвиг влево на 1 → ×2
print(bin(x << 3), "=", x << 3) # сдвиг влево на 3 → ×8
print(bin(40 >> 2), "=", 40 >> 2) # сдвиг вправо на 2 → //4
Вывод:
0b101 = 5 0b1010 = 10 0b101000 = 40 0b1010 = 10
Побитовые операции: И, ИЛИ, исключающее ИЛИ
Процессор умеет применять логику к каждому разряду пары чисел независимо. Это побитовые операции, и они невероятно полезны:
| Операция | Знак | Правило для бита | Зачем |
| И (AND) | & | 1 только если оба бита 1 | маскировать, проверять биты |
| ИЛИ (OR) | | | 1, если хотя бы один бит 1 | устанавливать биты |
| исключающее ИЛИ (XOR) | ^ | 1, если биты разные | переключать биты, простое шифрование |
Классический приём: чётность числа = его младший бит. Если последний бит 0 — число чётное, если 1 — нечётное. Проверяется через x & 1:
for x in (10, 7, 0, 255):
bit = x & 1 # выделяем младший бит
kind = "нечётное" if bit else "чётное"
print(f"{x:>3} = {bin(x):>10} младший бит = {bit} → {kind}")
Вывод:
10 = 0b1010 младший бит = 0 → чётное 7 = 0b111 младший бит = 1 → нечётное 0 = 0b0 младший бит = 0 → чётное 255 = 0b11111111 младший бит = 1 → нечётное
XOR и его удивительное свойство
Исключающее ИЛИ обладает свойством «обратимости»: если применить XOR с одним и тем же ключом дважды, число вернётся к исходному. Именно поэтому XOR лежит в основе простейших шифров и контрольных сумм. Покажем это:
data = 2025
key = 137
encrypted = data ^ key # «зашифровали»
decrypted = encrypted ^ key # тем же ключом «расшифровали»
print("исходное :", data)
print("после XOR:", encrypted)
print("вернулось:", decrypted)
print("совпало? ", data == decrypted)
Вывод:
исходное : 2025 после XOR: 1888 вернулось: 2025 совпало? True
Попробуй сам
Соберём всё вместе: сложим два числа в двоичном виде, проверим чётность суммы одним битом и быстро умножим на 4 сдвигом. Меняйте числа и сверяйте с тем, что ожидаете.
a, b = 13, 19
s = a + b
print(f"{a} + {b} = {s} ({bin(a)} + {bin(b)} = {bin(s)})")
print("сумма чётная?", (s & 1) == 0)
print(f"{s} * 4 = {s << 2} (сдвиг влево на 2)")
Вывод:
13 + 19 = 32 (0b1101 + 0b10011 = 0b100000) сумма чётная? True 32 * 4 = 128 (сдвиг влево на 2)
Частые ошибки
- Забывают перенос. 1+1 в двоичной — это не «2», а 0 с переносом 1 в старший разряд.
- Путают
&и&&. В Python логическое И — этоand, а&— побитовое. У них разный смысл и приоритет. - Сдвиг вправо ≠ деление с округлением.
>>отбрасывает младшие биты, то есть это деление на 2 с округлением вниз. - Приоритет операторов.
x & 1 == 0сначала вычислит1 == 0! Нужны скобки:(x & 1) == 0.
Итоги
- Двоичное сложение — сложение разрядов с переносом 1, когда получается «10».
- Сдвиг влево
<<= умножение на степень двойки, сдвиг вправо>>= целочисленное деление. - Побитовые
&,|,^работают с каждым битом отдельно: маскирование, установка, переключение. - Младший бит
x & 1даёт чётность; XOR обратим и потому годится для простого шифрования.