web-dev-qa-db-fra.com

Pourquoi le signe est-il différent après la soustraction de non signé et signé?

unsigned int t = 10;
int d = 16;
float c = t - d;
int e = t - d;

Pourquoi la valeur de c est-elle positive mais e est-elle négative?

41
Eugene Kolombet

Commençons par analyser le résultat de t - d.

t est un unsigned int tandis que d est un int, donc pour faire un calcul arithmétique sur eux, la valeur de d est convertie en un unsigned int (Les règles C++ disent que les non-signés obtiennent une préférence ici). Nous obtenons donc 10u - 16u, Qui (en supposant que int à 32 bits) tourne autour de 4294967290u.

Cette valeur est ensuite convertie en float dans la première déclaration et en int dans la seconde.

En supposant l'implémentation typique de float (IEEE 32 bits à simple précision), sa valeur représentable la plus élevée est approximativement 1e38, Donc 4294967290u Se situe bien dans cette plage. Il y aura des erreurs d'arrondi, mais la conversion en float ne débordera pas.

Pour int, la situation est différente. 4294967290u Est trop volumineux pour tenir dans un int. L'enveloppement a donc lieu et nous revenons à la valeur -6. Notez qu'un tel bouclage n'est pas garanti par la norme: la valeur résultante dans ce cas est définie par l'implémentation.(1)Cela signifie que la valeur du résultat revient au compilateur, mais elle doit être documentée.


(1) C++ 17 (N4659), [conv.integral] 7.8/3:

Si le type de destination est signé, la valeur n'est pas modifiée si elle peut être représentée dans le type de destination. sinon, la valeur est définie par l'implémentation.

64
Angew

Tout d’abord, vous devez comprendre "conversions arithmétiques usuelles" (ce lien est pour C, mais les règles sont les mêmes en C++). En C++, si vous faites de l'arithmétique avec des types mélangés (vous devriez éviter cela autant que possible, soit dit en passant), il existe un ensemble de règles qui décident du type dans lequel le calcul est effectué.

Dans votre cas, vous soustrayez un int signé d'un int non signé. Les règles de promotion indiquent que le calcul réel est effectué à l'aide de unsigned int.

Donc, votre calcul est 10 - 16 en arithmétique non signée. L'arithmétique non signée est une arithmétique modulo, ce qui signifie qu'elle tourne autour. Donc, en supposant que votre int typique de 32 bits, le résultat de ce calcul est 2 ^ 32 - 6.

C'est la même chose pour les deux lignes. Notez que la soustraction est complètement indépendante de l'affectation; le type à gauche n'a absolument aucune influence sur le déroulement du calcul. C'est une erreur courante pour les débutants de penser que le type situé à gauche influence le calcul; mais float f = 5 / 6 est égal à zéro, car la division utilise toujours l'arithmétique entière.

La différence est donc ce qui se passe pendant la mission. Le résultat de la soustraction est implicitement converti en float dans un cas et en int.

La conversion en float tente de trouver la valeur la plus proche de la valeur réelle que le type peut représenter. Ce sera une très grande valeur; pas tout à fait celle de la soustraction initiale.

La conversion en int indique que si la valeur entre dans la plage de int, la valeur sera inchangée. Mais 2 ^ 32 - 6 est beaucoup plus grand que le 2 ^ 31 - 1 qu'un int peut 32 bits, vous obtenez donc l'autre partie de la règle de conversion, qui dit que la valeur résultante est définie par l'implémentation. C'est un terme dans la norme qui signifie "différents compilateurs peuvent faire différentes choses, mais ils doivent documenter ce qu'ils font".

À toutes fins utiles, tous les compilateurs que vous rencontrerez diront que le modèle de bits reste identique et qu’il est simplement interprété comme signé. En raison de la manière dont l'arithmétique du complément à 2 fonctionne (c'est-à-dire que presque tous les ordinateurs représentent des nombres négatifs), le résultat obtenu est le -6 que vous êtes en droit d'attendre du calcul.

Mais tout ceci est un très long chemin pour répéter le premier point, qui est "ne faites pas de l'arithmétique de type mixte". Commencez explicitement par transtyper les types en types dont vous savez qu’ils feront le bon choix.

15
Sebastian Redl