web-dev-qa-db-fra.com

Pourquoi - (- 2147483648) = - 2147483648 sur une machine 32 bits?

Je pense que la question est explicite, je suppose que cela a probablement quelque chose à voir avec le débordement, mais je ne comprends toujours pas. Que se passe-t-il, au niveau des bits, sous le capot?

Pourquoi -(-2147483648) = -2147483648 (au moins pendant la compilation en C)?

61
Lesscomfortable

Annulation d'une constante entière (non corrigée):

L'expression -(-2147483648) est parfaitement définie en C, mais il est difficile de comprendre pourquoi.

Quand vous écrivez -2147483648, il est formé comme un opérateur moins unaire appliqué à une constante entière. Si 2147483648 ne peut pas être exprimé sous la forme int, il est alors représenté par long ou long long* (selon ce qui convient en premier), où ce dernier type est garanti par la norme C pour couvrir cette valeur.

Pour le confirmer, vous pouvez l'examiner en:

printf("%zu\n", sizeof(-2147483648));

qui donne 8 sur ma machine.

La prochaine étape consiste à appliquer le second - opérateur, auquel cas la valeur finale est 2147483648L (en supposant qu’il a finalement été représenté par long). Si vous essayez de l'attribuer à l'objet int, procédez comme suit:

int n = -(-2147483648);

alors le comportement réel est défini par l'implémentation . En référence à la norme:

C11 §6.3.1.3/3 Entiers signés et non signés

Sinon, le nouveau type est signé et la valeur ne peut pas y être représentée. soit le résultat est défini par l'implémentation, soit un signal défini par l'implémentation est émis.

Le moyen le plus courant consiste simplement à couper les bits les plus élevés. Par exemple, GCC documents comme:

Pour la conversion en un type de largeur N, la valeur est réduite modulo 2 ^ N pour être dans la plage du type; aucun signal n'est élevé.

Conceptuellement, la conversion en type de largeur 32 peut être illustrée par l'opération AND au niveau du bit:

value & (2^32 - 1) // preserve 32 least significant bits

Conformément à complément à deux arithmétique, la valeur de n est formée avec tous les zéros et le jeu de bits MSB (signe), qui représente la valeur de -2^31, C'est -2147483648.

Annulation d'un objet int:

Si vous essayez de nier l'objet int, sa valeur est de -2147483648, puis en supposant que la machine du complément à deux, le programme présentera un comportement indéfini :

n = -n; // UB if n == INT_MIN and INT_MAX == 2147483647

C11 §6.5/5 Expressions

Si une condition exceptionnelle apparaît lors de l'évaluation d'une expression (autrement dit, si le résultat n'est pas défini mathématiquement ou n'est pas compris dans la plage des valeurs pouvant être représentées pour sa valeur), type), le comportement est indéfini.

Références supplémentaires:


*) Dans la norme C90 retirée, il n'y avait pas de long long _ type et les règles étaient différentes. Plus précisément, la séquence pour le nombre décimal non mélangé était int, long int, unsigned long int (C90 § 6.1.3.2 Constantes de nombre entier).

†) Cela est dû à LLONG_MAX, qui doit être au moins +9223372036854775807 (C11 §5.2.4.2.1/1).

74
Grzegorz Szpetkowski

Remarque: cette réponse ne s'applique pas en tant que telle à la norme obsolète ISO C90 qui est toujours utilisée par de nombreux compilateurs

Tout d’abord, sur C99, C11, l’expression -(-2147483648) == -2147483648 est en fait faux:

int is_it_true = (-(-2147483648) == -2147483648);
printf("%d\n", is_it_true);

empreintes

0

Alors, comment est-il possible que cela devienne vrai? La machine utilise 32 bits complément à deux entiers. Le 2147483648 Est une constante entière qui ne rentre pas dans les 32 bits. Ce sera donc long int Ou long long int En fonction du premier emplacement. Cette négation aura pour résultat -2147483648 - et encore, même si le nombre -2147483648 Peut tenir dans un entier 32 bits, l'expression -2147483648 Consiste en un positif> 32 bits entier précédé d'unary -!

Vous pouvez essayer le programme suivant:

#include <stdio.h>

int main() {
    printf("%zu\n", sizeof(2147483647));
    printf("%zu\n", sizeof(2147483648));
    printf("%zu\n", sizeof(-2147483648));
}

La sortie sur une telle machine serait très probablement de 4, 8 et 8.

Maintenant, -2147483648 Annulé aura à nouveau pour résultat +214783648, Qui est toujours de type long int Ou long long int, Et tout va bien.

Dans C99, C11, l'expression constante entière -(-2147483648) est bien définie pour toutes les implémentations conformes.


Désormais, lorsque cette valeur est affectée à une variable de type int, avec une représentation du complément à 32 bits et à deux, la valeur n'est pas représentable, les valeurs du complément à 32 bits 2 étant comprises entre -2147483648 et 2147483647. .

La norme C11 6.3.1.3p indique les conversions d’entiers suivantes:

  • [Quand] le nouveau type est signé et la valeur ne peut y être représentée; soit le résultat est défini par la mise en oeuvre , soit un signal défini par la mise en oeuvre est soulevé.

En d’autres termes, le standard C ne définit pas réellement la valeur que ce serait dans ce cas, ni n’exclut la possibilité que l’exécution du programme s’arrête en raison de l’émission d’un signal, mais laisse le soin aux implémentations (par exemple, les compilateurs). ) pour décider comment le gérer (C11 3.4.1) :

comportement défini par la mise en oeuvre

comportement indéterminé où chaque implémentation documente comment le choix est fait

et (3.19.1) :

valeur définie par la mise en oeuvre

valeur non spécifiée où chaque implémentation documente comment le choix est fait


Dans votre cas, le comportement défini par l'implémentation est que la valeur correspond aux 32 bits de poids faible [*]. En raison du complément à 2, la valeur (longue) longue int 0x80000000 A le bit 31 activé et tous les autres bits effacés. Dans les entiers complémentaires à deux bits 32 bits, le bit 31 est le bit de signe, ce qui signifie que le nombre est négatif; tous les bits de valeur mis à zéro signifient que la valeur est le nombre représentable minimal, à savoir INT_MIN.


[*] GCC décrit dans ce cas son comportement défini par la mise en oeuvre :

Le résultat de, ou le signal généré par, la conversion d'un entier en un type entier signé lorsque la valeur ne peut pas être représentée dans un objet de ce type (C90 6.2.1.2, C99 et C11 6.3.1.3).

Pour la conversion en un type de largeur N, la valeur est réduite modulo 2^N Pour être dans la plage du type; aucun signal n'est élevé.

16
Antti Haapala

Ce n'est pas une question C, car sur une implémentation C comportant une représentation du complément à deux 32 bits pour le type int, l'effet de l'application de l'opérateur de négation unaire à un int ayant la valeur -2147483648 Est non défini. C'est-à-dire que le langage C désavoue spécifiquement la désignation du résultat de l'évaluation d'une telle opération.

Cependant, considérez plus généralement comment l’opérateur unaire - Est défini dans l’arithmétique du complément à deux: l’inverse d’un nombre positif x est formé en retournant tous les bits de sa représentation binaire et en ajoutant 1. Cette même définition est également valable pour tout nombre négatif ayant au moins un bit autre que le bit de signe défini.

Des problèmes mineurs se posent toutefois pour les deux nombres pour lesquels aucun bit de valeur n'est défini: 0, pour lequel aucun bit n'est défini, et le nombre pour lequel seul son bit de signe est défini (-2147483648 en représentation 32 bits). Lorsque vous retournez tous les bits de l'un ou l'autre, vous vous retrouvez avec tous les bits de valeur définis. Par conséquent, lorsque vous ajoutez ensuite 1, le résultat déborde des bits de valeur. Si vous imaginez effectuer l’addition comme si le nombre était non signé, en traitant le bit de signe comme un bit de valeur, alors vous obtenez

    -2147483648 (decimal representation)
-->  0x80000000 (convert to hex)
-->  0x7fffffff (flip bits)
-->  0x80000000 (add one)
--> -2147483648 (convert to decimal)

La même chose s'applique à l'inversion du zéro, mais dans ce cas, le débordement lors de l'ajout de 1 déborde également du bit de signe précédent. Si le dépassement de capacité est ignoré, les 32 bits de poids faible résultants sont tous nuls, d'où -0 == 0.

6
John Bollinger

Je vais utiliser un nombre de 4 bits, juste pour rendre les maths simples, mais l'idée est la même.

Dans un nombre à 4 bits, les valeurs possibles sont comprises entre 0000 et 1111. Ce serait entre 0 et 15, mais si vous voulez représenter des nombres négatifs, le premier bit est utilisé pour indiquer le signe (0 pour le positif et 1 pour le négatif).

Donc, 1111 n'est pas 15. Comme le premier bit est 1, c'est un nombre négatif. Pour connaître sa valeur, nous utilisons la méthode des deux compléments telle que décrite dans les réponses précédentes: "inverser les bits et ajouter 1":

  • inverser les bits: 0000
  • ajoutant 1: 0001

0001 en binaire est 1 en décimal, donc 1111 est -1.

La méthode des deux compléments fonctionne dans les deux sens, donc si vous l'utilisez avec un nombre, elle vous donnera la représentation binaire de ce nombre avec le signe inversé.

Voyons maintenant 1000. Le premier bit est 1, il s'agit donc d'un nombre négatif. Utilisation de la méthode des deux compléments:

  • inverser les bits: 0111
  • ajouter 1: 1000 (8 en décimal)

Donc 1000 est -8. Si nous faisons -(-8), en binaire, cela signifie -(1000), ce qui signifie en fait utiliser la méthode du double complément en 1000. Comme nous l'avons vu ci-dessus, le résultat est également 1000. Donc, dans un Nombre à 4 bits, -(-8) est égal à -8.

Dans un nombre 32 bits, -2147483648 En binaire est 1000..(31 zeroes), mais si vous utilisez la méthode à deux complément, vous obtiendrez la même valeur (le résultat est le même nombre ).

C'est pourquoi dans le nombre 32 bits, -(-2147483648) est égal à -2147483648

1
user7605325

Cela dépend de la version de C, des spécificités de l'implémentation et du fait qu'il s'agisse de variables ou de valeurs littérales.

La première chose à comprendre est qu’il n’existe pas de littéraux entiers négatifs dans C. "-2147483648" est une opération moins unaire suivie d’un littéral entier positif.

Supposons que nous fonctionnons sur une plate-forme 32 bits typique, où int et long sont tous deux de 32 bits et long, de 64 bits, et considérons l'expression.

(- - (- 2147483648) == -2147483648)

Le compilateur doit trouver un type pouvant contenir 2147483648. Sur un compilateur C99 conforme, il utilisera le type "long long", mais un compilateur C90 peut utiliser le type "unsigned long".

Si le compilateur utilise le type long long, rien ne déborde et la comparaison est fausse. Si le compilateur utilise unsigned long, les règles enveloppantes non signées entrent en jeu et la comparaison est vraie.

0
plugwash