«Зачем мне это? — спросите вы. — Я же хочу нейронки обучать, а не нули с единицами складывать!» Поверьте, понимание того, как компьютер «думает» на самом низком уровне, дает огромное преимущество. Это как знать анатомию, будучи врачом. Вы не всегда оперируете на уровне отдельных клеток, но знание их работы помогает понять весь организм.
Двоичная система — это язык, на котором говорит все «железо», от вашего смартфона до суперкомпьютеров, обучающих сложные модели. Плюс некоторые операции и трюки в программировании и даже в ML напрямую используют битовые операции для эффективности.
Что такое двоичная арифметика и зачем она нужна
Представьте, что у вас есть только два пальца для счета. Неудобно? А вот компьютеры только так и умеют! Двоичная система счисления — это система, где есть всего две цифры: 0 и 1. В отличие от нашей привычной десятичной системы (где цифр десять: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9), здесь все числа записываются комбинациями нулей и единиц.
Почему именно так? Все просто: электронные компоненты компьютера (транзисторы) могут находиться в двух устойчивых состояниях: есть ток (условно 1) или нет тока (условно 0). Как выключатель света: либо включен, либо выключен. Третьего не дано. Поэтому двоичная система — это естественный язык для цифровой техники.
Двоичная арифметика — это правила выполнения математических операций (сложение, вычитание, умножение, деление) над числами, записанными в двоичной системе.
Зачем она нужна (кроме того, что это язык компьютеров)?
- Понимание хранения данных. Любая информация в компьютере (числа, текст, картинки, звук) в конечном итоге хранится как последовательность нулей и единиц.
- Оптимизация. Некоторые вычисления (особенно связанные с целыми числами, флагами состояний) можно делать гораздо быстрее с помощью битовых операций, чем стандартными арифметическими. Иногда это критично в высоконагруженных системах или при работе с железом.
- Логические операции. Основа программирования и цифровой логики. Условия if, циклы while — все это внутри процессора сводится к логическим операциям над битами.
- Машинное обучение. Хотя в ML мы чаще работаем с числами с плавающей запятой, понимание двоичной системы полезно при работе с категориальными признаками (например, one-hot encoding, где используются 0 и 1), битовыми масками или при изучении низкоуровневых аспектов работы алгоритмов и библиотек.
Сложение двоичных чисел
Правила сложения в двоичной системе до смешного просты:
- 0 + 0 = 0
- 0 + 1 = 1
- 1 + 0 = 1
- 1 + 1 = 0 и 1 переносится в следующий (старший) разряд. Вот это ключевой момент!
Это очень похоже на сложение в столбик в десятичной системе. Когда у нас 9 + 1 = 10, мы пишем 0 и 1 переносим в следующий разряд. Здесь то же самое, только «переполнение» происходит уже при сумме 2 (которая в двоичной системе записывается как 10).
Пример: Сложим 5 и 3. Сначала переведем их в двоичную систему:
510 = 101₂ (12² + 02¹ + 12⁰ = 4 + 0 + 1 = 5)
310 = 11₂ (12¹ + 1*2⁰ = 2 + 1 = 3)
Теперь сложим в столбик:
¹ (перенос из предыдущего разряда)
1 0 1 (это 5)
+ 1 1 (это 3)
——-
1 0 0 0 (это 8)
Разберем по шагам справа налево:
- Правый разряд: 1 + 1 = 0, пишем 0 и 1 переносим влево.
- Средний разряд: 0 + 1 = 1. Но у нас есть перенос 1 из предыдущего разряда! Значит 1 + 1 = 0, пишем 0 и 1 переносим влево.
- Левый разряд: 1. Но у нас есть перенос 1! Значит 1 + 1 = 0, пишем 0 и 1 переносим влево (в новый разряд).
- Новый разряд: Сюда пришел перенос 1. Пишем 1.
Получили 1000₂. Проверим: 1*2³ + 0*2² + 0*2¹ + 0*2⁰ = 8 + 0 + 0 + 0 = 8. Все верно! 5 + 3 = 8.
Вычитание двоичных чисел
Правила вычитания тоже простые, но есть нюанс с «заемом»:
- 0 — 0 = 0
- 1 — 0 = 1
- 1 — 1 = 0
- 0 — 1 = 1, но нужно «занять» 1 из старшего разряда.
Опять же, аналогия с десятичной системой: когда нам нужно из 0 вычесть 5 (например, в числе 30 — 5), мы «занимаем» десяток у тройки, получаем 10 — 5 = 5. В двоичной системе мы «занимаем» двойку (которая выглядит как 10₂).
Пример: Давайте вычтем 3 из 5.
510 = 101₂
310 = 11₂
1 0 1 (это 5)
— 1 1 (это 3)
———-
1 0 (это 2)
Разберем по шагам справа налево:
- Правый разряд: 1 — 1 = 0. Пишем 0.
- Средний разряд: 0 — 1. Не хватает! Нужно занять у старшего разряда (слева). Там стоит 1. Занимаем ее (там остается 0). Наша 0 превращается в 10₂ (то есть в 2 в десятичной). Теперь вычитаем: 10₂ — 1₂ = 1₂. Пишем 1.
- Левый разряд: Там был 1, но мы его заняли, остался 0. 0 — 0 (под ним ничего нет) = 0. Ведущий ноль можно не писать.
Получили 10₂. Проверим: 1*2¹ + 0*2⁰ = 2 + 0 = 2. Все верно! 5 — 3 = 2.
На практике компьютеры часто используют трюк под названием «дополнительный код» для вычитания, сводя его к сложению с отрицательным числом, но для понимания основ достаточно и этого метода.
Умножение двоичных чисел
Умножение в двоичной системе — это просто сказка! Почему? Потому что умножать приходится только на 0 или 1.
- Любое число * 0 = 0.
- Любое число * 1 = Это же число.
Процесс похож на умножение столбиком в десятичной системе:
- Берем первый множитель.
- Умножаем его поочередно на каждую цифру второго множителя (справа налево).
- Если цифра второго множителя 0 — пишем ряд нулей.
- Если цифра 1 — просто переписываем первый множитель.
- Каждый следующий результат умножения сдвигаем на один разряд влево.
- Складываем все полученные ряды по правилам двоичного сложения.
Пример: Умножим 5 на 3.
510 = 101₂
310 = 11₂
1 0 1 (это 5)
x 1 1 (это 3)
——-
1 0 1 (101 * 1)
+ 1 0 1 (101 * 1, сдвинутое влево)
——-
1 1 1 1 (результат сложения)
Более подробно рассмотрим сложение промежуточных результатов:
¹ ¹ (переносы)
1 0 1
+ 1 0 1
——-
1 1 1 1
- Правый разряд: 1.
- Второй справа: 0 + 1 = 1.
- Третий справа: 1 + 0 = 1.
- Четвертый справа: 1.
Получили 1111₂. Проверим: 1*2³ + 1*2² + 1*2¹ + 1*2⁰ = 8 + 4 + 2 + 1 = 15. Все верно! 5 * 3 = 15.
Деление двоичных чисел
Деление в двоичной системе тоже напоминает деление столбиком (или «уголком»), которое мы учили в школе. Оно даже проще, потому что на каждом шаге нам нужно решить только один вопрос: помещается ли делитель в текущую часть делимого (1 раз) или нет (0 раз)?
- Берем из делимого столько старших разрядов, сколько в делителе (или на один больше, если этого мало).
- Сравниваем эту часть делимого с делителем.
- Если делитель меньше или равен этой части:
- Пишем 1 в частное.
- Вычитаем делитель из этой части делимого (по правилам двоичного вычитания).
- К остатку «сносим» следующую цифру делимого.
- Если делитель больше этой части:
- Пишем 0 в частное.
- «Сносим» следующую цифру делимого.
- Повторяем шаги 2–4, пока не закончатся цифры в делимом.
Пример: Разделим 7 на 3.
710 = 111₂
310 = 11₂
1 1 1 | 11 (делимое = 7)
|——————-
|1 0 (частное = 2)
— 1 1 (11 <= 11, пишем 1 в частное, вычитаем 11)
——
0 0 1 (остаток 0, сносим 1. Получаем 001 или просто 1)
— 0 (11 > 1, пишем 0 в частное)
—
1 (остаток = 1)
Разберем:
- Берем первые две цифры делимого: 11. Сравниваем с делителем 11. Они равны.
- Пишем 1 в частное.
- Вычитаем 11 из 11, получаем 0.
- Сносим следующую цифру делимого 1. Получаем 01 (или просто 1).
- Сравниваем 1 с делителем 11. 1 меньше 11.
- Пишем 0 в частное.
- Цифры в делимом закончились.
Результат: частное 10₂ (это 2) и остаток 1₂ (это 1). Проверяем: 7 / 3 = 2 с остатком 1. Все верно!
Логические операции над двоичными числами
Кроме арифметических операций, с двоичными числами (а точнее, с отдельными битами) можно выполнять логические операции. Они работают побитово, то есть операция применяется к каждой паре битов на одинаковых позициях.
- И (AND, &): Результат 1, только если оба бита равны 1.
- 0 & 0 = 0
- 0 & 1 = 0
- 1 & 0 = 0
- 1 & 1 = 1
- Аналогия из жизни: Чтобы получить права, нужно сдать теорию И практику. Невыполнение хотя бы одного условия дает отрицательный результат.
- Пример: 1010₂ & 1100₂
1 0 1 0
& 1 1 0 0
———
1 0 0 0
- ИЛИ (OR, |): Результат 1, если хотя бы один из битов равен 1.
- 0 | 0 = 0
- 0 | 1 = 1
- 1 | 0 = 1
- 1 | 1 = 1
- Аналогия: В магазине акция: скидка, если у вас есть карта лояльности ИЛИ вы пенсионер. Достаточно выполнения одного условия.
- Пример: 1010₂ | 1100₂
1 0 1 0
| 1 1 0 0
———
1 1 1 0
- Исключающее ИЛИ (XOR, ^): Результат 1, только если биты разные.
- 0 ^ 0 = 0
- 0 ^ 1 = 1
- 1 ^ 0 = 1
- 1 ^ 1 = 0
- Аналогия: Переключатель света в проходной комнате. Если оба выключателя в одинаковом положении (оба вкл или оба выкл), свет не горит. Если их положения разные – свет горит. Результат зависит от различия состояний.
- Пример: 1010₂ ^ 1100₂
1 0 1 0
^ 1 1 0 0
———
0 1 1 0
- НЕ (NOT, ~): Инвертирует каждый бит (0 становится 1, 1 становится 0). Это унарная операция (применяется к одному числу).
- ~ 0 = 1
- ~ 1 = 0
- Аналогия: Простое отрицание. Если было «да», стало «нет», и наоборот.
- Пример: ~ 1010₂ (Если считать, что число 4-битное.)
~ 1 0 1 0
———
0 1 0 1
- Важно: В программировании операция ~ часто работает с учетом знака и представления отрицательных чисел (дополнительный код), поэтому результат может выглядеть не так очевидно, как простая инверсия бит.
Эти логические операции невероятно важны в программировании для работы с флагами, масками, шифрованием и многими другими задачами.
Пример вычисления операций над двоичными числами с использованием кода на Python
Давайте посмотрим, как легко выполнять эти операции в Python. Он отлично умеет работать с двоичными числами и битовыми операциями.
# Зададим два числа в десятичной системе a = 10 # В двоичной это 1010 b = 6 # В двоичной это 0110 (дополним нулем для одинаковой длины) print(f"Число a = {a}, в двоичной системе: {bin(a)}") print(f"Число b = {b}, в двоичной системе: {bin(b)}") print("-" * 30) # Сложение sum_result = a + b print(f"Сложение: a + b = {sum_result}") print(f" {a} ({bin(a)}) + {b} ({bin(b)}) = {sum_result} ({bin(sum_result)})") print("-" * 30) # Вычитание sub_result = a - b print(f"Вычитание: a - b = {sub_result}") print(f" {a} ({bin(a)}) - {b} ({bin(b)}) = {sub_result} ({bin(sub_result)})") print("-" * 30) # Умножение mul_result = a * b print(f"Умножение: a * b = {mul_result}") print(f" {a} ({bin(a)}) * {b} ({bin(b)}) = {mul_result} ({bin(mul_result)})") print("-" * 30) # Целочисленное деление div_result = a // b remainder = a % b print(f"Деление: a // b = {div_result}, остаток: {remainder}") print(f" {a} ({bin(a)}) // {b} ({bin(b)}) = {div_result} ({bin(div_result)})") print(f" Остаток: {remainder} ({bin(remainder)})") print("-" * 30) # Логические операции print("Логические операции:") # AND and_result = a & b print(f"AND (&): a & b = {and_result}") print(f" {bin(a)} & {bin(b)} = {bin(and_result)}") # OR or_result = a | b print(f"OR (|): a | b = {or_result}") print(f" {bin(a)} | {bin(b)} = {bin(or_result)}") # XOR xor_result = a ^ b print(f"XOR (^): a ^ b = {xor_result}") print(f" {bin(a)} ^ {bin(b)} = {bin(xor_result)}") # NOT (Обратите внимание, как Python обрабатывает NOT для целых чисел) not_a_result = ~a print(f"NOT (~): ~a = {not_a_result}") print(f" ~{bin(a)} = {bin(not_a_result)}") # Результат ~a = -11, потому что Python использует доп. код для отрицательных чисел. # ~1010₂ инвертируется в ...11110101₂, что в доп. коде представляет -11.
Результат вычисления:
Число a = 10, в двоичной системе: 0b1010 Число b = 6, в двоичной системе: 0b110 ------------------------------ Арифметические операции: Сложение: a + b = 16 10 (0b1010) + 6 (0b110) = 16 (0b10000) ------------------------------ Вычитание: a - b = 4 10 (0b1010) - 6 (0b110) = 4 (0b100) ------------------------------ Умножение: a * b = 60 10 (0b1010) * 6 (0b110) = 60 (0b111100) ------------------------------ Деление: a // b = 1, остаток: 4 10 (0b1010) // 6 (0b110) = 1 (0b1) Остаток: 4 (0b100) ------------------------------ Логические операции: AND (&): a & b = 2 0b1010 & 0b110 = 0b10 OR (|): a | b = 14 0b1010 | 0b110 = 0b1110 XOR (^): a ^ b = 12 0b1010 ^ 0b110 = 0b1100 NOT (~): ~ a = -11 ~0b1010 = -0b1011
Функция bin() в Python возвращает строку с двоичным представлением числа, начинающуюся с префикса 0b. Как видите, Python позволяет легко выполнять все основные арифметические и логические операции над числами, не задумываясь об их двоичном представлении, — он делает это за нас. Но теперь вы знаете, что происходит «под капотом»!
Коротко о двоичной арифметике
Итак, друзья, мы с вами сделали первый шаг в понимании двоичной системы — фундаментального языка компьютеров.
- Мы узнали, что двоичная арифметика — это набор правил для операций над числами, состоящими только из 0 и 1.
- Разобрали на примерах сложение, вычитание, умножение и деление в двоичной системе, увидев их сходство с привычными нам операциями в столбик.
- Познакомились с логическими операциями (AND, OR, XOR, NOT), которые работают на уровне отдельных битов и играют ключевую роль в программировании и цифровой логике.
- Увидели, как легко эти операции выполняются в Python.
Даже если вы не будете каждый день вручную складывать двоичные числа, понимание этих принципов помогает увидеть, как работает компьютерная техника и как обрабатываются данные. Это знание может пригодиться при анализе производительности кода, работе с низкоуровневыми библиотеками или просто для расширения вашего кругозора как специалиста в области данных и ML.