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 :
-
la plus ancienne, constituée des fonctions atoi(), atol() et atof(), les plus fréquemment utilisées mais sans contrôle d'erreurs,
-
la famille plus récente (mais qui est quand même dans la norme ANSI C de 1989 !!!) : strtod(), strtol() et strtoul(), qui valident le résultat.
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 :
-
transformer le type de retour de atof() en double.
Il faut se souvenir que les conversions implicites sont possibles en C, et que toute valeur double, renvoyée par une fonction par exemple, peut être automatiquement convertie en réel plus court, un float, si le type de la variable réceptrice l'exige (attention aux éventuelles troncatures)
-
garder la fonction atoi() pour renvoyer un int, et ajouter une nouvelle fonction atol() pour les longs.
Ce choix était sans doute justifié pour ne pas modifier les habitudes des développeurs qui utilisaient très largement atoi(), et très peu les autres.
Exemple d'utilisation :
char * Nbre = "12345";
...
cout << "Nbre + 1 = " << atoi (Nbre) + 1 << endl; |
Remarques :
-
comme indiqué plus haut, la fonction ne valide pas la conversion : tout caractère invalide termine la conversion, les débordements de capacité ne sont pas détectés;
-
le nombre exprimé sous forme de chaîne doit être exprimé en base 10,
-
attention : une fréquente erreur est de vouloir utiliser ces fonctions (atoi() et atol()) pour convertir un caractère numérique en entier, comme par exemple :
char C = '9';
...
cout << "C + 1 = " << atoi (&C) + 1 <<
endl; // Erreur !!! |
Dans ce cas, la conversion est beaucoup plus simple :
char C = '9';
...
cout << "C + 1 = " << (C - '0') + 1 <<
endl; |
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); |
où
-
nptr pointe sur la chaîne NTCTS,
-
endptr, s'il est non nul en entrée, pointe en sortie sur l'adresse du premier caractère invalide de la chaîne ('\0' si la chaîne est valide),
-
base est la base (uniquement pour les entiers, entre 2 et 16).
Si la base est nulle, c'est par défaut la base 10 sauf si la chaîne commence par 0x ou 0, auxquel cas c'est la base hexadécimale ou octale qui est utilisée.
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 :
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 :
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 :
-
la première, proche du C, met en oeuvre la fonction membre c_str() qui permet de convertir une chaîne C++ string en chaîne C (NTCTS), sur laquelle peut être appliquée l'une des fonctions ci-dessus.
Par exemple :
string Nbre = "12345";
cout << "Nbre + 1 = " << atoi (Nbre.c_str()) + 1 <<
endl; |
-
la seconde, beaucoup plus dans l'esprit du C++, permet de considérer une chaîne de caractères NTCTS comme un flux d'entrée, un istrstream, à partir duquel il est possible d'extraire de éléments, quel que soit leur type, par exemple des entiers, comme le montre la séquence suivante :
char Tab1 [] = "123 456 789";
istrstream is (Tab1);
int Nbre;
for (is >> Nbre; !is.eof(); is >> Nbre) cout << Nbre
<< endl; |
provoque l'affichage suivant :
Comme on le remarque, le caractère '\0' de fin de la chaîne Tab joue ici le rôle de "fin-de-fichier", la quatrième extraction échoue, (la condition devient fausse).
L'instruction d'extraction pourrait aussi être écrite :
for (int Nbre ; is >> Nbre; ) cout << Nbre
<< endl; |
On pourrait aussi utiliser les fonctions fail() (ou bad()) membres de la classe istrstream, comme dans l'exemple suivant :
char Tab2 [] = "123 xxx";
istrstream is (Tab2);
int Nbre;
for (is >> Nbre; !is.fail(); is >> Nbre) cout << Nbre <<
endl;
cout << "La lecture a " << (is.fail () ? "échoué"
: "réussi") << endl; |
dont l'affichage serait :
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.
-
number : nombre à convertir en chaîne
-
ndigits : longueur totale de la chaîne désirée pour ecvt(), nombre de chiffres après la virgule pour fcvt(),
-
decpt : pointeur vers (ou adresse d')un entier résultat représentant le nombre de chiffres avant la virgule,
-
sign : pointeur vers (ou adresse d')un entier résultat qui prend la valeur 0 si le nombre est positif ou nul, ou 1 si le nombre est négatif,
-
ndigit : nombre maximal de chiffres significatifs désirés : s'il est inférieur au nombre de chiffres de la partie entière du nombre, celui-ci est écrit sous forme "virgule flottante" (écriture scientifique) - voir exemples plus loin.
Sinon il est écrit sous forme de "virgule fixe".
La fonction gcvt() ne conserve aucun zéro non significatif.
-
buf : adresse de la zone mémoire à laquelle sera rangée la chaîne résultante
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} |
-
ligne {1} : 2 chiffres avant la virgule, nombre positif ou nul
-
ligne {2} : 2 chiffres avant la virgule, nombre négatif (troncature complète de la partie décimale)
-
ligne {3} : 2 chiffres avant la virgule, nombre négatif ou nul (troncature de la partie entière !!!!!)
-
ligne {4} : virgule décalée d'une position à gauche de la chaîne, nombre négatif ou nul
-
ligne {5} : transformation d'un entier en chaîne, position de la virgule après le deuxième chiffre
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} |
-
lignes {1} et {2} : nombre de chiffres significatifs inférieur au nombre minimal nécessaire (3), donc notation scientifique.
-1.2e+02 doit être lu : -1.2 x 102
-
lignes {3}, {4} et {5} : notation classique avec 0, 1 ou 2 chiffres après la virgule,
-
ligne {6} : aucun zéro non significatif n'est ajouté en fin de chaîne, dont le nombre de chiffre significatif devient inférieur au nombre demandé (ndigit = 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 :
-
1 octet pour le signe éventuel,
-
1 octet pour le point décimal éventuel,
-
1 octet pour le caractère e,
-
1 octet pour le signe de l'exposant,
-
2 octets pour l'exposant.
-
1 octet pour le caractère nul terminal '\0'.
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