Поведение оператора % с отрицательными числами в Python
@python_quizРазберем этот квиз
Коротко: выражения

дают результаты, которые могут удивить, если вы ожидаете, что знак остатка всегда совпадает с знаком делимого. Разберёмся, почему так происходит и как это вычислять вручную.
Исходный код квиза

Результаты (быстро)
Запуск кода выдаёт три значения — сначала положительное, затем отрицательное и снова отрицательное. Ниже покажу точные вычисления и логику.
Правило и формула
В Python остаток определяется так:
a % b = a - b * (a // b)
Здесь // — это целочисленное деление с округлением вниз (floor division). Из-за этого поведение при отрицательных числах отличается от простого "остаток = абс(a) % абс(b)". Важное следствие: знак остатка совпадает со знаком делителя b (или равен нулю).
Также выполняется тождество:
a == b * (a // b) + (a % b)
Пошаговый разбор выражений
1) -3 % 2
- Сначала вычисляем целочисленное деление: -3 // 2 = -2 (потому что -3/2 = -1.5, а округление вниз даёт -2).
- Подставляем в формулу: -3 - 2 * (-2) = -3 + 4 = 1.
- Итог: остаток = 1 (положительный, как делитель 2).
2) 3 % -2
- Целочисленное деление: 3 // -2 = -2 (3/(-2) = -1.5, floor = -2).
- Подстановка: 3 - (-2) * (-2) = 3 - 4 = -1.
- Итог: остаток = -1 (отрицательный, как делитель -2).
3) -3 % -2
- Целочисленное деление: -3 // -2 = 1 (-3/(-2) = 1.5,
floor = 1). - Подстановка: -3 - (-2) * 1 = -3 + 2 = -1.
- Итог: остаток = -1 (отрицательный, как делитель -2).
Подтверждение в коде

Ожидаемый вывод:

Частые заблуждения и заметки
- Многие ожидают, что остаток при делении сохраняет знак делимого (a). В Python знак остатка следует знаку делителя (b).
- Поведение может отличаться в других языках (например, некоторые языки используют усечение к нулю при делении, из‑за чего знак остатка совпадает с делимым). Поэтому при переносе алгоритмов между языками на это стоит обратить внимание.
- Утверждение a % b == a - b * (a // b) — надёжный способ предсказать значение в Python.
Вывод
Оператор % в Python даёт такой остаток, что выполняется равенство a == b * (a // b) + (a % b), а // — это floor division (округление вниз). Следствие: знак остатка совпадает со знаком делителя b. Для данных выражений это даёт последовательность результатов: положительный, затем отрицательный и снова отрицательный.