Оператор деления по модулю в Python: как работает % и где применяется

Оператор % возвращает остаток от деления одного числа на другое. Если разделить 10 на 3, получим 3 целых и 1 в остатке — именно эту единицу и вычисляет выражение 10 % 3. Несмотря на внешнюю простоту, оператор покрывает десятки практических задач: проверку чётности, циклическую адресацию, перевод секунд в часы и минуты, валидацию данных. В этой статье разберём механику вычисления, подводные камни с отрицательными числами и типом float, а также покажем реальные сценарии использования.
Формула вычисления остатка
Python связывает оператор % с целочисленным делением // через инвариант:
a == (a // b) * b + (a % b)
Выражение a // b возвращает результат деления, округлённый вниз (floor division). Остаток — то, что нужно прибавить к произведению частного на делитель, чтобы получить исходное число. Инвариант работает для любых комбинаций знаков операндов и для типа float. Именно из этой формулы следует поведение % с отрицательными числами.
>>> 17 // 5
3
>>> 17 % 5
2
>>> 3 * 5 + 2
17
Частное равно 3, остаток равен 2. Проверка: 3 × 5 + 2 = 17.
Базовые примеры с целыми числами
Оператор работает с целыми числами (int) и возвращает int:
>>> 10 % 3 → 1
>>> 15 % 5 → 0
>>> 7 % 2 → 1
>>> 100 % 10 → 0
Если делимое меньше делителя, остаток равен самому делимому:
>>> 3 % 7
3
Логика проста: 3 // 7 даёт 0, а 0 × 7 + 3 = 3. Частное нулевое, поэтому всё число уходит в остаток. Если оба операнда одинаковы, остаток всегда нулевой: 5 % 5 = 0.
Проверка делимости
Самое частое применение — проверка, делится ли число нацело. Если x % y == 0, значит x делится на y без остатка. Так определяют чётность:
def is_even(n):
return n % 2 == 0
В книге «Python для всех» Чарльза Северанса сказано: оператор % позволяет узнать, делится ли одно число на другое, проверив, равен ли остаток нулю. Аналогично определяют кратность трём, пяти, любому числу.
Извлечение цифр из числа
Выражение x % 10 возвращает последнюю цифру, x % 100 — две последние:
>>> 12345 % 10 → 5
>>> 12345 % 100 → 45
>>> 12345 % 1000 → 345
Приём применяют при проверке контрольных сумм штрихкодов, при разбиении числа на разряды, при определении последней цифры номера телефона.
Поведение с отрицательными числами
Python отличается от C, C++ и Java: остаток всегда принимает знак делителя (второго операнда). Причина — оператор // округляет к минус бесконечности (floor), а не к нулю.
>>> -7 % 3 → 2
>>> 7 % -3 → -2
>>> -7 % -3 → -1
Разбор первого примера: -7 // 3 даёт -3 (floor от -2.33). Проверяем: -3 × 3 + 2 = -7. Остаток равен 2, а не -1, как было бы в C.
Правило: результат % лежит в диапазоне от 0 до b-1 при положительном b, и от b+1 до 0 при отрицательном b. Если нужен остаток со знаком делимого — math.fmod():
Выражение
Python %
math.fmod()
C/Java %
-7 % 3
2
-1.0
-1
7% -3
-2
1.0
1
-10 % 3
2
-1.0
-1
Оператор % с числами float
Формула та же: a - (a // b) * b:
>>> 7.5 % 2.5 → 0.0
>>> 7.5 % 2.0 → 1.5
>>> 10.3 % 3.1 → 0.9999999999999996
Третий пример демонстрирует ограничение IEEE 754: не все десятичные дроби представимы точно в двоичном формате. Для финансовых расчётов используют модуль decimal.
Функция divmod()
Встроенная функция divmod(a, b) возвращает кортеж из частного и остатка за одну операцию:
>>> divmod(17, 5) → (3, 2)
>>> divmod(-7, 3) → (-3, 2)
>>> divmod(7.5, 2.5) → (3.0, 0.0)
Вместо двух вычислений (a // b и a % b) выполняется одно. Для больших целых чисел divmod() может оказаться быстрее пары отдельных операций. Функция принимает int и float, но не complex — будет TypeError.
Перевод единиц времени
Классическая задача — перевод секунд в часы, минуты и секунды:
total_seconds = 7384
hours, remaining = divmod(total_seconds, 3600)
minutes, seconds = divmod(remaining, 60)
>>> f'{hours}ч {minutes}мин {seconds}с'
'2ч 3мин 4с'
Тот же подход применяют для конвертации байтов в килобайты/мегабайты, копеек в рубли. Каждый раз работает одна пара: // для «крупных единиц» и % для «мелкого остатка».
Циклическая адресация
Оператор % превращает линейный индекс в циклический. Если список содержит n элементов, i % n всегда даёт допустимый индекс от 0 до n-1:
days = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс']
>>> days[0 % 7] → 'Пн'
>>> days[7 % 7] → 'Пн'
>>> days[10 % 7] → 'Чт'
День 0 и день 7 — одинаковый понедельник. Индекс никогда не выходит за границы. Приём применяют в кольцевых буферах, при раздаче карт игрокам по кругу, при нормализации угла (0°–360°), при выборе текущего элемента зацикленного плейлиста.
Чередование действий в циклах
Оператор % помогает выполнять действие через равные интервалы:
for i in range(10000):
if i % 100 == 0:
print(f'Обработано {i} записей')
Или чередовать цвета строк таблицы:
for i, row in enumerate(data):
color = 'white' if i % 2 == 0 else 'gray'
Условие i % n == 0 срабатывает ровно каждую n-ю итерацию.
Валидация контрольных сумм
Алгоритмы проверки банковских карт (Луна), штрихкодов (EAN-13), ИНН, СНИЛС используют остаток от деления:
if checksum % 10 == 0:
print('Номер корректен')
Алгоритм Луна суммирует цифры номера карты с определёнными весами, а затем проверяет, делится ли итоговая сумма на 10 без остатка.
Шифр Цезаря
Простейший шифр сдвигает букву алфавита на фиксированное число позиций. Оператор % обеспечивает «заворачивание» в начало:
def caesar_encrypt(text, shift):
result = []
for char in text:
if char.isalpha():
base = ord('a') if char.islower() else ord('A')
shifted = (ord(char) - base + shift) % 26
result.append(chr(base + shifted))
else:
result.append(char)
return ''.join(result)
>>> caesar_encrypt('xyz', 3)
'abc'
Выражение % 26 гарантирует результат от 0 до 25 — без условных конструкций.
Разбиение на группы
Распределить элементы по n группам «по кругу»:
items = list(range(10))
n_groups = 3
groups = [[] for _ in range(n_groups)]
for i, item in enumerate(items):
groups[i % n_groups].append(item)
>>> groups
[[0, 3, 6, 9], [1, 4, 7], [2, 5, 8]]
Подход используют при параллельной обработке данных: каждый воркер получает свою «порцию» задач.
Магический метод __mod__
В пользовательских классах поведение % определяется методом __mod__:
class Angle:
def __init__(self, degrees):
self.degrees = degrees % 360
def __mod__(self, other):
return Angle(self.degrees % other)
def __repr__(self):
return f'Angle({self.degrees}°)'
>>> Angle(450) → Angle(90°)
>>> Angle(450) % 180 → Angle(90°)
Обратный метод __rmod__ вызывается, когда экземпляр класса стоит справа от оператора.
Оператор % и строки (устаревшее форматирование)
Если левый операнд — строка, % становится оператором форматирования:
>>> 'Мне %d лет' % 25
'Мне 25 лет'
Python определяет поведение по типу левого операнда: строка — форматирование, число — остаток. Сегодня рекомендуют f-строки, но старый стиль встречается в модуле logging и унаследованном коде.
Деление на ноль
Выражение x % 0 вызывает ZeroDivisionError — как и обычное деление. Исключение бросается и для float: 10.0 % 0.0 тоже ошибка, а не NaN.
Модулярное возведение в степень
Встроенная функция pow() принимает третий аргумент — модуль:
>>> pow(2, 10, 1000)
24
pow(a, b, mod) вычисляет (a ** b) % mod через алгоритм быстрого модулярного возведения. Для больших чисел разница в скорости — тысячи раз. Функция активно применяется в криптографии (RSA, Диффи-Хеллман).
Частые ошибки
- Путать % с делением. Оператор возвращает остаток, не частное.
- Ожидать отрицательный остаток. В Python -5 % 3 = 1, а не -2.
- Использовать % для форматирования строк в новом коде. F-строки быстрее.
- Забывать о погрешности float. Выражение 0.3 % 0.1 не даёт точный 0.0.
- Не обрабатывать деление на ноль. x % 0 выбросит исключение при любом x.
FAQ
Чем отличается % от //?
// возвращает частное, % — остаток. divmod(a, b) возвращает оба значения в кортеже.
Работает ли % с комплексными числами?
Нет. Выражение (3+2j) % 2 вызовет TypeError. Оператор определён только для int и float.
Когда использовать math.fmod()?
Когда нужен остаток со знаком числителя, как в C. Это бывает при портировании алгоритмов из C/C++.
Как ускорить (a ** b) % mod?
Использовать pow(a, b, mod) — встроенная функция работает на порядки быстрее раздельных операций.
