web-dev-qa-db-fra.com

Le comportement défini de soustraction d’entiers est-il non signé?

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?

90
jbcreix

Le résultat d'une soustraction générant un nombre négatif dans un type non signé est bien défini:

  1. [...] 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.

98
bdonlan

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:

enter image description here

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)
    // ...
109
LihO

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.

3
supercat

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.

3
AnT