Деление с остатком – часто используемая операция в программировании. Начиная от классических заданий для начинающих на вычисление минут и секунд:
total_seconds = 119 seconds = total_seconds % 60 minutes = total_seconds // 60 print(f'{minutes}:{seconds}') # 1:59
Заканчивая тем, что на остатках построена львиная доля криптографии. Нахождения остатка часто называют modulo (или коротко mod).
При делении a на b неполное частное q и остаток r связаны формулой:
a = b · q + r, где b ≠ 0
В Python 3 частное и остаток вычисляются операторами:
q = a // b r = a % b
Именно двойной слэш, одинарный слэш – деление без остатка (до конца). Иногда двойной слэш называют целочисленным делением, что не очень справедливо, потому что мы можем без проблем делить числа с запятой. Если оба числа целые (int), то частное будет тоже целым числом (int), иначе float. Посмотрите примеры:
10 / 3 == 3.3333333333333335 10 // 3 == 3 10.0 / 3.0 == 3.3333333333333335 10.0 // 3.0 == 3.0 10.0 % 3.0 == 1.0 10 % 3 == 1 2.4 // 0.4 == 5.0 2.4 / 0.4 == 5.999999999999999 2.4 % 0.4 == 0.3999999999999998
Последние три примера немного обескураживают из-за особенностей вычислений с плавающей точкой на компьютере, но формула a = b · q + r всегда остается справедлива.
Поговорим об отрицательных числах. Математически остаток не должен быть меньше нуля и больше или равен модулю делителя b: 0 ≤ r < |b|. Однако, Intel в своих процессорах случайно либо намеренно ввела отрицательные остатки в реализации ассемблерных команд деления. Компиляторы языков C и С++, являясь платформо-зависимыми, обычно полагаются на процессорное поведение. Пример на С++. И вообще посмотрите на эту огромную таблицу, каждый язык программирования пляшет, как хочет. Не будем спорить, кто из них прав. Просто узнаем, как у нас в Python:
a, b = [10, -10], [3, -3] for x in a: for y in b: print(f'{x} // {y} = {x // y}') print(f'{x} % {y} = {x % y}') print() 10 // 3 = 3 10 % 3 = 1 10 // -3 = -4 10 % -3 = -2 -10 // 3 = -4 -10 % 3 = 2 -10 // -3 = 3 -10 % -3 = -1
Формула выполняется всегда, но результаты отличаются для С++ и Python, где при делении на положительное число – остаток всегда положителен, а на отрицательное число – отрицателен. Если бы мы сами реализовали взятие остатка, то получилось бы так:
def mod_python(a, b): return int(a - math.floor(a / b) * b) # на С++ работает так: def mod_cpp(a, b): return int(a - math.trunc(a / b) * b)
Где floor – ближайшее целое число не превышающее аргумент: floor(-3.3) = -4
, а trunc – функция отбрасывания целой части: trunc(-3.3) = -3
. Разница проявляется между ними только для отрицательных чисел. Отсюда и разные остатки и частные – все зависит от того, с какой стороны числовой оси мы приближаемся к частному.
Вывод: если вам доведется писать или портировать код, где возможно деление отрицательных чисел с остатком, будьте предельно аккуратны, и помните про разницу поведения деления в разных языках.
🐉 Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈