Je suis tombé sur du code provenant de quelqu'un qui semble croire qu'il existe un problème pour soustraire un entier non signé d'un autre entier du même type lorsque le résultat serait négatif. Ce code serait donc incorrect même s’il s’avérait fonctionner sur la plupart des architectures.
unsigned int To, Tf;
To = getcounter();
while (1) {
Tf = getcounter();
if ((Tf-To) >= TIME_LIMIT) {
break;
}
}
C’est la seule citation vaguement pertinente du standard C que j’ai pu trouver.
Un calcul impliquant des opérandes non signés ne peut jamais déborder, car un résultat qui ne peut pas être représenté par le type entier non signé résultant est réduit modulo le nombre qui est supérieur à la valeur la plus grande pouvant être représentée par le type obtenu.
Je suppose que l’on pourrait prendre cette citation comme signifiant que lorsque le bon opérande est plus grand, l’opération est ajustée pour avoir un sens dans le contexte de nombres tronqués modulo.
c'est à dire.
0x0000 - 0x0001 == 0x 1 0000 - 0x0001 == 0xFFFF
par opposition à l'utilisation de la sémantique signée dépendant de l'implémentation:
0x0000 - 0x0001 == (non signé) (0 + -1) == (0xFFFF mais aussi 0xFFFE ou 0x8001)
Quelle ou quelle interprétation est la bonne? Est-ce défini du tout?
Le résultat d'une soustraction générant un nombre négatif dans un type non signé est bien défini:
- [...] Un calcul impliquant des opérandes non signés ne peut jamais déborder, car un résultat qui ne peut pas être représenté par le type entier non signé résultant est réduit modulo le nombre qui est supérieur à la plus grande valeur pouvant être représentée par le type résultant. (ISO/IEC 9899: 1999 (E) §6.2.5/9)
Comme vous pouvez le voir, (unsigned)0 - (unsigned)1
est égal à -1 modulo UINT_MAX + 1 ou, en d'autres termes, UINT_MAX.
Notez que bien que "Un calcul impliquant des opérandes non signés ne puisse jamais déborder", ce qui peut laisser supposer qu'il s'applique uniquement au dépassement de la limite supérieure, il est présenté sous la forme motivation pour la liaison réelle. partie de la phrase: "un résultat qui ne peut pas être représenté par le type entier non signé résultant est réduit modulo le nombre qui est supérieur à la valeur la plus grande pouvant être représentée par le type résultant." Cette phrase n'est pas limitée au dépassement de la limite supérieure du type et s'applique également aux valeurs trop basses pour être représentées.
Lorsque vous travaillez avec unsigned types, arithmétique modulaire (également connu sous le nom "wrap around" comportement) est en cours. Pour comprendre ceci arithmétique modulaire, il suffit de regarder ces horloges:
9 + 4 = 1 ( 13 mod 12), donc dans l'autre sens c'est: 1 - 4 = 9 ( 3 mod 12). Le même principe est appliqué lorsque vous travaillez avec des types non signés. Si le type de résultat est unsigned
, l'arithmétique modulaire a alors lieu.
Examinons maintenant les opérations suivantes stockant le résultat sous forme de unsigned int
:
unsigned int five = 5, seven = 7;
unsigned int a = five - seven; // a = (-2 % 2^32) = 4294967294
int one = 1, six = 6;
unsigned int b = one - six; // b = (-5 % 2^32) = 4294967291
Lorsque vous voulez vous assurer que le résultat est signed
, stockez-le dans la variable signed
ou convertissez-le en signed
. Lorsque vous voulez obtenir la différence entre les nombres et vous assurer que l'arithmétique modulaire ne sera pas appliquée, vous devriez envisager d'utiliser la fonction abs()
définie dans stdlib.h
:
int c = five - seven; // c = -2
int d = abs(five - seven); // d = 2
Soyez très prudent, surtout lors de la rédaction des conditions, car:
if (abs(five - seven) < seven) // = if (2 < 7)
// ...
if (five - seven < -1) // = if (-2 < -1)
// ...
if (one - six < 1) // = if (-5 < 1)
// ...
if ((int)(five - seven) < 1) // = if (-2 < 1)
// ...
mais
if (five - seven < 1) // = if ((unsigned int)-2 < 1) = if (4294967294 < 1)
// ...
if (one - six < five) // = if ((unsigned int)-5 < 5) = if (4294967291 < 5)
// ...
Avec des nombres non signés de type unsigned int
ou plus, en l’absence de conversion de type, a-b
est défini comme donnant le nombre non signé qui, ajouté à b
, donnera a
. La conversion d'un nombre négatif en non signé est définie comme donnant le nombre qui, ajouté au nombre initial avec inversion de signe, donnera zéro (la conversion de -5 en non signé donnera donc une valeur qui, ajoutée à 5, donnera zéro) .
Notez que les nombres non signés inférieurs à unsigned int
peut être promu à taper int
avant la soustraction, le comportement de a-b
dépendra de la taille de int
.
Eh bien, la première interprétation est correcte. Cependant, votre raisonnement sur la "sémantique signée" dans ce contexte est faux.
Encore une fois, votre première interprétation est correcte. L'arithmétique non signée suit les règles de l'arithmétique modulo, ce qui signifie que 0x0000 - 0x0001
est évalué à 0xFFFF
pour les types non signés 32 bits.
Cependant, la deuxième interprétation (celle basée sur la "sémantique signée") est également requise pour produire le même résultat. C'est à dire. même si vous évaluez 0 - 1
dans le domaine de type signé et obtenez -1
comme résultat intermédiaire, ceci -1
est toujours nécessaire pour produire 0xFFFF
quand plus tard, il est converti en type non signé. Même si certaines plates-formes utilisent une représentation exotique pour les entiers signés (complément à 1, magnitude signée), cette plate-forme est toujours tenue d'appliquer des règles d'arithmétique modulo lors de la conversion de valeurs d'entiers signés en non signés.
Par exemple, cette évaluation
signed int a = 0, b = 1;
unsigned int c = a - b;
est toujours garanti pour produire UINT_MAX
dans c
, même si la plateforme utilise une représentation exotique pour les entiers signés.