C и Python-различное поведение операции по модулю ( % )


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

В Python:

-1 % 10

производит 9

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

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

7 ответов:

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

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

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

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

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

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

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

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

оба ответа верны, так как -1 modulo 10 это то же самое, что 9 modulo 10.

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

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


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

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

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

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

а вообще:

n mod M == (n + X * M) mod M

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

мы можем использовать десятичный модуль python, чтобы вести себя как C

from decimal import Decimal
Decimal('-1') % Decimal('10')

Decimal representation

выполнение евклидова деления 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

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

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

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

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

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

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), и вы бы изобретать довольно нетрадиционный модуль, но это все равно будет своего рода модуль...