C и Python - различное поведение операции по модулю (%) - PullRequest
63 голосов
/ 15 декабря 2009

Я обнаружил, что одна и та же операция мода дает разные результаты в зависимости от используемого языка.

В Python:

-1 % 10

производит 9

В C он производит -1 !

  1. Какой из них является правильным по модулю?
  2. Как сделать так, чтобы работа мода в C была такой же, как в Python?

Ответы [ 6 ]

67 голосов
/ 15 декабря 2009
  1. Оба варианта верны, однако в математике (в частности, в теории чисел) чаще всего используется Python по модулю .
  2. В C вы делаете ((n % M) + M) % M, чтобы получить тот же результат, что и в Python. Например ((-1 % 10) + 10) % 10. Обратите внимание, как это все еще работает для положительных целых чисел: ((17 % 10) + 10) % 10 == 17 % 10, а также для обоих вариантов реализации C (положительный или отрицательный остаток).
30 голосов
/ 15 декабря 2009

Python имеет "истинную" операцию по модулю, в то время как C имеет остаток .

Он имеет прямое отношение к тому, как обрабатывается отрицательное целочисленное деление, то есть округляется до 0 или минус бесконечность. Python округляет до минус бесконечности, а C (99) - до 0, но на обоих языках (n/m)*m + n%m == n, поэтому оператор% должен компенсировать в правильном направлении.

Ада является более явным и имеет оба, как mod и rem.

15 голосов
/ 15 декабря 2009

В C89 / 90 поведение оператора деления и оператора остатка с отрицательными операндами определяется реализацией , что означает, что в зависимости от реализации вы можете получить любое поведение. Просто требуется, чтобы операторы договорились друг с другом: от a / b = q и a % b = r следует a = b * q + r. Используйте статические утверждения в своем коде для проверки поведения, если оно критически зависит от результата.

В C99 поведение, которое вы наблюдаете, стало стандартным.

На самом деле, в любом поведении есть определенная логика. Поведение Python реализует истинную операцию по модулю. Поведение, которое вы наблюдали, соответствует C и соответствует округлению до 0 (это также поведение Fortran).

Одна из причин, по которой округление в 0 является предпочтительным, состоит в том, что вполне естественно ожидать, что результат -a / b будет таким же, как -(a / b). В случае истинного поведения по модулю -1 % 10 будет иметь значение 9, что означает, что -1 / 10 должно быть -1. Это может показаться довольно неестественным, поскольку -(1 / 10) равно 0.

4 голосов
/ 15 декабря 2009

Оба ответа верны, поскольку -1 modulo 10 совпадает с 9 modulo 10.

r = (a mod m)
a = n*q + r

Вы можете быть уверены, что |r| < |n|, но не в значении r. Есть 2 ответа, отрицательный и положительный.


В C89, хотя ответ всегда будет правильным, точное значение операции по модулю (они называют ее остатком) не определено, то есть это может быть либо отрицательный результат, либо положительный результат. В C99 результат определен.

Если вы хотите получить положительный ответ, вы можете просто добавить 10, если найдете, что ваш ответ отрицательный.

Чтобы оператор modulo работал одинаково на всех языках, просто запомните:

n mod M == (n + M) mod M

и вообще:

n mod M == (n + X * M) mod M
1 голос
/ 21 июня 2018

Выполнение евклидова деления a = b*q + r похоже на округление дроби a/b до целого отношения q, а затем вычисление остатка r.

Различные результаты, которые вы видите, зависят от соглашения, используемого для округления фактора ...

Если вы округлите до нуля (усечь), вы получите симметрию около нуля, как в C:

truncate(7/3) = 2
7 = 3*2 + 1

truncate(-7/3) = -2
-7 = 3* -2 - 1

truncate(7/-3) = -2
7 = -3* -2 + 1

Если вы округлите в сторону отрицательной бесконечности (этажа), вы получите остаток, как в Python:

floor(7/3) = 2
7 = 3*2 + 1

floor(-7/3) = -3
-7 = 3* -3 + 2

floor(7/-3) = -3
7 = -3* -3 - 2

Если вы округлите до ближайшего целого (привязать к тому, что вы хотите, к четному или от нуля), вы получите центрированное по модулю:

round(7/3) = 2
7 = 3*2 + 1

round(8/3) = 3
8 = 3*3 - 1

round(-7/3) = -2
-7 = 3* -2 - 1

round(7/-3) = -2
7 = -3* -2 + 1

Вы могли бы попытаться реализовать свой собственный модуль с округлением до положительной бесконечности (ceil), и вы бы изобрели довольно нетрадиционный модуль, но это все равно было бы по модулю ...

0 голосов
/ 08 мая 2017

Начиная с 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

Из документов :

Вернуть остаток x в стиле IEEE 754 относительно 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, результат этой операции всегда точно представлен: ошибка округления не вводится.

...