Conversions chaînes <--> numériques en C/C++

© D. Mathieu     mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique
Créé le 31/07/2001 - Dernière mise à jour : 31/07/2001

Sommaire

Conversions de chaînes de caractères en variables numériques en C
Les fonctions atoi(), atol() et atof()
Les fonctions strtod(), strtol() et strtoul()
Conversions de variables numériques en string de C++
Utilisation simple
Utilisation sophistiquée
Utilisation sécurisée
Conversions de chaînes de caractères en variables numériques en C++
Conversions de variables numériques en chaînes en C
Exemples d'utilisation de ecvt()
Exemples d'utilisation de fcvt()
Exemples d'utilisation de gcvt()

Conversions de chaînes de caractères en variables numériques en C

    Il est très fréquent que le développeur possède la représentation d'un nombre, entier ou réel, sous la forme d'une chaîne de caractères. C'est par exemple le cas lorsqu'il récupère ces données soit d'un argument du lancement de la commande (argv [i]), soit d'une lecture au clavier ou dans un fichier, soit encore d'une zone de saisie d'un contrôle (TextField d'une boîte de dialogue).

    Les chaînes de caractères dont il est question ici sont les chaînes au sens C (NTCTS), suites de caractères terminées par un '\0'. La bibliothèque standard C offre deux familles de fonctions :

Sommaire

Les fonctions atoi(), atol() et atof()

    Elles ont pour profils :
 
#include <stdlib.h>           // ou <cstdlib> en C++

int    atoi (const char *nptr);
long   atol (const char *nptr);
double atof (const char *nptr);

    Ces fonctions convertissent la chaîne de caractères pointée par nptr respectivement en un entier int, un entier long ou un réel double. On peut se demander pourquoi une seule fonction suffit pour les nombres réels (par de conversion en float) alors qu'il y a deux fonctions pour les entiers (et pourquoi pas d'autres pour les shorts et les non signés). Il est probable que ce soit pour des  raisons historiques et de compatibilité, les premières fonctions ayanté été atoi() pour renvoyer un int et atof() pour renvoyer un float. Les types long et double étant apparus ultérieurement, les concepteurs de C ont choisi deux stratégies différentes :

    Exemple d'utilisation :
 
char * Nbre = "12345";
...
cout << "Nbre + 1 = " << atoi (Nbre) + 1 << endl;

Remarques :

Sommaire

Les fonctions strtod(), strtol() et strtoul()

    Elles ont pour profils :
 
#include <stdlib.h>            // ou <cstdlib> en C++

long int          strtol  (const char *nptr, char **endptr, int base);
unsigned long int strtoul (const char *nptr, char **endptr, int base);
double            strtod  (const char *nptr, char **endptr);

    Une chaîne vide est considérée comme invalide.

    Exemple d'utilisation :
 
char * Nbre = "0xD3xyz";
...
char * Pos;
cout << "Nbre + 1 = " << strtol (Nbre, &Pos, 0) + 1 << endl;
cout << "Reste de chaîne invalide : " << Pos << ';' << endl;

qui provoque l'affichage :
 
Nbre + 1 = 212
Reste de chaîne invalide : xyz;

Remarque : les fonctions atoi(), atol() et atof() sont respectivement équivalentes à :
 
strtol (nptr, 0, 10);
strtol (nptr, 0, 10);
strtod (nptr, 0);

    Pour plus de détails, voir le manuel man 3 strtol.

Sommaire

Conversions de variables numériques en string de C++

    La façon normale de convertir un nombre en string est d'utiliser toutes les possibilités qu'offre l'injecteur << dans un flux. Rappelons en effet que cet opérateur possède en standard toutes les surcharges nécessaires pour transformer tous les types de base (entiers, réels, etc.) en suites de caractères qui sont dirigés sur un flux de sortie. Rappelons aussi que cette édition peut être formatée de différentes façons (cadrage à droite, remplissage par des caractères spéciaux, choix du nombre de chiffres significatifs, etc.) grâce aux manipulateurs. Une grande élégance du C++ est d'utiliser un vecteur de caractères comme flux de sortie, et d'y injecter n'importe quel objet affichable au moyen d'un injecteur.

Sommaire

Utilisation simple

    Dans ce premier exemple, l'utilisateur réserve un espace de 100 octets, l'associe avec un flux de sortie de type ostrstream (output string stream), puis injecte la valeur numérique, obligatoirement terminée par le manipulateur ends qui vient "fermer" la chaîne.
 
char Tab1 [100];
ostrstream os1 (Tab1, sizeof (Tab1));
os1 << 1234 << ends;

cout << "Tab1 = " << Tab1 << ';' << endl;

qui provoque l'affichage :
 
Tab1 = 1234;

Sommaire

Utilisation sophistiquée

    Une meilleure possibilité (plus dans l'esprit de C++) est de récupérer la chaîne NTCTS associée au string par la fonction str() membre de la classe ostrstream :
 
cout << "os1.str() = " << os1.str() << ';' << endl;

qui provoque le même affichage que précédemment.

    L'utilisation d'un flux permet des générations de chaînes plus sophistiquées, comme par exemple :
 
char Tab2 [100];
ostrstream os2 (Tab2, sizeof (Tab2));
os2 << "1235 en hexa = " << hex << 1234 << ends;

cout << os2.str() << ';' << endl;

qui provoque l'affichage de :
 
1235 en hexa = 4d2;

Sommaire

Utilisation sécurisée

    Le dernier avantage à utiliser les outils de C++ est la possibilité de sécuriser la conversion. En effet, si l'espace mémoire préalablement réservé est insuffisant pour stocker la chaîne de caractères résultant de la conversion, l'utilisateur en est informé et ne risque pas des "désagréments" comme des violations mémoire par exemple (segmentation fault).

    Considérons l'exemple suivant :
 
char Tab3 [3];
ostrstream os (Tab3, sizeof (Tab3));
os << 123456 << ends;

cout << os.str() << ';' << endl;
cout << "La conversion a " << (os.good () ? "réussi" : "échoué") << endl;

qui provoque l'affichage :
 
123;
La conversion a échoué

    Comme on le voit, le résultat n'est pas incohérent, le processus reste sous contrôle du développeur qui peut prendre les mesures adéquates (allouer plus d'espace par exemple). Les fonctions membres habituelles des flux (good(), bad() et fail()) sont disonibles pour les ostrstreams.

Sommaire

Conversions de chaînes de caractères en variables numériques en C++

    En C++, il n'existe aucune fonction membre de la classe string permettant ces conversions. Cependant, deux solutions permettent d'atteindre cet objectif :

Sommaire

Conversions de variables numériques en chaînes en C

    Trois fonctions sont proposées par Linux (non définies dans la norme Ansi du C de 1989, et aucune indication d'appartenance à une autre norme) : ecvt(), fcvt() et gcvt() , déclarées dans <stdlib.h> (ou <cstdlib> en C++), de profils :
 
char *ecvt (double number, size_t ndigits, int *decpt, int *sign);
char *fcvt (double number, size_t ndigits, int *decpt, int *sign);

char *gcvt (double number, size_t ndigit, char *buf);

    Les deux premières utilisent une zone mémoire interne, dans laquelle elles stockent le résultat de la représentation du nombre passé en paramètre. Elles en renvoient l'adresse mémoire. La chaîne ne contient ni l'éventuel point décimal, ni le signe éventuel.

Sommaire

Exemples d'utilisation de ecvt()

char * pN;
int decpt, sign;

// Nombre réel, largeur suffisante                                 {1}

pN = ecvt (/* number = */  12.34, /* ndigits = */ 6, &decpt, &sign);
cout << pN << ";\t" << decpt << '\t' << sign << endl;

// Nombre réel, largeur suffisante pour la partie entière          {2}

pN = ecvt (/* number = */ -12.34, /* ndigits = */ 2, &decpt, &sign);
cout << pN << ";\t" << decpt << '\t' << sign << endl;

// Nombre réel, largeur insuffisante pour la partie entière        {3}

pN = ecvt (/* number = */ -12.34, /* ndigits = */ 1, &decpt, &sign);
cout << pN << ";\t" << decpt << '\t' << sign << endl;

// Nombre réel, largeur suffisante                                 {4}

pN = ecvt (/* number = */ -.034, /* ndigits = */ 6, &decpt, &sign);
cout << pN << ";\t" << decpt << '\t' << sign << endl;

// Nombre entier, largeur suffisante                               {5}

pN = ecvt (/* number = */ 12, /* ndigits = */ 3, &decpt, &sign);
cout << pN << ";\t" << decpt << '\t' << sign << endl;

provoque les éditions suivantes :
 
123400; 2       0     {1}
12;     2       1     {2}
1;      2       1     {3}
340000; -1      1     {4}
120;    2       0     {5}

Sommaire

Exemples d'utilisation de fcvt()

char * pN;
int decpt, sign;

pN = fcvt (/* number = */  12.34, /* ndigits = */ 3, &decpt, &sign);
cout << pN << ";\t" << decpt << '\t' << sign << endl;

pN = fcvt (/* number = */ -12.34, /* ndigits = */ 2, &decpt, &sign);
cout << pN << ";\t" << decpt << '\t' << sign << endl;

pN = fcvt (/* number = */ -12.34, /* ndigits = */ 1, &decpt, &sign);
cout << pN << ";\t" << decpt << '\t' << sign << endl;

pN = fcvt (/* number = */ -.034, /* ndigits = */ 6, &decpt, &sign);
cout << pN << ";\t" << decpt << '\t' << sign << endl;

pN = fcvt (/* number = */ 12, /* ndigits = */ 3, &decpt, &sign);
cout << pN << ";\t" << decpt << '\t' << sign << endl; 

provoque les éditions suivantes :
 
12340;  2       0     {1}
1234;   2       1     {2}
123;    2       1     {3}
34000;  -1      1     {4}
12000;  2       0     {5}

    Comme on le remarque, il ne peut plus y avoir de troncature, donc de perte d'information.

    Chaque fonction utilise une variable interne qui est écrasée à chaque opération. Sa sauvegarde passe donc par une copie dans une autre chaîne, comme par exemple :
 
#include <cstring>    // strlen(), strcpy()
#include <cstdlib>    // fcvt()

char * pN;
int decpt, sign;

pN = fcvt (/* number = */  12.34, /* ndigits = */ 3, &decpt, &sign);

// Réservation d'un octet de plus que la longueur 
//     pour stocker le caractère nul '\0'

char * Chaine = new char [strlen (pN) + 1];

// Recopie du résultat dans la Chaine en mémoire dynamique

strcpy (/* destination = */ Chaine, /* source = */ pN); 

Sommaire

Exemples d'utilisation de gcvt()

char buf [15];

cout << gcvt (-123.45, /* ndigit = */ 1, buf) << endl;     {1}
cout << gcvt (-123.45, /* ndigit = */ 2, buf) << endl;     {2}

cout << gcvt (-123.45, /* ndigit = */ 3, buf) << endl;     {3}
cout << gcvt (-123.45, /* ndigit = */ 4, buf) << endl;     {4}
cout << gcvt (-123.45, /* ndigit = */ 5, buf) << endl;     {5}
cout << gcvt (-123.45, /* ndigit = */ 6, buf) << endl;     {6}

provoque les éditions suivantes :
 
-1e+02        {1}
-1.2e+02      {2}
-123          {3}
-123.5        {4}
-123.45       {5}
-123.45       {6}

    Pour éviter tout risque d'erreur, dans le cas général, le nombre d'octets de la chaîne réceptrice doit donc être supérieur de 7 au nombre de chiffres significatifs demandés :     La fonction ne peut faire aucune validation de la longueur de la zone mémoire résultat. En cas de débordement, on peut s'attendre à tout type d'erreur (données voisines écrasées, erreur de segmentation, etc.).

© D. Mathieu     mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique