Operazione Modulo con numeri negativi


196

In un programma C stavo provando le seguenti operazioni (Solo per verificare il comportamento)

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

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

mi ha dato un output come (2, -2 , -2)in gcc. Mi aspettavo un risultato positivo ogni volta. Un modulo può essere negativo? Qualcuno può spiegare questo comportamento?



Risposte:


170

C99 richiede che quando a/bè rappresentabile:

(a/b) * b + a%b deve essere ugualea

Questo ha senso, logicamente. Destra?

Vediamo cosa porta a:


L'esempio A. 5/(-3)è-1

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

Questo può succedere solo se 5%(-3)è 2.


L'esempio B. (-5)/3è-1

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

Questo può succedere solo se lo (-5)%3è-2


1
Il compilatore dovrebbe essere abbastanza intelligente e rilevare che un modulo senza segno un altro senza segno è sempre positivo? Attualmente (bene, GCC 5.2) il compilatore sembra pensare che "%" restituisca un "int" in questo caso, piuttosto che "unsigned" anche quando entrambi gli operandi sono uint32_t o più grandi.
Federico Nord,

@FrederickNord Hai un esempio per mostrare quel comportamento ?
chux - Ripristina Monica il

11
Comprendi che ciò che descrivi è la solita descrizione int (a / b) (troncata) del mod. Ma è anche possibile che la regola sia floor (a / b) (Knuth). Nel caso Knuth lo -5/3è -2e la mod diventa 1. In breve: un modulo ha un segno che segue il segno del dividendo (troncato), l'altro modulo ha un segno che segue il segno del divisore (Knuth).
Isacco,

1
Questo è un caso in cui lo standard C non è esattamente quello che voglio. Non ho mai voluto troncare a zero o numeri di modulo negativi, ma spesso voglio il contrario e ho bisogno di aggirare C.
Joe

145

L' %operatore in C non è l' operatore modulo ma l' operatore residuo .

Gli operatori Modulo e resto differiscono rispetto ai valori negativi.

Con un operatore residuo, il segno del risultato è uguale al segno del dividendo, mentre con un operatore modulo il segno del risultato è lo stesso del divisore.

C definisce l' %operazione per a % bcome:

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

con /la divisione intera con troncamento verso 0. Questo è il troncamento che viene fatto verso 0(e non verso l'iniquità negativa) che definisce l' %operatore residuo come un operatore modulo.


8
Il resto è il risultato dell'operazione modulo per definizione. L'operatore residuo non dovrebbe esistere perché non esiste un'operazione rimanente, si chiama modulo.
gronostaj,

41
@gronostaj non in CS. Guarda linguaggi di livello superiore come Haskell o Scheme che definiscono entrambi due diversi operatori ( remaindere moduloin Scheme reme modin Haskell). Le specifiche di questi operatori differiscono su queste lingue su come viene eseguita la divisione: troncamento verso 0 o verso l'infinito negativo. A proposito, lo Standard C non chiama mai %l' operatore modulo , lo chiamano semplicemente l' operatore% .
ouah,

2
Da non confondere con la remainder funzione in C, che implementa il resto IEEE con la semantica round-near-più vicina nella divisione
Eric

68

Basato sulla specifica C99: a == (a / b) * b + a % b

Possiamo scrivere una funzione per calcolare (a % b) == a - (a / b) * b!

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

Per il funzionamento del modulo, possiamo avere la seguente funzione (presupponendo b > 0)

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

La mia conclusione è che a % bin C è un'operazione rimanente e NON un'operazione modulo.


3
Questo non dà risultati positivi quando bè negativo (e in effetti per entrambi re bnegativo dà risultati inferiori a -b). Per garantire risultati positivi per tutti gli input che è possibile utilizzare r + abs(b)o per abbinare bil segno s, è possibile modificare invece la condizione r*b < 0.
Martin Ender,

@MartinEnder r + abs(b)è UB quando b == INT_MIN.
chux - Ripristina Monica il

60

Non credo che non sia necessario verificare se il numero è negativo.

Una semplice funzione per trovare il modulo positivo sarebbe questa:

Modifica: supponendo N > 0eN + N - 1 <= INT_MAX

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

Questo lavoro per entrambi positivi e negativi valori di x.

PS originale: anche come sottolineato da @chux, Se la tua x e N possono raggiungere rispettivamente qualcosa come INT_MAX-1 e INT_MAX, basta sostituirlo intcon long long int.

E se stanno attraversando anche limiti di long long (cioè vicino a LLONG_MAX), dovrai trattare separatamente i casi positivi e negativi come descritto nelle altre risposte qui.


1
Si noti che quando N < 0, il risultato può essere negativo come in modulo(7, -3) --> -2. Inoltre x % N + Npuò traboccare la intmatematica che è un comportamento indefinito. ad es. modulo(INT_MAX - 1,INT_MAX)potrebbe risultare in -3.
chux - Ripristina Monica il

Sì, in quel caso puoi semplicemente usare long long into gestire il caso negativo separatamente (a costo di perdere la semplicità).
Udayraj Deshmukh,

9

Le altre risposte hanno spiegato in C99 o versioni successive, la divisione degli interi che coinvolgono operandi negativi si tronca sempre verso zero .

Si noti che, in C89 , se il risultato arrotondato verso l'alto o verso il basso è definito dall'implementazione. Poiché è (a/b) * b + a%buguale ain tutti gli standard, il risultato del %coinvolgimento di operandi negativi è anche definito in C89.


5

Un modulo può essere negativo?

%può essere negativo in quanto è l' operatore residuo , il resto dopo la divisione, non dopo Euclidean_division . Dal C99 il risultato può essere 0, negativo o positivo.

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

Il modulo OP voluto è un classico modulo euclideo , no %.

Mi aspettavo un risultato positivo ogni volta.

Per eseguire un modulo euclideo che è ben definito ogni volta che a/bviene definito, a,bsono di qualsiasi segno e il risultato non è mai 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

Il risultato di un'operazione di modulo dipende dal segno del numeratore e, quindi, che stai ricevendo -2 per y e z

Ecco il riferimento

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

Divisione Integer

Questa sezione descrive le funzioni per eseguire la divisione di numeri interi. Queste funzioni sono ridondanti nella libreria GNU C, poiché in GNU C l'operatore '/' arrotonda sempre verso zero. Ma in altre implementazioni in C, '/' può arrotondare diversamente con argomenti negativi. div e ldiv sono utili perché specificano come arrotondare il quoziente: verso zero. Il resto ha lo stesso segno del numeratore.


5
Ti riferisci a un testo su ANSI C. Questa è una norma abbastanza vecchia di C. Non sono sicuro che il testo sia corretto rispetto ad ANSI C, ma sicuramente non riguardo a C99. In C99 §6.5.5 la divisione di numeri interi viene definita per troncare sempre verso zero.
Palec,

2

In matematica, da dove derivano queste convenzioni, non si afferma che l'aritmetica del modulo dovrebbe produrre un risultato positivo.

Per esempio.

1 mod 5 = 1, ma può anche essere uguale a -4. Cioè, 1/5 produce un resto 1 da 0 o -4 da 5. (Entrambi i fattori di 5)

Allo stesso modo, -1 mod 5 = -1, ma può anche essere uguale a 4. Cioè, -1/5 produce un resto -1 da 0 o 4 da -5. (Entrambi i fattori di 5)

Per ulteriori approfondimenti, guarda le classi di equivalenza in Matematica.


La classe di equivalenza è un concetto diverso e il modulo è definito in modo molto rigoroso. Diciamo che abbiamo due numeri interi ae b, b <> 0. Secondo il teorema della divisione euclidea esiste esattamente una coppia di numeri interi m, rdove a = m * b + re 0 <= r < abs( b ). Detto rè il risultato dell'operazione (matematica) del modulo e per definizione non è negativo. Altre letture e ulteriori collegamenti su Wikipedia: en.wikipedia.org/wiki/Euclidean_division
Ister

Questo non è vero. 1 mod 5è sempre 1. -4 mod 5potrebbe essere anche 1, ma sono cose diverse.
FelipeC,

2

Secondo lo standard C99 , sezione 6.5.5 Operatori moltiplicativi , è richiesto quanto segue:

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

Conclusione

Il segno del risultato di un'operazione residua, secondo C99, è lo stesso di quello del dividendo.

Vediamo alcuni esempi ( dividend / divisor):

Quando solo il dividendo è negativo

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

(-3 / 2) * 2 = -2

(-3 % 2) must be -1

Quando solo il divisore è negativo

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

(3 / -2) * -2 = 2

(3 % -2) must be 1

Quando sia il divisore che il dividendo sono negativi

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

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

(-3 % -2) must be -1

6.5.5 Operatori moltiplicativi

Sintassi

  1. moltiplicativa espressione:
    • cast-expression
    • multiplicative-expression * cast-expression
    • multiplicative-expression / cast-expression
    • multiplicative-expression % cast-expression

vincoli

  1. Ciascuno degli operandi deve avere un tipo aritmetico. Gli operandi dell'operatore % devono avere un numero intero.

Semantica

  1. Le solite conversioni aritmetiche vengono eseguite sugli operandi.

  2. Il risultato dell'operatore binario * è il prodotto degli operandi.

  3. Il risultato dell'operatore / è il quoziente della divisione del primo operando per il secondo; il risultato dell'operatore % è il resto. In entrambe le operazioni, se il valore del secondo operando è zero, il comportamento non è definito.

  4. Quando gli interi sono divisi, il risultato dell'operatore / è il quoziente algebrico con qualsiasi parte frazionaria scartata [1]. Se il quoziente a/bè rappresentabile, l'espressione (a/b)*b + a%bdeve essere uguale a.

[1]: questo è spesso chiamato "troncamento verso zero".


1

L'operatore del modulo fornisce il resto. L'operatore del modulo in c di solito prende il segno del numeratore

  1. x = 5% (-3) - qui il numeratore è positivo quindi risulta in 2
  2. y = (-5)% (3) - qui il numeratore è negativo, quindi risulta -2
  3. z = (-5)% (-3) - qui il numeratore è negativo, quindi risulta -2

Inoltre, l'operatore modulo (resto) può essere utilizzato solo con tipo intero e non può essere utilizzato con virgola mobile.


2
Sarebbe bello se è possibile eseguire il backup con collegamenti a risorse esterne.
J ... S,

1

Credo che sia più utile pensare modcome è definito nell'aritmetica astratta; non come un'operazione, ma come una classe completamente diversa di aritmetica, con elementi diversi e operatori diversi. Ciò significa che l'aggiunta mod 3non è la stessa dell'aggiunta "normale"; questo è; aggiunta di numeri interi.

Quindi quando lo fai:

5 % -3

Stai tentando di mappare l' intero 5 su un elemento nel set di mod -3. Questi sono gli elementi di mod -3:

{ 0, -2, -1 }

Così:

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

Di 'che devi rimanere sveglio per qualche motivo 30 ore, quante ore ti resteranno di quel giorno? 30 mod -24.

Ma ciò che C implementa non lo è mod, è un resto. Comunque, il punto è che ha senso restituire negativi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.