problème avec des suffixes et comparaisons de types différents

a marqué ce sujet comme résolu.

Bonjour à tous,

un ami vient de me poser une colle. Il voulait des explications sur un passage du K&R. Je cite:

Les règles de conversion se compliquent dans le cas d'opérandes unsigned. Le problème est que les comparaisons entre les valeurs signées et non signées dépendent de la machine, parce qu'elles dépendent des tailles des divers types entiers. Par exemple, en supposant qu'un int soit représenté sur 16 bits et un long sur 32 bits, -1L < -1U, car 1U, qui est un int, est promu en un signed long. Mais -1L > -1UL, car -1L est promu en unsigned long, et prend donc l'apparence d'un nombre positif très grand.

Ce que je comprends c'est:

-1U : on prend l'int -1 et on le transforme en unsigned, donc en obtient un nombre positif grand et du coup -1L < -1U

-1UL : on prend l'int -1 et on le transforme en unsigned, on obtient ce même nombre positif grand et le met dans un long. -1L est promu en unsigned long pour la comparaison, on obtient alors un nombre positif très grand supérieur au nombre précédent: -1L > -1UL

Est-ce que mon explication (pas très claire je l'avoue !) est correcte ? Est-ce que l'ordre des suffixes dans -1UL à de l'importance ? Merci d'avance :)

+0 -0

TL;DR : Il me semble que la premiere expression est toujours vraie effectivement vraie ou fausse suivant le systeme, et que la deuxieme est toujours fausse. Ce passage du K&R est peut-etre un peu vieux, ou alors c'est une erreur (je ne l'ai pas sous la main) ?

Voila le point de vue theorique, ie. comment la sémantique du langage est definie par la norme (qui est plus actuelle).


-1U est l'expression formee de l'operateur unaire - applique à la constante 1U (a priori, ce n'est pas pareil que la conversion de -1 en unsigned, qu'on ecrirait (unsigned)-1).

The operand of the unary + or - operator shall have arithmetic type; of the ~ operator, integer type; of the ! operator, scalar type.

Deja, c'est une expression valide, car 1U a un type arithmetique (ie. entier ou flottant). De plus :

The result of the unary - operator is the negative of its (promoted) operand. The integer promotions are performed on the operand, and the result has the promoted type.

La promotion entiere concerne les types "plus petits" qu'un int, ce qui n'est pas le cas ici. Le "type promu" reste donc unsigned int.
Il faut donc chercher ailleurs pour savoir quel sens donner a la "negation de l'operande" quand celle-ci a un type non signe. Apres un peu de recherche, il s'agirait de :

A computation involving unsigned operands can never overflow, because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting type.

Ainsi, -1U vaut UINT_MAX - 1 (au sens de l'arithmetique usuelle).


Regardons maintenant l'expression (-1L < -1U). Les deux operandes -1L et -1U ont un type reel, donc c'est correct.

If both of the operands have arithmetic type, the usual arithmetic conversions are performed.

On applique donc les conversions arithmetiques usuelles aux deux operandes. Avant de les citer, voila une propriete du "rank of a type" qui apparait dans ce qui suit :

rank(signed char) = rank(unsigned char) < rank(short) = rank(unsigned sort) < rank(signed) = rank(unsigned) < rank(long) = rank(unsigned long) < rank(long long) = rank(unsigned long long)

First, if the corresponding real type of either operand is long double, the other operand is converted, without change of type domain, to a type whose corresponding real type is long double. (ne s'applique pas ici)

Otherwise […] (d'autres conditions qui ne s'appliquent pas ici)

Otherwise, the integer promotions are performed on both operands (sans effet ici). Then the following rules are applied to the promoted operands:

  • If both operands have the same type, then no further conversion is needed. (ce n'est pas le cas, l'une a le type unsigned int, l'autre le type long int)

  • Otherwise, if both operands have signed integer types or both have unsigned integer types, the operand with the type of lesser integer conversion rank is converted to the type of the operand with greater rank. (ce n'est pas le cas, l'une a un type signe, l'autre non signe)

  • Otherwise, if the operand that has unsigned integer type has rank greater or equal to the rank of the type of the other operand, then the operand with signed integer type is converted to the type of the operand with unsigned integer type. (ce n'est pas le cas, long int a un rang superieur a unsigned int, qui est signe)

  • Otherwise, if the type of the operand with signed integer type can represent all of the values of the type of the operand with unsigned integer type, then the operand with unsigned integer type is converted to the type of the operand with signed integer type. (si long int peut representer toutes les valeurs de unsigned int, cette regle s'applique)

  • Otherwise, both operands are converted to the unsigned integer type corresponding to the type of the operand with signed integer type. (sinon, c'est celle-ci qui s'applique)

Dans l'exemple de systeme du K&R, un long peut representer toutes les valeurs d'un unsigned, donc -1U est converti en long. Or on a vu que -1U valait UINT_MAX - 1, donc cette derniere expression typee en long est effectivement un "grand" nombre, tandis que -1L est negatif. D'ou : (-1L < -1U) est evalue a vrai.

Sur un systeme ou long ne peut pas representer toutes les valeurs d'un unsigned, les deux operandes seraient converties en unsigned long, et dans ce cas, en vertu des conversions vers un type non signe, on aurait toujours dans l'arithmetique usuelle -1L = ULONG_MAX - 1 et -1U = UINT_MAX - 1. Dans ce cas aussi, (-1L < -1U) est donc aussi evaluee a vrai. Etant donne que UINT_MAX <= ULONG_MAX, on aura dans ce cas -1U <= -1L donc l'expression initiale est fausse.

En particulier, je t'invite a mediter sur le resultat suivant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// a.c
#include <stdio.h>
#include <limits.h>

int main(void)
{
  printf("%ld %u\n", LONG_MAX, UINT_MAX);
  printf("%d\n", -1U <= -1L);
  return 0;
}
1
2
3
4
5
6
$ gcc -m32 a.c && ./a.out
2147483647 4294967295
1
$ gcc a.c && ./a.out
9223372036854775807 4294967295
0

Exercice : expliquer avec les regles ci-dessus. :p


Si maintenant on prend l'expression (-1L > -1UL), cette fois c'est le troisieme point des conversions arithmetiques usuelles qui s'applique. Les deux operandes sont converties en unsigned long. Sauf erreur de ma part, dans ce cas -1L vaut ULONG_MAX - 1 et -1UL vaut… ULONG_MAX - 1. Donc je pense que l'expression -1L > -1UL est au contraire toujours evaluee a faux.


Sinon, pour ta derniere question, l'ordre des suffixes n'a ici pas d'importance : 1UL et 1LU sont equivalents.


+0 -0

Salut,

TL;DR : Il me semble que la premiere expression est toujours vraie, et que la deuxieme est toujours fausse. Ce passage du K&R est peut-etre un peu vieux, ou alors c'est une erreur (je ne l'ai pas sous la main) ?

Lucas-84

À mon avis, il y a effectivement une erreur, je pense que le deuxième L est de trop dans l'expression -1L > -1UL (une faute de frappe peut-être ?).

Dans ce cas aussi, (-1L < -1U) est donc aussi evaluee a vrai.

Lucas-84

Dans ce cas là il me semble justement que c'est faux, non ?

+0 -0

À mon avis, il y a effectivement une erreur, je pense que le deuxième L est de trop dans l'expression -1L > -1UL (une faute de frappe peut-être ?).

Euh si on enleve le deuxieme L, je ne vois pas la difference avec l'autre expression.

Dans ce cas aussi, (-1L < -1U) est donc aussi evaluee a vrai.

Lucas-84

Dans ce cas là il me semble justement que c'est faux, non ?

Taurre

Ah oui, tu as raison, je me suis emmele les pinceaux, merci ! Je vais editer ca.

Pour preciser, le cas -1L == -1UL est le plus probable dans la situation ou long ne peut pas representer toutes les valeurs d'un unsigned.

Theoriquement je pense qu'on pourrait avoir -1L > -1U. Cela voudrait dire exactement ULONG_MAX > UINT_MAX > LONG_MAX. Ces dernieres inegalites impliquent qu'on a strictement plus de bits de padding dans un long que dans un unsigned long (les tailles maximales sont necessairement de la forme 2^k - 1 par 6.2.6.2).

Exercice 2 (non trivial) : trouver un systeme qui verifie ca, donc. :p

Je viens de regarder dans mon K&R qui est la seconde édition en anglais, je n'ai pas fait de faute en recopiant.

En regardant ici par exemple, la citation parle plutot de comparer -1L et 1UL, ce qui n'est pas pareil. Mais c'est ptete une histoire d'editions.

En tout cas je galère malgré les explications de Lucas. Je suis en train de programmer quelques tests pour comprendre tout les mécanismes.

matthieuj

Les regles de conversion en C c'est pas un sujet facile, j'ai pas ecrit ma derniere reponse en 5 minutes, hein. :p (et en plus y avait des erreurs)
L'important c'est de savoir qu'il existe des regles de conversion arithmetiques usuelles, apres si on va dans le detail, il y a plein de choses qu'on ne peut pas affirmer et ca devient vite casse-tete. T'as bien de raison d'experimenter, deja en alternant du 32/64 bits tu devrais avoir de quoi tester les cas les plus courants.


[EDIT] Pour resumer :

  • Si LONG_MAX >= UINT_MAX (la plupart des systemes actuels compatibles x86-64 verifient ca), alors -1L < -1U (conversion vers long, regle 4)
  • Si LONG_MAX < UINT_MAX (c'est par exemple le cas sur la plupart des systemes ou sizeof(int) == sizeof(long)), alors -1U <= -1L (conversion vers unsigned long, regle 5). De plus, on a rarement l'inegalite stricte.
  • Dans tous les cas, -1UL == -1L (conversion vers unsigned long, regle 3).
+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte