C, Python - различное поведение операции modulo (%)
Я обнаружил, что одна и та же операция mod производит разные результаты в зависимости от того, какой язык используется.
В Python:
-1 % 10
создает 9
В C он производит -1!
1) Какой из них является правильным по модулю?
2) Как сделать работу mod в C такой же, как в Python?
Ответы
Ответ 1
- Оба варианта правильны, однако в математике (в частности, в теории чисел) наиболее часто используется Python modulo.
- В C вы выполняете
((n % M) + M) % M
, чтобы получить тот же результат, что и в Python. E. g. ((-1 % 10) + 10) % 10
. Обратите внимание, как это работает для целых положительных чисел: ((17 % 10) + 10) % 10 == 17 % 10
, а также для обоих вариантов реализаций C (положительный или отрицательный остаток).
Ответ 2
Python имеет "истинную" модульную операцию, а C имеет операцию остатка.
Он имеет прямое отношение к тому, как обрабатывается отрицательное целочисленное деление, то есть округляется до 0 или минус бесконечно. Python округляется до минуса бесконечности и C (99) в направлении 0, но на обоих языках (n/m)*m + n%m == n
, поэтому оператор% должен компенсировать в правильном направлении.
Ada более явна и имеет оба значения: mod
и rem
.
Ответ 3
В C89/90 поведение оператора деления и оператора остатка с отрицательными операндами определяется реализацией, что означает, что в зависимости от реализации вы можете получить либо поведение. Просто требуется, чтобы операторы соглашались друг с другом: из a / b = q
и a % b = r
следует a = b * q + r
. Используйте статические утверждения в своем коде, чтобы проверить поведение, если оно критически относится к результату.
В C99 поведение, которое вы наблюдаете, стало стандартным.
Фактически, либо поведение имеет определенную логику в нем. Поведение Python реализует истинную модульную операцию. Поведение, которое вы наблюдаете, соответствует C, округляя до 0 (это также поведение Fortran).
Одна из причин, когда округление к 0 является предпочтительным в C, состоит в том, что вполне естественно ожидать, что результат -a / b
будет таким же, как -(a / b)
. В случае истинного по модулю поведения -1 % 10
будет оцениваться до 9, что означает, что -1 / 10
должно быть -1. Это можно считать довольно неестественным, так как -(1 / 10)
равно 0.
Ответ 4
Оба ответа верны, так как -1 modulo 10
совпадает с 9 modulo 10
.
r = (a mod m)
a = n*q + r
Вы можете быть уверены, что |r| < |n|
, но не значение r
. Есть два ответа, отрицательные и положительные.
В C89, хотя ответ всегда будет правильным, точное значение операции с модулем (они относятся к нему как к остатку) undefined, что означает, что это может быть либо отрицательный результат, либо положительный результат. В C99 определяется результат.
Если вы хотите получить положительный ответ, вы можете просто добавить 10, если найдете свой ответ отрицательным.
Чтобы заставить modulo работать одинаково на всех языках, просто помните, что:
n mod M == (n + M) mod M
и вообще:
n mod M == (n + X * M) mod M
Ответ 5
С помощью python 3.7 вы также можете использовать .remainder()
из math
встроенный модуль.
Python 3.7.0a0 (heads/master:f34c685020, May 8 2017, 15:35:30)
[GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import math
>>> math.remainder(-1, 10)
-1.0
От docs:
Верните остаток в стиле IEEE 754 по x относительно y. Для конечного x и конечного ненулевого y это разность x - n*y
, где n является ближайшим целым к точному значению частного x / y
. Если x / y
находится на полпути между двумя последовательными целыми числами, то для n
используется ближайшее четное целое число. Остаток r = remainder(x, y)
, таким образом, всегда удовлетворяет abs(r) <= 0.5 * abs(y)
.
Особые случаи следуют за IEEE 754: в частности, remainder(x, math.inf)
есть x для любого конечного x, а remainder(x, 0)
и remainder(math.inf, x)
повышают ValueError для любого не-NaN x. Если результат операции останова равен нулю, этот ноль будет иметь тот же знак, что и x.
На платформах с использованием двоичной с плавающей запятой IEEE 754 результат этой операции всегда точно представлен: ошибка округления не вводится.