CONVERSION BINAIRE/DECIMAL/HEXADECIMAL - TRES SIMPLE [DJGPP]

BeLZeL Messages postés 110 Date d'inscription mardi 10 octobre 2000 Statut Membre Dernière intervention 20 décembre 2005 - 10 oct. 2004 à 15:28
Pigeo Messages postés 1 Date d'inscription lundi 17 juillet 2006 Statut Membre Dernière intervention 22 janvier 2007 - 22 janv. 2007 à 16:54
Cette discussion concerne un article du site. Pour la consulter dans son contexte d'origine, cliquez sur le lien ci-dessous.

https://codes-sources.commentcamarche.net/source/10065-conversion-binaire-decimal-hexadecimal-tres-simple-djgpp

Pigeo Messages postés 1 Date d'inscription lundi 17 juillet 2006 Statut Membre Dernière intervention 22 janvier 2007
22 janv. 2007 à 16:54
Il y a un certain nombre d'autres intérets à utiliser cette astuce, et un certain nombre d'applications autres que la conversion binaire/decimal/hexadecimal... Vu que personne n'en a parlé jusque ici, j'ai pas pu m'empecher de faire ce post :-) Pour ceux qui ont toujours des petits problemes de comprehension du fonctionnement de ce code, rendez-vous à la fin de mon post.

Un autre intérêt de l'astuce citée par gorgonzola, et non des moindres, c'est la vitesse d'execution du code... Si vous comparez la vitesse d'execution de ce code source par rapport aux méthode classique des puissances de deux (ou des décallage de bits et masques binaires), vous verrez que ce code source est plus rapide :-) (suivant si vous activez les optimisations ou non, et suivant le niveau d'optimisation l'ecart sera plus ou moins grand mais en tout cas ce code est toujours plus rapide que la méthode classique).

Deux autres applications de cette astuce :

- pour convertir un entier long (32 bits) en entier court (16 bits), ou un char (8 bits) en entier long (32 bits), etc. Avec le code suivant :
 typedef union NEWINT32		
 {
 	unsigned int32 entier;	/* 32bits */
 	struct
 	{
 		unsigned char  octet1 : 8;
 		unsigned char  octet2 : 8;
 		unsigned char  octet3 : 8;
 		unsigned char  octet4 : 8;
 	};
 	struct			
 	{
 		unsigned int16	mot1 : 16;	
 		unsigned int16	mot2 : 16;     
 	};
 };



- pour convertir un entier (16, 32, ou 64 bits) de Little Endian à Big Endian, et vice versa... Sur processeur Intel, dans un entier, les octets de poids faibles sont stockés en premier, suivi des entiers de poids fort. Sur Motorola, c'est l'inverse. (a moins que ca ne soit le contraire, je ne me souviens jamais !) Du coup quand vous envoyez un paquet IP d'un mac à un PC et vice versa... il faut bien que l'adresse IP (32 bits) soient stockée d'une certain manière dans le paquet IP... Et du coup votre ordi fait des conversions Big endian / little endian en permanence (les fonctions qui font ca c'est ntohs, ntohl, htonl, htons... voir http://www.codeproject.com/cpp/endianness.asp par exemple.)

Maintenant il y en a qui se demandent peut etre pourquoi ce code est plus optimisé que la méthode classique ? Et bien, lorsque ce code source est compilé en langage machine, en fait le compilateur se contente de faire le strict minimum pour accéder au bits demandés. Tandis qu'avec la méthode "classique" des puissances de deux ou des décallage de bits et masques binaires, le compilateur n'est pas en mesure de comprendre "sémantiquement" ce qu'on est en train de faire, et traduit notre code "tel quel" en langage machine (un peu comme une traduction mot à mot dans Babelfish, quoi, vous imaginez le résultat...) du coup on se retrouve avec un code assembleur super compliqué tout ca pour accéder à un bit d'un octet !

En règle général, quand vous avez le choix entre un code tres court, avec tres peu d'instructions, et un autre beaucoup plus gros, avec plus d'instructions, qui font tous les deux la meme chose, celui qui est plus petit a toutes les chances d'etre mieux traduit en langage machine par le compilateur, surtout s'il s'agit d'un mauvais compilateur (qui ne fait pas l'effort d'essayer de "comprendre" le code source et qui ne possède pas de "vision globale" du code source).

D'ailleurs, meme si cette méthode n'etait pas plus claire et plus lisible que les méthodes classiques, et meme si elle n'etait pas plus optimisée que les méthodes classiques... elle serait quand meme mieux ! :-) Pourquoi ? Parce que si demain vous voulez optimiser un compilateur ou bien faire votre propre compilateur pour un nouveau type de processeur, et que ce processeur possède une instruction en langage machine qui permet de tester un bit donné dans un octet, vous pourrez tres facilement faire en sorte que quand le compilateur voit "ma_variable.bit3" dans le code source, c'est traduit en une seule instruction machine, tandis qu'avec les méthodes classiques, vous pourrez toujours vous accrocher pour faire en sorte que le compilateur reconnaisse qu'il s'agit de l'extraction d'un bit dans un octet et qu'il arrive à optimiser tout ca pour le traduire en une seule instruction machine, au lieu de faire bêtement des décallages, des masques, patin couffin, tout ca pour arriver au meme resultat au final. L'optimisation des compilateurs c'est un sujet tres délicat et il est toujours possible de faire mieux dans ce domaine là. Sur la plupart des compilateurs du marché, meme en compilant avec les options d'optimisations au niveau max, les optimisations réalisées sont vraiment très en-dessous de ce qu'il serait theoriquement possible de faire. Autrement dit, ce code là a toute les chances de tourner de plus en plus vite lorsque les compilateurs seront plus optimisés ou que les processeurs évolueront. Les méthodes classiques, en revanche, ont toutes les chances de ne pas "se faire optimiser" aussi vite, donc l'écart de performance entre les deux ne pourra que devenir de plus en plus important :-)

Maintenant, ce qui est dommage... C'est que partiquement PERSONNE n'utilise cette astuce !!! Et ca c'est vraiment VRAIMENT dommage. J'ai bossé dans plusieurs grandes boites d'informatique, et je peux vous dire que tout le monte utilise la méthode classique avec les décallages et les masques de bits. Moi meme j'avoue que par le passé, avant de connaitre cette astuce, j'ai fait la meme boulette que tous ces autres informaticiens. Et c'est vraiment couillon, parce que c'est juste par ignorance, autrement il n'y a aucune raison de ne pas utiliser cette astuce. Au contraire, elle est beaucoup plus lisible et simple à écrire, plus consise ! D'ailleurs sur le site que j'ai mentionné plus haut, http://www.codeproject.com/cpp/endianness.asp, ils donnent en code source un truc barbare du style (((nLongNumber&0x000000FF)<<24)+((nLongNumber&0x0000FF00)<<8)+ ((nLongNumber&0x00FF0000)>>8)+((nLongNumber&0xFF000000)>>24))... BERK !!! ca me donne la nausée, j'ai meme pas envie d'essayer de déchiffrer ces hiéroglyphes !!! lol

Enfin bon, que voulez-vous... aujourd'hui tout le monde s'en fou royalement de faire du code optimisé, et le nombre de ligne de codes d'un programme est un argument marketing... Si votre programme fait plusieurs millions de lignes de codes, on va vous admirer, alors que le type d'a coté qui a fait le meme programme en plus rapide mais avec 4 fois moins de lignes de code il va passer completement inappercu. Que voulez-vous ! Pas étonnant apres qu'on se retrouve avec un répertoire Windows qui pèse plusieurs Gigas, et que rien que pour afficher la liste des disques dur dans l'explorateur mon Pentium 4 dernier cri rame sa mère qu'il en peut plus... ca met plusieurs secondes ! Et encore j'ai un giga de RAM. C'est devenu indispensable aujourd'hui, avec tous ces programmes obèses mal programmés qui sont devenus boulimiques de RAM. Bref, et là je sens les linuxiens qui rigolent dans leur coin en se disant que Windows c'est de la merde et que Linux c'est merveilleux c'est génial c'est carrément top... ouais, bin, ils feraient mieux de jeter un coup d'oeuil au code source... Hem, à l'epoque ou je bidouillais pas mal dans le kernel de linux et tout ca, bin je peut vous dire que les conversions binaires/hexa/decimal elles etaient aussi faites avec la methode classique et non pas avec cette astuce ! :-) Comme quoi, je vous dit, PERSONNE n'utilise cette astuce, et c'est vraiment regrettable :-(

en fait le gros "# typedef union NEWCHAR" c'est juste une structure qui superpose dans le meme espace mémoire (de 8 bits) à la fois un char, 8 booleens, et 2 entiers sur 4 bits... Comme ils occupent le meme espace mémoire, quand on écrit quelque chose dans un des booleens, ca modifie aussi le char et les 2 hexa. C'est un peu comme si c'etait des pointeurs qui pointent vers la meme case mémoire, sauf que c'est pas des pointeurs (C'est le compilateur qui se démerde pour faire en sorte que ces variables soient stockées dans la meme "case" mémoire de 8 bits). Ce qui est bien c'est que la déclaration de cette union, on peut la mettre dans un fichier include, et ensuite on peut l'utiliser à plein d'endroits, jusque en écrivant une ligne, "NEWCHAR octet;" et accéder bit par bit ou 4bits par 4bits, sans se prendre la tete avec des lignes de code compliquées :-)

Voila voila, je crois que j'ai fait le tour de ce que j'avais à dire. Bon, sinon en ce qui concerne la conversion de big endian en little endian, il y a une autre méthode qui est encore plus rapide (et que personne n'utilise non plus), mais bon je vais pas m'attarder là dessus, mon post est deja beaucoup trop long comme ca !
deck_bsd Messages postés 1243 Date d'inscription jeudi 31 mars 2005 Statut Membre Dernière intervention 3 août 2016 2
1 août 2005 à 08:38
Je te remercie pour ton explication, je comprend mieu mainteant :D
BeLZeL Messages postés 110 Date d'inscription mardi 10 octobre 2000 Statut Membre Dernière intervention 20 décembre 2005
31 juil. 2005 à 11:00
Tout est dans la création, un peu spéciale, de la structure, avec "typedef UNION".
La structure permet d'utiliser les 3 unions en même temps, mais une seule initialisation est nécessaire.
C'est-à-dire que quand on fait octet.dec=<nombre>, on remplit également octet.bitX et octet.hexaX.
dec, bitX et hexaX prennent tous exactement 8 bits en mémoire.
Voilà pourquoi ca marche.
deck_bsd Messages postés 1243 Date d'inscription jeudi 31 mars 2005 Statut Membre Dernière intervention 3 août 2016 2
30 juil. 2005 à 15:54
Bonjour,


Voila, j'ai des petits problèmes de compréhension de ce code, je le comprend, ce que je comprend pas c'est comment ce fait til que ça fasse la conversion (je c c un peu bizar ma phrase lol).

J'espère que vous saurez combler mes lacunes.

++all
BeLZeL Messages postés 110 Date d'inscription mardi 10 octobre 2000 Statut Membre Dernière intervention 20 décembre 2005
10 oct. 2004 à 15:28
J'avais déjà entendu parlé de cette technique. Le problème, c'est que apparemment, ca ne compile pas "directement" sous DevCpp. J'ai remplacé les BOOL par UNSIGNED CHAR car chez moi, il arrive à trouver des BOOL négatifs (allez comprendre pourquoi...). En mettant des UNSIGNED CHAR partout, plus de pb de conversion de type.

Voilà l'équivalent de l'union pour DevCpp :
#include <windows.h> //<-------------
#include <stdio.h>   /* pour printf */
#include <conio.h>   /* pour getch   */

typedef union U_NEWCHAR //<-------------
{
    unsigned char   dec;    
    struct
    {
        unsigned char    bit8:1; //<-------------
        unsigned char    bit7:1; //<-------------
        unsigned char    bit6:1; //<------------- 
        unsigned char    bit5:1; //<-------------
        unsigned char    bit4:1; //<-------------
        unsigned char    bit3:1; //<-------------
        unsigned char    bit2:1; //<-------------
        unsigned char    bit1:1; //<-------------
    };
    struct
    {
        unsigned char    hexa2:4;   
        unsigned char    hexa1:4;  
    };
} NEWCHAR; //<-------------


Voiloi voilou :)
Rejoignez-nous