A operação de módulo em números negativos em Python

97

Encontrei um comportamento estranho em Python em relação aos números negativos:

>>> -5 % 4
3

Alguém poderia explicar o que está acontecendo?

facha
fonte
27
parece certo para mim
wheaties
6
..., -9, -5, -1, 3, 7, ...
NullUserException
9
Você pode usar math.fmodpara obter o mesmo comportamento que em C ou Java.
0x2b3bfa0

Respostas:

142

Ao contrário de C ou C ++, o operador de módulo do Python ( %) sempre retorna um número com o mesmo sinal do denominador (divisor). Sua expressão produz 3 porque

(-5) / 4 = -1,25 -> piso (-1,25) = -2

(-5)% 4 = (-2 × 4 + 3)% 4 = 3.

Ele é escolhido em vez do comportamento C porque um resultado não negativo costuma ser mais útil. Um exemplo é calcular os dias da semana. Se hoje é terça-feira (dia 2), qual é o dia da semana N dias antes? Em Python podemos computar com

return (2 - N) % 7

mas em C, se N ≥ 3, obtemos um número negativo que é um número inválido e precisamos corrigi-lo manualmente adicionando 7:

int result = (2 - N) % 7;
return result < 0 ? result + 7 : result;

(Consulte http://en.wikipedia.org/wiki/Modulo_operator para saber como o sinal do resultado é determinado para diferentes idiomas.)

Kennytm
fonte
6
Surpreendentemente, o operador de módulo do Python (%) nem sempre retorna um número com o mesmo sinal do denominador (divisor). Consulte stackoverflow.com/questions/48347515/…
zezollo
33

Aqui está uma explicação de Guido van Rossum:

http://python-history.blogspot.com/2010/08/why-pythons-integer-division-floors.html

Essencialmente, é assim que a / b = q com o resto r preserva as relações b * q + r = a e 0 <= r <b.

Kevin
fonte
4
Linguagens como C ++ e Java também preservam o primeiro relacionamento, mas se limitam a negativo a, positivo b, enquanto os pisos de Python. É sempre verdade isso abs(r) < b, e eles se fecham r <= 0.
Evgeni Sergeev
9

Não existe uma maneira melhor de lidar com a divisão de inteiros e mods com números negativos. Seria bom se a/bfosse da mesma magnitude e sinal oposto de (-a)/b. Seria bom se a % bfosse mesmo um módulo b. Como realmente queremos a == (a/b)*b + a%b, os dois primeiros são incompatíveis.

Qual delas manter é uma questão difícil e há argumentos para ambos os lados. C e C ++ arredondam a divisão inteira para zero (portanto a/b == -((-a)/b)), e aparentemente o Python não.

David Thornley
fonte
1
"Seria bom se a / b tivesse a mesma magnitude e sinal oposto de (-a) / b." Por que isso seria bom? Quando esse é um comportamento desejado?
user76284
Porque ele agiria da mesma maneira que a divisão e multiplicação regulares e, portanto, é intuitivamente fácil de trabalhar. Isso pode não fazer sentido matematicamente, no entanto.
Demis
7

Como apontado, o módulo Python é uma exceção bem fundamentada às convenções de outras linguagens.

Isso dá aos números negativos um comportamento contínuo, especialmente quando usados ​​em combinação com o //operador de divisão de inteiros, como o %módulo costuma ser (como em matemática. Divmod ):

for n in range(-8,8):
    print n, n//4, n%4

Produz:

 -8 -2 0
 -7 -2 1
 -6 -2 2
 -5 -2 3

 -4 -1 0
 -3 -1 1
 -2 -1 2
 -1 -1 3

  0  0 0
  1  0 1
  2  0 2
  3  0 3

  4  1 0
  5  1 1
  6  1 2
  7  1 3
  • Python %sempre produz zero ou positivo *
  • Python //sempre gira em direção ao infinito negativo

* ... desde que o operando correto seja positivo. Por outro lado11 % -10 == -9

Bob Stein
fonte
Obrigado, seu exemplo me fez entender :)
Lamis
6

Em python , o operador módulo funciona assim.

>>> mod = n - math.floor(n/base) * base

então o resultado é (para o seu caso):

mod = -5 - floor(-1.25) * 4
mod = -5 - (-2*4)
mod = 3

enquanto outras linguagens como C, JAVA e JavaScript usam truncamento em vez de floor.

>>> mod = n - int(n/base) * base

o que resulta em:

mod = -5 - int(-1.25) * 4
mod = -5 - (-1*4)
mod = -1

Se você precisar de mais informações sobre arredondamento em python, leia isto .

Munkhbold Enkhtur
fonte
3

Módulo, classes de equivalência para 4:

  • 0: 0, 4, 8, 12 ... e -4, -8, -12 ...
  • 1: 1, 5, 9, 13 ... e -3, -7, -11 ...
  • 2: 2, 6, 10 ... e -2, -6, -10 ...
  • 3: 3, 7, 11 ... e -1, -5, -9 ...

Aqui está um link para o comportamento do módulo com números negativos . (Sim, eu pesquisei)

trigo
fonte
@NullUserException - sim, era. fixo. Obrigado.
wheaties,
1

Também achei que era um comportamento estranho do Python. Acontece que eu não estava resolvendo bem a divisão (no papel); Eu estava atribuindo um valor de 0 ao quociente e um valor de -5 ao restante. Terrível ... Esqueci a representação geométrica dos números inteiros. Ao relembrar a geometria dos inteiros dada pela reta numérica, pode-se obter os valores corretos para o quociente e o restante e verificar se o comportamento do Python está correto. (Embora eu presuma que você já resolveu sua preocupação há muito tempo).

Joser
fonte
1

Também vale a pena mencionar que também a divisão em python é diferente de C: Considere

>>> x = -10
>>> y = 37

em C você espera o resultado

0

o que é x / y em python?

>>> print x/y
-1

e% é módulo - não o resto! Enquanto x% y em C produz

-10

python produz.

>>> print x%y
27

Você pode obter tanto como em C

A divisão:

>>> from math import trunc
>>> d = trunc(float(x)/y)
>>> print d
0

E o restante (usando a divisão acima):

>>> r = x - d*y
>>> print r
-10

Este cálculo talvez não seja o mais rápido, mas está funcionando para qualquer combinação de sinais de xey para obter os mesmos resultados que em C, além de evitar declarações condicionais.

bebbo
fonte