Operação do módulo com números negativos


191

Em um programa C, eu estava tentando as operações abaixo (Apenas para verificar o comportamento)

 x = 5 % (-3);
 y = (-5) % (3);
 z = (-5) % (-3); 

printf("%d ,%d ,%d", x, y, z); 

me deu saída como (2, -2 , -2)no gcc. Eu estava sempre esperando um resultado positivo. Um módulo pode ser negativo? Alguém pode explicar esse comportamento?



Respostas:


167

C99 exige que quando a/bé representável:

(a/b) * b + a%b será iguala

Isso faz sentido, logicamente. Certo?

Vamos ver o que isso leva a:


O exemplo A. 5/(-3)é-1

=> (-1) * (-3) + 5%(-3) =5

Isso só pode acontecer se 5%(-3)for 2.


O exemplo B. (-5)/3é-1

=> (-1) * 3 + (-5)%3 =-5

Isso só pode acontecer se (-5)%3for-2


1
O compilador deve ser inteligente o suficiente e detectar que um módulo não assinado e outro não assinado são sempre positivos? Atualmente (bem, GCC 5.2), o compilador parece pensar que "%" retorna um "int" nesse caso, em vez de "não assinado", mesmo quando os dois operandos são uint32_t ou maiores.
Frederick Nord

@FrederickNord Você tem um exemplo para mostrar esse comportamento ?
chux - Restabelece Monica

10
Entenda que o que você descreve é ​​a descrição int (a / b) (truncada) usual do mod. Mas também é possível que a regra seja o piso (a / b) (Knuth). No caso Knuth -5/3é -2eo mod se torna 1. Em suma: um módulo tem uma placa que segue o sinal de dividendos (truncar), o outro módulo tem uma placa que segue o sinal divisor (Knuth).
Isaac

1
Este é um caso de o padrão C não ser exatamente o que eu quero. Eu nunca quis truncado para zero ou negativos números modulo, mas muitas vezes querem o oposto e necessidade de trabalho em torno de C.
Joe

142

O %operador em C não é o operador de módulo , mas o operador restante .

Os operadores de módulo e restante diferem em relação aos valores negativos.

Com um operador restante, o sinal do resultado é o mesmo que o sinal do dividendo, enquanto que com um operador de módulo, o sinal do resultado é o mesmo que o divisor.

C define a %operação para a % b:

  a == (a / b * b) + a % b

com /a divisão inteira com truncamento para 0. Esse é o truncamento que é feito para 0(e não para a inifinidade negativa) que define o %operador como restante em vez de operador de módulo.


8
O restante é o resultado da operação do módulo por definição. Não deve haver operador restante, porque não existe operação restante, é chamado módulo.
precisa saber é o seguinte

41
@gronostaj não está no CS. Observe linguagens de nível superior, como Haskell ou Scheme, que definem dois operadores diferentes ( remaindere modulono Scheme reme modno Haskell). Essas especificações dos operadores diferem nesses idiomas em como a divisão é feita: truncamento para 0 ou para infinito negativo. Pela forma como o C padrão nunca chama o %o operador módulo , eles apenas nome do operador% .
ouah

2
Não deve ser confundido com a remainder função em C, que implementa IEEE restante com a semântica mais próximas redondas-para-a-na divisão
Eric

67

Com base na especificação C99: a == (a / b) * b + a % b

Podemos escrever uma função para calcular (a % b) == a - (a / b) * b!

int remainder(int a, int b)
{
    return a - (a / b) * b;
}

Para operação do módulo, podemos ter a seguinte função (assumindo b > 0)

int mod(int a, int b)
{
    int r = a % b;
    return r < 0 ? r + b : r;
}

Minha conclusão é que a % bem C é uma operação restante e NÃO uma operação de módulo.


3
Isso não dá resultados positivos quando bé negativo (e, de fato, para re bambos negativos, resultados menores que -b). Para garantir resultados positivos para todas as entradas que você poderia usar r + abs(b)ou para corresponder ao bsinal de s, você poderia alterar a condição para r*b < 0.
Martin Ender

@MartinEnder r + abs(b)é UB quando b == INT_MIN.
chux - Restabelece Monica

60

Acho que não há necessidade de verificar se o número é negativo.

Uma função simples para encontrar o módulo positivo seria esta -

Edit: Assumindo N > 0eN + N - 1 <= INT_MAX

int modulo(int x,int N){
    return (x % N + N) %N;
}

Isso funcionará para valores positivos e negativos de x.

PS original: também como indicado pelo @chux, se o seu x e N podem alcançar algo como INT_MAX-1 e INT_MAX, respectivamente, substitua intpor long long int.

E se eles também estão ultrapassando limites de muito tempo (ou seja, perto de LLONG_MAX), você deve lidar com casos positivos e negativos separadamente, conforme descrito em outras respostas aqui.


1
Observe que quando N < 0o resultado pode ser negativo como em modulo(7, -3) --> -2. Também x % N + Npode estourar intmatemática, que é um comportamento indefinido. por exemplo, modulo(INT_MAX - 1,INT_MAX)pode resultar em -3.
chux - Restabelece Monica

Sim, nesse caso, você pode simplesmente usar long long intou lidar com o caso negativo separadamente (ao custo de perder a simplicidade).
Udayraj Deshmukh

9

As outras respostas explicaram em C99 ou posterior, a divisão de números inteiros envolvendo operandos negativos sempre trunca em direção a zero .

Observe que, em C89 , se o resultado arredondado para cima ou para baixo é definido pela implementação. Como (a/b) * b + a%bé igual aem todos os padrões, o resultado do %envolvimento de operandos negativos também é definido na implementação em C89.


5

Um módulo pode ser negativo?

%pode ser negativo, pois é o operador restante , o restante após a divisão, não após a divisão Euclidean . Desde C99, o resultado pode ser 0, negativo ou positivo.

 // a % b
 7 %  3 -->  1  
 7 % -3 -->  1  
-7 %  3 --> -1  
-7 % -3 --> -1  

O módulo OP desejado é um módulo euclidiano clássico , não %.

Eu estava sempre esperando um resultado positivo.

Executar um módulo euclidiano que seja bem definido sempre que a/bfor definido a,bé de qualquer sinal e o resultado nunca é negativo:

int modulo_Euclidean(int a, int b) {
  int m = a % b;
  if (m < 0) {
    // m += (b < 0) ? -b : b; // avoid this form: it is UB when b == INT_MIN
    m = (b < 0) ? m - b : m + b;
  }
  return m;
}

modulo_Euclidean( 7,  3) -->  1  
modulo_Euclidean( 7, -3) -->  1  
modulo_Euclidean(-7,  3) -->  2  
modulo_Euclidean(-7, -3) -->  2   

2

O resultado da operação do módulo depende do sinal do numerador e, portanto, você obtém -2 para y e z

Aqui está a referência

http://www.chemie.fu-berlin.de/chemnet/use/info/libc/libc_14.html

Divisão Inteira

Esta seção descreve funções para executar a divisão inteira. Essas funções são redundantes na biblioteca GNU C, pois no GNU C o operador '/' sempre volta para zero. Mas em outras implementações C, '/' pode arredondar de maneira diferente com argumentos negativos. div e ldiv são úteis porque especificam como arredondar o quociente: para zero. O restante tem o mesmo sinal que o numerador.


5
Você está se referindo a um texto sobre ANSI C. Esta é uma norma bastante antiga de C. Não tenho certeza se o texto está correto em relação ao ANSI C, mas definitivamente não em relação ao C99. Em C99, §6.5.5, a divisão inteira é definida para sempre truncar em direção a zero.
Palec 5/02/2014

2

Em Matemática, de onde essas convenções se originam, não há afirmação de que o módulo aritmético deva produzir um resultado positivo.

Por exemplo.

1 mod 5 = 1, mas também pode ser igual a -4. Ou seja, 1/5 produz um restante 1 de 0 ou -4 de 5. (Ambos os fatores de 5)

Da mesma forma, -1 mod 5 = -1, mas também pode ser igual a 4. Ou seja, -1/5 produz um restante -1 de 0 ou 4 de -5. (Ambos os fatores de 5)

Para uma leitura mais aprofundada, procure classes de equivalência em Matemática.


Classe de equivalência é um conceito diferente e o módulo é definido de uma maneira muito estrita. Vamos dizer que temos dois números inteiros ae b, b <> 0. De acordo com o teorema da divisão euclidiana, existe exatamente um par de números inteiros m, ronde a = m * b + re 0 <= r < abs( b ). O dito ré o resultado da operação do módulo (matemático) e, por definição, não é negativo. Mais leitura e mais links na Wikipedia: en.wikipedia.org/wiki/Euclidean_division
Ister

Isso não é verdade. 1 mod 5é sempre 1. também -4 mod 5pode ser 1, mas são coisas diferentes.
FelipeC 5/06/19

2

De acordo com o padrão C99 , seção 6.5.5 Operadores multiplicativos , é necessário o seguinte:

(a / b) * b + a % b = a

Conclusão

O sinal do resultado de uma operação remanescente, de acordo com C99, é o mesmo que o do dividendo.

Vamos ver alguns exemplos ( dividend / divisor):

Quando apenas o dividendo é negativo

(-3 / 2) * 2  +  -3 % 2 = -3

(-3 / 2) * 2 = -2

(-3 % 2) must be -1

Quando apenas o divisor é negativo

(3 / -2) * -2  +  3 % -2 = 3

(3 / -2) * -2 = 2

(3 % -2) must be 1

Quando o divisor e o dividendo são negativos

(-3 / -2) * -2  +  -3 % -2 = -3

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

(-3 % -2) must be -1

6.5.5 Operadores multiplicativos

Sintaxe

  1. expressão multiplicativa:
    • cast-expression
    • multiplicative-expression * cast-expression
    • multiplicative-expression / cast-expression
    • multiplicative-expression % cast-expression

Restrições

  1. Cada um dos operandos deve ter um tipo aritmético. Os operandos do operador % devem ter um tipo inteiro.

Semântica

  1. As conversões aritméticas usuais são realizadas nos operandos.

  2. O resultado do operador binário * é o produto dos operandos.

  3. O resultado do operador / é o quociente da divisão do primeiro operando pelo segundo; o resultado do operador % é o restante. Nas duas operações, se o valor do segundo operando for zero, o comportamento será indefinido.

  4. Quando números inteiros são divididos, o resultado do operador / é o quociente algébrico com qualquer parte fracionária descartada [1]. Se o quociente a/bé representável, a expressão (a/b)*b + a%bdeve ser igual a.

[1]: Isso geralmente é chamado de "truncamento em direção a zero".


1

O operador de módulo fornece o restante. O operador de módulo em c geralmente leva o sinal do numerador

  1. x = 5% (-3) - aqui o numerador é positivo, portanto resulta em 2
  2. y = (-5)% (3) - aqui o numerador é negativo, portanto resulta -2
  3. z = (-5)% (-3) - aqui o numerador é negativo, portanto resulta -2

O operador módulo (restante) também pode ser usado apenas com o tipo inteiro e não pode ser usado com ponto flutuante.


1
Seria bom se você pudesse fazer backup disso com links para recursos externos.
J ... S

1

Eu acredito que é mais útil pensar modcomo definido na aritmética abstrata; não como uma operação, mas como uma classe aritmética totalmente diferente, com diferentes elementos e diferentes operadores. Isso significa que a adição mod 3não é igual à adição "normal"; isso é; adição de número inteiro.

Então, quando você faz:

5 % -3

Você está tentando mapear o número inteiro 5 para um elemento no conjunto de mod -3. Estes são os elementos de mod -3:

{ 0, -2, -1 }

Assim:

0 => 0, 1 => -2, 2 => -1, 3 => 0, 4 => -2, 5 => -1

Digamos que você tenha que ficar acordado por algum motivo 30 horas, quantas horas você terá nesse dia? 30 mod -24.

Mas o que C implementa não é mod, é um restante. De qualquer forma, a questão é que faz sentido retornar negativos.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.