Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Division de nombre entiers signés par non signés

65 réponses
Avatar
Yann Renard
Bonjour à tous,

avec le code suivant, je m'attends à obtenir "-1" comme résultat.

#include <stdio.h>

int main(int argc, char** argv)
{
unsigned long long b = 5;
signed long long a = -5;
signed long long c = a / b;
printf("%lli\n", c);
return 0;
}

Or j'obtiens 3689348814741910322. La plateforme que j'utilise pour faire
ce test est Ubuntu Linux Lucid Lynx sur architecture x86_64.

# uname -a
Linux cervelet 2.6.32-25-generic #44-Ubuntu SMP Fri Sep 17 20:05:27 UTC
2010 x86_64 GNU/Linux

# gcc --version
gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3

Compilé sans optimisation. Est ce que je me trompe ? Ou est ce GCC qui
fait des bétises ?

Merci d'avance,
Yann

10 réponses

1 2 3 4 5
Avatar
Marc Boyer
Le 05-10-2010, Yann Renard a écrit :
Bonjour à tous,

avec le code suivant, je m'attends à obtenir "-1" comme résultat.

#include <stdio.h>

int main(int argc, char** argv)
{
unsigned long long b = 5;
signed long long a = -5;
signed long long c = a / b;
printf("%llin", c);
return 0;
}



Et non. Tu as une opération qui implique un signé et un non signé
de même taille. Il faut donc faire une conversion, soit du signé en
non signé, soit du non signé en signé.
La norme dit que c'est le signé qui sera convertit en non signé
(donc surement 2^64 - 6 dans ton cas). Puis on divise par 5.
Puis on convertit le non signé en signé (ce qui se faite sans modif
de la valeur).

Compilé sans optimisation. Est ce que je me trompe ? Ou est ce GCC qui
fait des bétises ?



gcc a surement plein de bugs, mais sans optimisation, sur une
architecture répandue et sur un code si simple, je pense qu'on
peut lui faire confiance.

Marc Boyer
--
En prenant aux 10% des francais les plus riches 12% de leurs revenus,
on pourrait doubler les revenus des 10% les plus pauvres.
http://www.inegalites.fr/spip.php?article1&id_mot0
Avatar
Yann Renard
On 10/05/2010 03:44 PM, Marc Boyer wrote:
Le 05-10-2010, Yann Renard a écrit :
Bonjour à tous,

avec le code suivant, je m'attends à obtenir "-1" comme résultat.

#include<stdio.h>

int main(int argc, char** argv)
{
unsigned long long b = 5;
signed long long a = -5;
signed long long c = a / b;
printf("%llin", c);
return 0;
}



Et non. Tu as une opération qui implique un signé et un non signé
de même taille. Il faut donc faire une conversion, soit du signé en
non signé, soit du non signé en signé.
La norme dit que c'est le signé qui sera convertit en non signé
(donc surement 2^64 - 6 dans ton cas). Puis on divise par 5.
Puis on convertit le non signé en signé (ce qui se faite sans modif
de la valeur).

Compilé sans optimisation. Est ce que je me trompe ? Ou est ce GCC qui
fait des bétises ?



gcc a surement plein de bugs, mais sans optimisation, sur une
architecture répandue et sur un code si simple, je pense qu'on
peut lui faire confiance.

Marc Boyer



Merci Marc pour cette réponse rapide.

J'aurais parié que le signé l'aurais emporté sur le non signé ;)

Bonne journée,
Yann
Avatar
Marc Boyer
Le 05-10-2010, Yann Renard a écrit :
On 10/05/2010 03:44 PM, Marc Boyer wrote:

J'aurais parié que le signé l'aurais emporté sur le non signé ;)



Ce choix de privilégier non signé sur signé m'a beaucoup interrogé.

Il a l'avantage de produire le débordement sur de petites valeurs
négatives, que l'on voit vite sur des tests unitaires, alors que
le choix contraire ne l'aurait fait apparaître sur de grandes valeurs,
que pas grand monde ne teste (jusqu'au jour où la fusée explose ;-) ).

Il a l'inconvénient de faire planter plein de code qui de toutes
façons ne manipuleront jamais de grandes valeurs (genre du nombre
de trimestres de cotisation d'un salarié ;-) ).

Mais un grand ancien va bien passer par là nous expliquer les enjeux
et l'histoire de ce choix.

Marc Boyer
--
En prenant aux 10% des francais les plus riches 12% de leurs revenus,
on pourrait doubler les revenus des 10% les plus pauvres.
http://www.inegalites.fr/spip.php?article1&id_mot0
Avatar
Jean-Marc Bourguet
Marc Boyer writes:

Le 05-10-2010, Yann Renard a écrit :
> On 10/05/2010 03:44 PM, Marc Boyer wrote:
>
> J'aurais parié que le signé l'aurais emporté sur le non signé ;)

Ce choix de privilégier non signé sur signé m'a beaucoup interrogé.



Il y a d'abord un choix de privilegier la valeur (autrement dit si le type
signe est capable de representer toutes les valeurs du type non signe,
c'est le type signe qui est utilise).

Ensuite, quand il n'y a pas un type capable de representer tout
l'intervalle possible de l'autre, utiliser le type non signe a l'avantage
d'etre toujours bien defini (plutot que defini par l'implementation ou non
specifie ou indefini, je ne sais plus -- je crains l'indefini a cause des
traps values).

A+

--
Jean-Marc
FAQ de fclc: http://www.levenez.com/lang/c/faq
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
-ed-
On 5 oct, 15:18, Yann Renard wrote:
Bonjour à tous,

avec le code suivant, je m'attends à obtenir "-1" comme résultat.

#include <stdio.h>

int main(int argc, char** argv)
{
     unsigned long long b = 5;
     signed long long a = -5;
     signed long long c = a / b;
     printf("%llin", c);
     return 0;

}

Or j'obtiens 3689348814741910322.



Ceci fait le résultat escompté :

#include <stdio.h>

int main (int argc, char **argv)
{
long long b = 5;
long long a = -5;
long long c = a / b;
printf ("%lldn", c);

return 0;
}

Il ne faut pas mélanger les signés et les non signés.
Avatar
Yann Renard
On 10/05/2010 04:58 PM, Yann Renard wrote:
On 10/05/2010 03:44 PM, Marc Boyer wrote:
Le 05-10-2010, Yann Renard a écrit :
Bonjour à tous,

avec le code suivant, je m'attends à obtenir "-1" comme résultat.

#include<stdio.h>

int main(int argc, char** argv)
{
unsigned long long b = 5;
signed long long a = -5;
signed long long c = a / b;
printf("%llin", c);
return 0;
}



Et non. Tu as une opération qui implique un signé et un non signé
de même taille. Il faut donc faire une conversion, soit du signé en
non signé, soit du non signé en signé.
La norme dit que c'est le signé qui sera convertit en non signé
(donc surement 2^64 - 6 dans ton cas). Puis on divise par 5.
Puis on convertit le non signé en signé (ce qui se faite sans modif
de la valeur).

Compilé sans optimisation. Est ce que je me trompe ? Ou est ce GCC qui
fait des bétises ?



gcc a surement plein de bugs, mais sans optimisation, sur une
architecture répandue et sur un code si simple, je pense qu'on
peut lui faire confiance.

Marc Boyer



Merci Marc pour cette réponse rapide.

J'aurais parié que le signé l'aurais emporté sur le non signé ;)

Bonne journée,
Yann



A propos de norme, ou peut on la trouver et y a t il un moyen efficace
de faire une recherche dedans pour trouver ce genre de réponse ?

Bonne journée,
Yann
Avatar
Marc Boyer
Le 05-10-2010, Jean-Marc Bourguet a écrit :
Marc Boyer writes:

Le 05-10-2010, Yann Renard a écrit :
> On 10/05/2010 03:44 PM, Marc Boyer wrote:
>
> J'aurais parié que le signé l'aurais emporté sur le non signé ;)

Ce choix de privilégier non signé sur signé m'a beaucoup interrogé.



Il y a d'abord un choix de privilegier la valeur (autrement dit si le type
signe est capable de representer toutes les valeurs du type non signe,
c'est le type signe qui est utilise).



Ca, c'est raisonnable.

Ensuite, quand il n'y a pas un type capable de representer tout
l'intervalle possible de l'autre, utiliser le type non signe a l'avantage
d'etre toujours bien defini



Oui, préférer le bug reproductible à une éventuelle bizarrerie sur
une architecture exotique. C'est très "C" comme approche. ;-)

Marc Boyer
--
En prenant aux 10% des francais les plus riches 12% de leurs revenus,
on pourrait doubler les revenus des 10% les plus pauvres.
http://www.inegalites.fr/spip.php?article1&id_mot0
Avatar
Jean-Marc Bourguet
Marc Boyer writes:

Oui, préférer le bug reproductible à une éventuelle bizarrerie sur une
architecture exotique. C'est très "C" comme approche. ;-)



C'est pas l'archi exotique, c'est l'optimiseur un peu trop agressif que je
crains.

A+

--
Jean-Marc
FAQ de fclc: http://www.levenez.com/lang/c/faq
Site de usenet-fr: http://www.usenet-fr.news.eu.org
Avatar
Antoine Leca
Le 05-10-2010, Yann Renard a écrit :
J'aurais parié que le signé l'aurais emporté sur le non signé ;)





Il faut mieux apprendre les règles que parier ou deviner.
En l'occurrence, en C, non signé a toujours gagné sur signé, même si
depuis la norme ANSI la domination est moins nette.


Marc Boyer écrivit :
Ce choix de privilégier non signé sur signé m'a beaucoup interrogé.



Cela me semble pourtant logique : le problème est l'étendue des valeurs
donc le nombre de bits, qui est la vraie limite. Et avec les nombres non
signés (qui représentent des adresses ou des tailles), on approche plus
les bornes qu'avec les nombres signés, qui sont plus généralistes.

Cela dans le contexte de la décision initiale, celle de DMR quand il y
introduit unsigned en 1972. Quelques années plus tard, au moment de la
norme ANSI, un autre facteur est venu compliquer la situation, la
possibilité d'avoir des entiers non signés avec diverses
représentations, dont certaines créant des situations d'exceptions sur
débordement : la possibilité de pouvoir définir un ensemble cohérent de
règles privilégiant les nombres signés sur les non signés sont devenues
quasiment nulles (dans le contexte de C). De ce fait, le comité a opté
pour l'affirmation de règles très claires sur les nombres non signés
(qui doivent impérativement utiliser l'arithmétique modulo n), et les
entiers signés suivent comme ils peuvent.

Après, toute règle a ses limites, en particulier ici c'est ptrdiff_t qui
pour bien faire dans la logique ambiante, nécessiterait un bit de plus
(pour permettre les deux sens pour la soustraction).
Il est impossible de parfaire toutes les contraintes ; et ptrdiff_t
n'est pas le type le plus utile du C ; mais il est très bien pour lancer
des discussions à rallonge sur comp.std.c ;-).


Il a l'avantage de produire le débordement sur de petites valeurs
négatives, que l'on voit vite sur des tests unitaires, alors que
le choix contraire ne l'aurait fait apparaître sur de grandes valeurs,
que pas grand monde ne teste (jusqu'au jour où la fusée explose ;-) ).



Remarque que le problème des grandes valeurs est peut-être estompé à ce
niveau, mais il reste pour l'opérateur * (et à un moindre niveau <<).


Il a l'inconvénient de faire planter plein de code qui de toutes
façons ne manipuleront jamais de grandes valeurs (genre du nombre
de trimestres de cotisation d'un salarié ;-) ).



Si tu as du code qui manipule des nombres de trimestres ou équivalent en
non signé, ÀMHA le souci ce n'est pas que cela va planter, ce n'est même
pas que la conception est complètement à revoir, c'est qu'il faut
d'urgence changer l'analyste-programmeur ;-) Le fait d'utiliser des
types non signés fait effectivement gagner quelques valeurs dans
l'intervalle (et c'était très important il y a quelques années avec les
quantités 16 bits) mais le prix à payer ce sont des conversions
intempestives. De ce fait, les programmeurs C apprennent vite à se
méfier des unsigned qui traînent, puis par ricochet des sizeof() et
autres size_t qui importent le même effet.


Mais un grand ancien va bien passer par là nous expliquer les enjeux
et l'histoire de ce choix.



Je sens comme une tentative (ratée) d'éviter les commentaires...


Antoine
Avatar
Marc Boyer
Le 06-10-2010, Antoine Leca a écrit :
Marc Boyer écrivit :
Ce choix de privilégier non signé sur signé m'a beaucoup interrogé.



Cela me semble pourtant logique : le problème est l'étendue des valeurs
donc le nombre de bits, qui est la vraie limite. Et avec les nombres non
signés (qui représentent des adresses ou des tailles), on approche plus
les bornes qu'avec les nombres signés, qui sont plus généralistes.



C'est ça: on privilégie un comportement robuste aux bornes,
au détriment de la généralité.

Cela dans le contexte de la décision initiale, celle de DMR quand il y
introduit unsigned en 1972. Quelques années plus tard, au moment de la
norme ANSI, un autre facteur est venu compliquer la situation, la
possibilité d'avoir des entiers non signés avec diverses
représentations, dont certaines créant des situations d'exceptions sur
débordement : la possibilité de pouvoir définir un ensemble cohérent de
règles privilégiant les nombres signés sur les non signés sont devenues
quasiment nulles (dans le contexte de C). De ce fait, le comité a opté
pour l'affirmation de règles très claires sur les nombres non signés
(qui doivent impérativement utiliser l'arithmétique modulo n), et les
entiers signés suivent comme ils peuvent.



Là encore: on privilégie les règles claires, la portabilité, quitte
à perdre les débutants.

Il a l'avantage de produire le débordement sur de petites valeurs
négatives, que l'on voit vite sur des tests unitaires, alors que
le choix contraire ne l'aurait fait apparaître sur de grandes valeurs,
que pas grand monde ne teste (jusqu'au jour où la fusée explose ;-) ).



Remarque que le problème des grandes valeurs est peut-être estompé à ce
niveau, mais il reste pour l'opérateur * (et à un moindre niveau <<).



Je ne vois pas en quoi le problème diffère avec la multiplication.
int i= -3;
int u= 6;
int idiv= u / i;
int imul= u * i;
Dans les deux cas, on a conversion du -3 en un grand signé, et l'opération
effectuée n'est surement pas celle que le débutant attendrait.

Si on avait pris la convention inverse (conversion en signé en cas de
tailles égales), seules les grandes valeurs de non signés causeraient
des surprises aux débutants.

Il a l'inconvénient de faire planter plein de code qui de toutes
façons ne manipuleront jamais de grandes valeurs (genre du nombre
de trimestres de cotisation d'un salarié ;-) ).



Si tu as du code qui manipule des nombres de trimestres ou équivalent en
non signé, ÀMHA le souci ce n'est pas que cela va planter, ce n'est même
pas que la conception est complètement à revoir, c'est qu'il faut
d'urgence changer l'analyste-programmeur ;-) Le fait d'utiliser des
types non signés fait effectivement gagner quelques valeurs dans
l'intervalle (et c'était très important il y a quelques années avec les
quantités 16 bits) mais le prix à payer ce sont des conversions
intempestives.



J'avais pris le nombre de trimestre, car il a cette amusante propriété
que pour 40 années de cotisation, il tient sur un char, et pour 42
(qui est arrivé en France), il faut un unsigned char.

Mais bon, plus généralement, c'est la question des valeurs réprésentant
des cardinaux, des décomptes, etc.

De ce fait, les programmeurs C apprennent vite à se
méfier des unsigned qui traînent, puis par ricochet des sizeof() et
autres size_t qui importent le même effet.



Sauf que, comme dit ailleurs, comme *alloc, str* et des soeurs prennent
des size_t en paramêtre ou valeur de retour, on est obligé d'y faire
attention.
Supposons que l'on stocke l'ensemble des revenus pour chaque trimestre
du salarié, dans une liste chainée ou un tableau dynamique. On va
surement vouloir stocker la taille de cet ensemble.
Dans quoi ? Dans un int ? Oui, mais si on utilise un conteneur
fait dans une bibliothèque, pourquoi aurait-il choisi un int ?

Mais un grand ancien va bien passer par là nous expliquer les enjeux
et l'histoire de ce choix.



Je sens comme une tentative (ratée) d'éviter les commentaires...



Plutôt un appel déguisé.
Cette question me taraude depuis des années.

Marc Boyer
--
En prenant aux 10% des francais les plus riches 12% de leurs revenus,
on pourrait doubler les revenus des 10% les plus pauvres.
http://www.inegalites.fr/spip.php?article1&id_mot0
1 2 3 4 5