Conversion entier - chaine ( string )

Publié le par JJ

Bonjour !
 
aujourd'hui je vous soumets quelques méthodes pour changer la représentation des entiers en mémoire. En effet s'il n'est pas difficile d'écrire sur le shell (terminal, console) un entier, il arrive souvent qu'on ai besoin pour une raison ou une autre de former des chaines de caractères  avec des entiers. Le C n'est pas un langage tres souple de ce côté là et requière un peu de ruse.

ITOA()
itoa() est une primitive documentée ici ou . Cette fonction est assez courante, c'est pour celà que j'en parle. Cependant cette fonction n'est pas conforme au standard ANSI-C alors je ne vous donnerais pas d'exemple. Il faudrait posséder MicroSoft DOS pour le programmer et ce n'est pas mon cas.

Étrangement, les fonctions atoi() et atof() existent dans stdlib.h mais pas itoa().


UNE FONCTION PERSONNELLE:

Ce n'est pas forcement une mauvaise idée, cependant l'intérêt n'est pas flagrant du tout. L'idée est de faire une ITOA() à la main...

ci dessous le listing.


/*
converti un nombre vers son écriture en chiffre dans une base de 2 à 16
la valeur de retour est NULL en cas d'erreur ou un pointeur vers la chaîne résultat
const long x = l'entier à convertir
const unsigned short base = la base dans laquelle écrire le nombre, notez que cette fonction n'ajoute pas de préfixe de base.
char* resultat = tableau d'entier dans lequel le résultat sera écrit si cette valeur est renseignée
*/
char* convertion(const int x,const unsigned short base, char* resultat){
 
char HEX[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

/* espace de travail */
int i, j, cpt, reste; /* reste est compris entre 0 et 16*/
char chaine[34]; /*en base 2 le plus long int se représente sur 32 octets + 1  octet final + 1 octet de signe ...*/
int quotient = x;
   
     
/* vérification de la base. il faut que ça cadre avec "HEX" */
if ((base<2)||(16<base)){
        printf("base non valide \n");
        /* ici il faudrait presque mettre un exit... ce qui est un peut violent*/
        return NULL;
}
   
   
   
    /* parce qu'on ne travaille qu'avec des entiers positifs */
if (quotient<0){
        quotient = -quotient;
}
   
   
/*initialisations*/
cpt = 0;
   
   
/* calculs */
while (quotient!=0){
      reste = quotient % base ; /* sinon: reste -= (quotient*base) */
 
            /*pour passer à la ligne suivante*/
            quotient = (int) quotient/base;
        
         /*operation qui nous interesse*/          
            chaine[cpt]=HEX[reste]; 
            cpt++;
}
   
/*ajout du signe*/
if (x<0){ /* si c'est négatif*/
        chaine[cpt]='-';
        cpt++;
}
   
     
/* si l'utilisateur ne précise pas un pointeur pour stoquer la chaine alors on crée une nouvelle "chaîne" */
/* ceci est ce que j'appelle un piège à programmeur. fuite de mémoire garantie! */
if (resultat == NULL){
        resultat = malloc(cpt*sizeof(char));
       
        /*inverse le sens de chaîne en le copiant dans la chaîne de resultat*/
        if (resultat == NULL){
            /*l'idéal serai d'écrire dans le flux d'erreur */
            printf("Ne peut pas allouer de la memoire. \n");
            /* ici il faudrait presque mettre un exit... ce qui est un peut violent*/
            return NULL;   
        }
}
   
   
/*inversion du sens de lecture de la chaîne pour obtenir le résultat*/
for(i = 0, j=cpt-1 ; i< cpt ;i++, j--){
        resultat[j]=chaine[i];
}
resultat[cpt]=0; /*caractère de fin de chaîne a NE PAS oublier normalement on écrit ' 0 '  */
   
return resultat;

}


Un des grand avantage de cette fonction est la capacité à l'appeler comme ceci:
    char* pt = conversion(nombre,10,NULL);
et la chaîne produite aura directement la meilleure taille. Sinon on a un problème de prédiction de la longueur du tableau de caractères à donner en zone d'écriture.

Pour trouver une implémentation différente de la mienne  suivez simplement  http://www.google.com/codesearch?q=itoa.


LES FONCTIONS  STANDARTS

sprintf() et snprintf sont largement ce qui se fait de plus simple. d'une par la page de man est claire, d'autre par lorsqu'on connaît printf() alors la messe est dite.

extrait du man:      
       int sprintf (char *str, const char *format, ...);
       int snprintf (char *str, size_t size, const char *format,...);

Le problème de ses deux fonctions est qu'il faut prévoir la taille du tampon à l'avance à ma connaissance.

Pour remédier à cela je conseille l'utilisation des logarithmes dans la base souhaitée. ln logarithme népérien,

LongeurChiffre = PartieEntiere( ln(abs(nombre))/ln(base) ) + 2


Ceci dit une estimation bien faite de la taille de vos nombres peut vous permettre de considérer qu'on n'aura jamais plus besoin de 12 char pour écrire MAX_INT en base 10.



ANALYSE

    Laquelle de ses propositions choisir? Ces fonctions sont à peu près équivalentes, hormis le fait que sprintf et snprintf offre des fonctionnalités avancées non couvertes par mon code. Mon code propose lui des bases non disponibles dans la famille printf qui se limite aux bases binaire, octales, hexa.
     Si on s'intéresse aux performances alors printf n'est pas  forcement  la plus  mal placée, heureusement. Observons le tableau ci dessous:

Performance des méthodes de conversion 
   sans optimisation
 -O1  -O2  -O3
 conversion avec resultat!=NULL
  5.355799
 2.459770
2.438106
2.449575
 conversion avec resultat==NULL  6.004145
 3.198292
3.275354
3.313968
 sprint()
 3.103214
 3.094078
3.193490
3.136056
 snprintf
 3.223397
 3.149992
3.111499
3.117731

Ces mesures ne sont pas très  représentatives. environs 15% de variation d'une exécution à l'autre. J'ai une machine multimédia qui fait autre chose que mon calcul pendant la mesure, et on peut difficilement l'en empêcher, c'est pourquoi on itère de nombreuses fois le calcul dans l'espoir qu'on moyenne les perturbations  ...


Ces mesures sont issues du programme suivant, pour 10000000 calculs:



#include <stdio.h> 
#include <stdlib.h>

/*!
 * include d'une façon ou d'une autre les codes pour
 * conversion() et pour le chronométrage.
 */

const int X = 500;
const int L =100;

int main (int argc , char** argv){
  
/* des entiers arbitraires */     
    int a = -123456789;
    int b = -153347537;
    int c = -135455594;
    int d = -158532721;
    char res1[X][L+1]; /*le résultat peut être une chaîne de 16 octets*/
 
    int i,j;
    double t;
    double t1,t2,t3,t4;   
    t1 = 0.0;    t2 = 0.0;
    t3 = 0.0;    t4 = 0.0;
     
    for (j=0; j<20000; j++){
        a+=100;
        b+=100;
        c+=100;
        d+=100;
       
        chrono_start();
        for (i=0; i<X ;  i++) {
            free (convertion(d+i,10, NULL));
        }
        t = chrono_stop();
        t4+=t;
         
        /* test méthode standard non sécurisée*/
        chrono_start();
        for (i=0; i<X ;  i++) {
            sprintf(res1[i],"%d",b+i);
        }
        t = chrono_stop();
        t2+=t;
       
       
        /* test méthode perso*/
        chrono_start();
        for (i=0; i<X ;  i++) {
            convertion(a+i,10, res1[i]);
        }
        t = chrono_stop();
        t1+=t;
         
        /* test méthode standard sécurisée*/
        chrono_start();
        for (i=0; i<X ;  i++) {
            snprintf(res1[i],L,"%d",c+i);
        }
         
        t = chrono_stop();
        t3+=t;
    }       
   
    printf("test sur %d * %d = %d calculs \n", X, j, X*j);
    printf("temps perso  : %f \n",t1);
    printf("temps perso 2: %f \n",t4);
    printf("temps std    : %f \n",t2);   
    printf("temps std sec: %f \n",t3);     

    return 0;
}


CONCLUSION

Finalement le gain est maigre lorsqu'on n'oublie pas d'activer l'optimisation. Il me semble inutile, pour de l'écriture en base 10 de penser à autre chose qu'à sprintf ou snprintf. Le problème de ses deux fonctions est qu'il faut savoir prévoir la taille de la chaîne à l'avance, nous sommes en C n'est ce pas...

Pour une autre base je pense que ma fonction est la bienvenue, sinon...

à retenir :
  
snprintf(chaine, taille ,"coucou %d", entier);


Publié dans Divers

Commenter cet article

moi 16/01/2008 06:31

A l'époque je n'aimais pas utiliser time parce quelle ne permet pas de chronometrer qu'une partie du code.Time a cependant l'avantage de savoir faire la différence entre le temps ou le processus est réellement dans le processeur et le moment ou il est endormi pour faire autre chose (bouger la souris, recevoir des données sur la carte réseau....)