Fonctionnement modulo avec des nombres négatifs


206

Dans un programme C, j'essayais les opérations ci-dessous (juste pour vérifier le comportement)

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

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

m'a donné la sortie comme (2, -2 , -2)dans gcc. Je m'attendais à un résultat positif à chaque fois. Un module peut-il être négatif? Quelqu'un peut-il expliquer ce comportement?



Réponses:


179

C99 exige que quand a/best représentable:

(a/b) * b + a%b est égal àa

Cela a du sens, logiquement. Droite?

Voyons à quoi cela mène:


L'exemple A. 5/(-3)est-1

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

Cela ne peut se produire que si 5%(-3)est 2.


L'exemple B. (-5)/3est-1

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

Cela ne peut se produire si (-5)%3est-2


1
Le compilateur doit-il être assez intelligent et détecter qu'un module non signé ou un autre non signé est toujours positif? Actuellement (enfin, GCC 5.2) le compilateur semble penser que "%" renvoie un "int" dans ce cas, plutôt que "unsigned" même lorsque les deux opérandes sont uint32_t ou plus.
Frederick Nord

@FrederickNord Avez-vous un exemple pour montrer ce comportement ?
chux - Réintégrer Monica le

12
Comprenez que ce que vous décrivez est la description habituelle int (a / b) (tronquée) de mod. Mais il est également possible que la règle soit floor (a / b) (Knuth). Dans le cas Knuth -5/3est -2et le mod devient 1. En bref: un module a un signe qui suit le signe du dividende (tronquer), l'autre module a un signe qui suit le signe diviseur (Knuth).
Isaac

1
C'est un cas où la norme C n'est pas exactement ce que je veux. Je n'ai jamais voulu tronquer à zéro ou des nombres modulo négatifs, mais je veux souvent le contraire et j'ai besoin de contourner C.
Joe

154

L' %opérateur en C n'est pas l' opérateur modulo mais le reste opérateur .

Les opérateurs modulo et reste diffèrent par rapport aux valeurs négatives.

Avec un opérateur de reste, le signe du résultat est le même que le signe du dividende tandis qu'avec un opérateur modulo le signe du résultat est le même que le diviseur.

C définit l' %opération pour a % bcomme:

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

avec /la division entière avec troncature vers 0. C'est la troncature qui est faite vers 0(et non vers l'inifinité négative) qui définit le %comme un opérateur de reste plutôt qu'un opérateur modulo.


8
Le reste est le résultat de l'opération modulo par définition. Il ne devrait pas y avoir d'opérateur de reste car il n'y a pas d'opération de reste, cela s'appelle modulo.
gronostaj

48
@gronostaj pas dans CS. Regardez les langages de niveau supérieur comme Haskell ou Scheme qui définissent tous deux deux opérateurs différents ( remainderet modulodans Scheme remet moddans Haskell). Les spécifications de ces opérateurs diffèrent sur ces langages sur la manière dont la division est effectuée: troncature vers 0 ou vers l'infini négatif. Soit dit en passant la norme C appelle jamais %l' opérateur modulo , ils nomment simplement l' opérateur% .
ouah

2
À ne pas confondre avec la remainder fonction en C, qui implémente le reste IEEE avec une sémantique arrondie vers la plus proche dans la division
Eric

72

Basé sur la spécification C99: a == (a / b) * b + a % b

On peut écrire une fonction à calculer (a % b) == a - (a / b) * b!

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

Pour le fonctionnement modulo, nous pouvons avoir la fonction suivante (en supposant b > 0)

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

Ma conclusion est que a % bdans C est une opération de reste et PAS une opération modulo.


3
Cela ne donne pas de résultats positifs quand best négatif (et en fait pour ret les bdeux négatifs, cela donne des résultats inférieurs à -b). Pour garantir des résultats positifs pour toutes les entrées que vous pouvez utiliser r + abs(b)ou pour correspondre au bsigne s, vous pouvez remplacer la condition par r*b < 0.
Martin Ender

@MartinEnder r + abs(b)est UB quand b == INT_MIN.
chux - Réintégrer Monica le

64

Je ne pense pas qu'il soit nécessaire de vérifier si le nombre est négatif.

Une fonction simple pour trouver le modulo positif serait la suivante -

Edit: En supposant N > 0etN + N - 1 <= INT_MAX

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

Cela fonctionnera pour les valeurs positives et négatives de x.

PS d'origine: également comme indiqué par @chux, si vos x et N peuvent atteindre quelque chose comme INT_MAX-1 et INT_MAX respectivement, remplacez-les simplement intpar long long int.

Et s'ils franchissent également les limites du long long (c'est-à-dire près de LLONG_MAX), alors vous devez gérer les cas positifs et négatifs séparément comme décrit dans d'autres réponses ici.


1
Notez que quand N < 0, le résultat peut être négatif comme dans modulo(7, -3) --> -2. x % N + NPeut également déborder de intmaths, ce qui est un comportement non défini. par exemple, cela modulo(INT_MAX - 1,INT_MAX)peut entraîner -3.
chux - Réintégrer Monica le

Oui, dans ce cas, vous pouvez simplement utiliser long long intou traiter le cas négatif séparément (au prix de perdre la simplicité).
Udayraj Deshmukh

10

Les autres réponses ont expliqué dans C99 ou plus tard, la division d'entiers impliquant des opérandes négatifs tronque toujours vers zéro .

Notez que, dans C89 , si le résultat arrondi à la hausse ou à la baisse est défini par l'implémentation. Parce que (a/b) * b + a%bégale adans toutes les normes, le résultat de l' %implication d'opérandes négatifs est également défini par l'implémentation dans C89.


6

Un module peut-il être négatif?

%peut être négatif car il s'agit de l' opérateur de reste , le reste après division, pas après Euclidean_division . Depuis C99, le résultat peut être 0, négatif ou positif.

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

Le modulo OP recherché est un modulo euclidien classique , non %.

Je m'attendais à un résultat positif à chaque fois.

Pour effectuer un modulo euclidien qui est bien défini chaque fois qu'il a/best défini, a,bsont de n'importe quel signe et le résultat n'est jamais négatif:

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

Le résultat de l'opération Modulo dépend du signe du numérateur, et donc vous obtenez -2 pour y et z

Voici la référence

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

Division entière

Cette section décrit les fonctions permettant d'effectuer une division entière. Ces fonctions sont redondantes dans la bibliothèque GNU C, puisque dans GNU C l'opérateur '/' arrondit toujours vers zéro. Mais dans d'autres implémentations C, «/» peut être arrondi différemment avec des arguments négatifs. div et ldiv sont utiles car ils spécifient comment arrondir le quotient: vers zéro. Le reste a le même signe que le numérateur.


5
Vous faites référence à un texte sur ANSI C. C'est une norme assez ancienne de C. Je ne sais pas si le texte est correct en ce qui concerne ANSI C, mais certainement pas en ce qui concerne C99. Dans C99 §6.5.5, la division entière est définie pour toujours tronquer vers zéro.
Palec

2

En mathématiques, d'où proviennent ces conventions, il n'y a pas d'affirmation selon laquelle l'arithmétique modulo devrait donner un résultat positif.

Par exemple.

1 mod 5 = 1, mais il peut aussi égaler -4. Autrement dit, 1/5 donne un reste 1 de 0 ou -4 de 5. (Les deux facteurs de 5)

De même, -1 mod 5 = -1, mais il peut également être égal à 4. Autrement dit, -1/5 donne un reste -1 à partir de 0 ou 4 à partir de -5. (Les deux facteurs de 5)

Pour en savoir plus, regardez dans les classes d'équivalence en mathématiques.


La classe d'équivalence est un concept différent et modulo est défini de manière très stricte. Disons que nous avons deux nombres entiers aet b, b <> 0. Selon le théorème de division euclidienne, il existe exactement une paire d'entiers m, ra = m * b + ret 0 <= r < abs( b ). Le dit rest le résultat de l'opération modulo (mathématique) et par définition est non négatif. Plus de lecture et d'autres liens sur Wikipedia: en.wikipedia.org/wiki/Euclidean_division
Ister

Ce n'est pas vrai. 1 mod 5est toujours 1. -4 mod 5peut être 1 aussi, mais ce sont des choses différentes.
FelipeC

2

Selon la norme C99 , section 6.5.5 Opérateurs multiplicatifs , les éléments suivants sont requis:

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

Conclusion

Le signe du résultat d'une opération de reliquat, selon C99, est le même que celui du dividende.

Voyons quelques exemples ( dividend / divisor):

Quand seul le dividende est négatif

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

(-3 / 2) * 2 = -2

(-3 % 2) must be -1

Quand seul le diviseur est négatif

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

(3 / -2) * -2 = 2

(3 % -2) must be 1

Lorsque le diviseur et le dividende sont négatifs

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

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

(-3 % -2) must be -1

6.5.5 Opérateurs multiplicatifs

Syntaxe

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

Contraintes

  1. Chacun des opérandes doit avoir un type arithmétique. Les opérandes de l' opérateur % doivent être de type entier.

Sémantique

  1. Les conversions arithmétiques usuelles sont effectuées sur les opérandes.

  2. Le résultat de l' opérateur binaire * est le produit des opérandes.

  3. Le résultat de l' opérateur / est le quotient de la division du premier opérande par le second; le résultat de l' opérateur % est le reste. Dans les deux opérations, si la valeur du deuxième opérande est zéro, le comportement n'est pas défini.

  4. Lorsque les entiers sont divisés, le résultat de l' opérateur / est le quotient algébrique avec toute partie fractionnaire écartée [1]. Si le quotient a/best représentable, l'expression (a/b)*b + a%bdoit être égale a.

[1]: Ceci est souvent appelé "troncature vers zéro".


1

L'opérateur de module donne le reste. L'opérateur de module en c prend généralement le signe du numérateur

  1. x = 5% (-3) - ici le numérateur est positif donc il en résulte 2
  2. y = (-5)% (3) - ici le numérateur est négatif donc il en résulte -2
  3. z = (-5)% (-3) - ici le numérateur est négatif donc il en résulte -2

De plus, l'opérateur module (reste) ne peut être utilisé qu'avec un type entier et ne peut pas être utilisé avec une virgule flottante.


2
Ce serait bien si vous pouviez sauvegarder cela avec des liens vers des ressources externes.
J ... S

1

Je pense qu'il est plus utile de penser modcomme il est défini dans l'arithmétique abstraite; non pas comme une opération, mais comme une toute autre classe d'arithmétique, avec différents éléments et différents opérateurs. Cela signifie que l'addition mod 3n'est pas la même chose que l'addition «normale»; C'est; addition d'entiers.

Alors quand vous faites:

5 % -3

Vous essayez de mapper l' entier 5 sur un élément de l'ensemble de mod -3. Voici les éléments de mod -3:

{ 0, -2, -1 }

Donc:

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

Disons que vous devez rester éveillé pour une raison quelconque 30 heures, combien d'heures vous restera-t-il ce jour-là? 30 mod -24.

Mais ce que C implémente n'est pas mod, c'est un reste. Quoi qu'il en soit, le fait est qu'il est logique de renvoyer des négatifs.


0

Il semble que le problème /ne soit pas une opération au sol .

int mod(int m, float n)
{  
  return m - floor(m/n)*n;
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.