Cette semaine, le but de ce défi est de recoder la célèbre fonction printf. Bien entendu, nous n'allons pas faire une fonction aussi complexe et complète que l'originale, mais néanmoins notre version reprendra les bases de printf.
Si vous souhaitez plus de renseignements sur les défis, rendez vous sur le topic de recensement des défis, vous y trouverez également les règles principales.
zPrintf
Retour sur la fonction originale
La fonction printf sert à produire des sorties sur le flux standard stdout. Elle permet ainsi non seulement d'afficher une chaîne de caractères, mais permet aussi de prendre et d'afficher autant d'arguments que l'on veut :
printf("Salut %s, %d, %c", "zero", 42, 'z');
Et ces arguments peuvent recevoir des attributs, comme n'afficher que 2 chiffres après la virgule, ou bien écrire en écriture scientifique. Ces arguments sont très nombreux, et pour les découvrir je vous renvoie à la page de man de printf.
Pistes
Notre but est de faire un mini-version de printf. Or on sait que cette fonction peut prendre autant de paramètre que l'on veut. Il faut donc penser à la gestion des paramètres. Il faut aussi gérer les différents attributs, ainsi que trouver comment afficher le résultat. Dernier point : il ne faut pas oublier que les nombres doivent être convertis en chaînes de caractères.
Consignes
Le but n'est pas de recopier entièrement la fonction printf, ce qui est compliqué et assez long, mais de faire une version simplifiée. On doit néanmoins pouvoir utiliser les principaux indicateurs de conversion :
Pour rendre l'exercice plus amusant, les seuls éléments de la bibliothèque standard autorisés sont la fonction fwrite et les macros va_start, va_arg et va_end. Toutes les autres fonctions utilisées doivent être recodées.
Objectifs
Manipuler les fonctions à nombre variable d'arguments.
Apprendre à réfléchir aux fonctions utilisées.
Découvrir de manière approfondie la bibliothèque standard.
Énoncé
Niveau 1
Dans un premier temps, il ne faudra gérer que %c, %d, %i et %s, ainsi que %%.
zPrintf("%d %s", 4882, "SdZ");
doit afficher :
4882 SdZ
Pensez à une fonction pour convertir les nombres en chaîne de caractères.
Niveau 2
Cette fois, le programme doit gérer %o, %u, %x et %X.
zPrintf("%d %o %X", 47, 47, 47);
doit afficher :
47 57 2F
Essayez de reprendre votre fonction de conversion et de l'adapter pour la rendre générique pour n'importe quelle base.
Niveau 3
La prochaine étape prend en charge les flottants et l'écriture scientifique avec %f, %F%e, %E, %g, %G, %a et %A.
zPrintf("%f %e %G", 3.1415, 3.1415, 3.1415);
doit afficher :
3.141500 3.141500e+000 3.1415
Niveaux supplémentaires
La page de man ! Essayez d'implémenter le plus de choses possibles, comme les caractères d'attribut, les modificateur de longueur, les largeurs de champ, la précision, etc. Si vous le souhaitez, vous pouvez créer vos propres indicateurs de fonctions ou vos propres modificateurs. Vous pouvez également tenter de réécrire d'autres fonctions de la famille de printf. Votre imagination est votre seule limite !
Bon courage à tous et amusez-vous bien.
PS : si vous n'avez pas compris quelque chose, n'hésitez pas à poser votre question.
#include <stdarg.h>
#include <stdio.h>
#define BUFF_SIZE 256
void my_putchar(char c)
{
fwrite(&c, sizeof(char), 1, stdout);
}
int itos(int nombre, int base)
{
char buff[BUFF_SIZE];
char lettre[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
int cmp, temp, res;
cmp = temp = res = 0;
if (nombre < 0)
res++, putchar('-');
do
{
temp = nombre % base;
temp = (temp < 0) ? -temp : temp;
buff[cmp] = lettre[temp];
cmp++;
nombre /= base;
} while (nombre != 0);
for (cmp = cmp - 1; cmp >= 0; cmp--)
{
putchar(buff[cmp]);
res++;
}
return res;
}
int my_vprintf(const char * str, va_list args)
{
int i, res;
i = res = 0;
for (i = 0; str[i] != '\0';i++)
{
switch(str[i])
{
case '%':
i++;
switch(str[i])
{
case 'b':
{
int entier = va_arg(args, int);
res += itos(entier, 2);
i++;
}
break;
case 'o':
{
int entier = va_arg(args, int);
my_putchar('0');
res += itos(entier, 8) + 1;
i++;
}
break;
case 'i':
case 'd':
{
int entier = va_arg(args, int);
res += itos(entier, 10);
i++;
}
break;
case 'u':
{
unsigned int entier = va_arg(args, unsigned int);
res += itos(entier, 10);
i++;
}
break;
case 'X':
{
int entier = va_arg(args, int);
my_putchar('0');
my_putchar('x');
res += itos(entier, 16) + 2;
i++;
}
break;
case 'c':
{
char c = (unsigned char)va_arg(args, int);
my_putchar(c);
i++, res++;
}
break;
case 's':
{
char * s = va_arg(args, char *);
for (; *s != '\0'; s++)
{
my_putchar(*s);
res++;
}
i++;
}
break;
case '%':
my_putchar('%');
res++;
break;
default:
my_putchar('%');
res++;
break;
}
default:
my_putchar(str[i]);
res++;
break;
}
}
return res;
}
int my_printf(const char * str, ...)
{
int res;
va_list ap;
va_start(ap, str);
res = my_vprintf(str, ap);
va_end(ap);
return res;
}
int main(void)
{
int a, b;
a = my_printf("%d %i %c %s\n", 24577, -21, 'a', "Sa");
b = printf("%d %i %c %s\n", 24577, -21, 'a', "Sa");
printf("%d %d \n", a, b);
a = my_printf("Salut\n");
b = printf("Salut\n");
printf("%d %d \n", a, b);
printf("%d %u\n", -42 , -42);
my_printf("%d %u\n", -42 , -42);
my_printf("%d %X %o\n", 47, 47, 47);
my_printf("%d %X %b\n", 0xAA55, 0xAA55, 0xAA55);
my_printf("%d %X %o %b\n", 11, 11, 11, 11);
return 0;
}
24577 -21 a Sa
24577 -21 a Sa
15 15
Salut
Salut
6 6
-42 4294967254
-42 -42
47 0x2F 057
43605 0xAA55 1010101001010101
11 0xB 013 1011
Le code n'est pas du tout optimisé, je ne gère pas %x à cause d'une mauvaise conception, tout comme %u. De plus, le switch devenant de plus en plus imbuvable, je compte le remplacer par un tableau de pointeurs sur fonctions pour rendre ça plus propre. Je suis donc ouvert à toutes remarques pouvant m'aider à améliorer mon code.
L'exercice est intéressant, cependant il y a deux petites choses qui retienne mon attention :
- pourquoi n'utiliser que fwrite et les macros de l'en-tête stdarg.h ?
- vous avez essayer de convertir un flottant en chaîne de caractère avant de proposer cet exercice pour le niveau 3 ? Si oui, je veux bien votre code
Pourquoi n'utiliser que fwrite et les macros de l'en-tête stdarg.h ?
C'est juste pour forcer à recoder toutes les fonctions utilisées, en se basant sur le minimum. C'est un peu inutile, mais ça fait un bon exercice. Sinon je peux très bien faire un simple :
int my_printf(const char * str, ...)
{
int res;
va_list ap;
va_start(ap, str);
res = vprintf(str, ap);
va_end(ap);
return res;
}
et pis voilà, j'ai recodé printf.
Citation : Taurre
Vous avez essayer de convertir un flottant en chaîne de caractère avant de proposer cet exercice pour le niveau 3 ? Si oui, je veux bien votre code
Pour tout t'avouer non, mais si cet exercice est si souvent proposé dans les écoles d'informatique, c'est que ça ne doit pas être impossible à faire. Après une simple conversion basique suffira.
C'est juste pour forcer à recoder toutes les fonctions utilisées, en se basant sur le minimum. C'est un peu inutile, mais ça fait un bon exercice. Sinon je peux très bien faire un simple :
Ma foi, dans ce cas, il suffit d'interdire les fonctions de la famille printf (ce qui est au final assez logique pour un exercice visant à recoder printf ), nul besoin d'interdire les autres
Citation : informaticienzero
Pour tout t'avouer non, mais si cet exercice est si souvent proposé dans les écoles d'informatique, c'est que ça ne doit pas être impossible à faire. Après une simple conversion basique suffira.
Hmm... J'avoue que j'ai bon y réfléchir je ne vois pour l'instant pas comment faire cela. Je serais curieux de savoir comment ils s'en sortent à Epitech par exemple
Hmm... J'avoue que j'ai bon y réfléchir je ne vois pour l'instant pas comment faire cela. Je serais curieux de savoir comment ils s'en sortent à Epitech par exemple
Tiens j'ai trouvé ça sur le site d'un gars qui a recodé toute la libC. Ce lien mène vers les fonctions de conversions de nombres en chaînes.
C'est exact, j'ai fais une confusion.
Sinon il me semble que ce lien-là montre bien une conversion de double en chaîne.
Aah ! Très intéressante comme implémentation, merci pour le lien
J'avais regardé du côté du code source de FreeBSD et j'étais tombé sur un truc imbuvable
Attention qu'avec cette fonction tu écris le byte de poids faible de la variable c, tu écriras donc probablement 0 sur une machine big endian. De plus, le retour de putchar doit être de type int (retourne EOF en cas d'erreur) et non size_t.
EDIT : Aussi, les noms d'identificateurs commençant par deux underscores sont réservés et ne doivent par conséquent pas être utilisés
@informaticienzero: j'ai fait quelque test sur le principe de la fonction que tu m'as passée en lien et malheureusement cela ne fonctionne plus lorsque l'on dépasse des nombres d'une certaine taille. Par exemple, le code suivant :
#include <float.h>
#include <math.h>
#include <stdio.h>
int
main(void)
{
double n = FLT_MAX;
double i, f;
f = modf(n, &i);
printf("%f, %f\n", i, f);
while (i > 0.) {
double tmp = modf(i / 10., &i);
printf("%f, %f\n", i, tmp);
}
return 0;
}
@informaticienzero: j'ai fait quelque test sur le principe de la fonction que tu m'as passée en lien et malheureusement cela ne fonctionne plus lorsque l'on dépasse des nombres d'une certaine taille. Par exemple, le code suivant :
#include <float.h>
#include <math.h>
#include <stdio.h>
int
main(void)
{
double n = FLT_MAX;
double i, f;
f = modf(n, &i);
printf("%f, %f\n", i, f);
while (i > 0.) {
double tmp = modf(i / 10., &i);
printf("%f, %f\n", i, tmp);
}
return 0;
}
Effectivement. Par contre, ça marche avec des valeurs inférieures. C'est à mon avis un problème innérant à toute fonction qui manipule des nombres.
En fait, c'est un problème de précision. Si on suit la norme IEEE 754, un double a une mantisse de 54 bits (en comptant le bit implicite). Or, en l'occurrence, on y stocke 24 bits à 1 et un exposant de 127 (maximum possible pour un float). Ce qui veut dire que les bits de ma mantisse ne peuvent représentés les puissances de 2 que de 127 à 75. Il est donc impossible de représenté un nombre inférieure à 2 x1075. La preuve en image :
#include <float.h>
#include <math.h>
#include <stdio.h>
int
main(void)
{
double n = FLT_MAX;
printf("%f\n", n);
printf("%f\n", n + pow(2., 75));
printf("%f\n", n + pow(2., 74));
return 0;
}
... J'avoue que j'ai bon y réfléchir je ne vois pour l'instant pas comment faire cela. Je serais curieux de savoir comment ils s'en sortent à Epitech par exemple
Pour parler franchement, la plupart des gens ne pensent pas à la manière dont cela est stocké, et vont partir de leur postulat que c'est en base 10, et donc l'afficher à base de multiplications successives par 10.
Très peu se servent de la mantisse et de l'exponentiation pour écrire leur résultat.
Mais la représentation du nombre en base 10 est approximative, et tu peux donc avoir quelques erreurs (de l'ordre de 10-6).
C'est néanmoins celle qui est demandée par la norme, et donc par l'exercice. C'est tout de suite plus dur de lire du flottant en hexadécimal.
Puis bon, il y a déjà des problèmes de précisions inhérents à la représentation flottante, on sait que toute manipulation de ces représentations est sujette à ce type de problèmes. On prend le risque.
J'ai déménagé sur Zeste de savoir — Ex-manager des modérateurs.
C'est vrai que je devrais le préciser, pour cet exercice je pense que la gestion pour la base 10 est suffisante, bien qu'il y ait aussi l'hexadécimal avec le C99.
Mais la représentation du nombre en base 10 est approximative, et tu peux donc avoir quelques erreurs (de l'ordre de 10-6).
C'est néanmoins celle qui est demandée par la norme, et donc par l'exercice. C'est tout de suite plus dur de lire du flottant en hexadécimal.
Puis bon, il y a déjà des problèmes de précisions inhérents à la représentation flottante, on sait que toute manipulation de ces représentations est sujette à ce type de problèmes. On prend le risque.
Oui, bien sûr, je voulais simplement dire qu'il était préférable de recalculer cette approximation à la main, plutôt que de se baser sur une multiplication du nombre par une puissance de 10, qui elle apporte des imprécisions.
Pour parler franchement, la plupart des gens ne pensent pas à la manière dont cela est stocké, et vont partir de leur postulat que c'est en base 10, et donc l'afficher à base de multiplications successives par 10.
Très peu se servent de la mantisse et de l'exponentiation pour écrire leur résultat.
Citation : Mr21
Surtout que le %f est considere comme un petit bonus dans notre sujet.
Perso j'avais fait ça très simplement hein.
Mmh... Ok, on ne vous parle donc pas de la représentation des flottants et on vous laisse proposer une solution qui ne fonctionne que sur un certain intervalle de nombre.
Citation : Mr21
M'enfin le vrai printf lui, gère ce genre de chose par exemple: %.25f contrairement a mon truc qui va bugger complètement :S
Effectivement, mais même avec la précision par défaut il y a déjà un risque d'integer overflow, avec cette instruction :
putnbr((int)mon_float);
étant donné qu'un float peut stocker un nombre allant jusqu'à 1 x1037 (suivant la norme C).
Tu parles de la fonction `minprintf' que l'on trouve dans le livre ? Si oui, c'est quand même assez réducteur (en plus, `printf' est utilisé dans la fonction). Du moins, dans mon édition.
× Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
× Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
entwanne — @entwanne — Un zeste de Python — La POO en Python — Notions de Python avancées — Les secrets d'un code pythonique
entwanne — @entwanne — Un zeste de Python — La POO en Python — Notions de Python avancées — Les secrets d'un code pythonique
entwanne — @entwanne — Un zeste de Python — La POO en Python — Notions de Python avancées — Les secrets d'un code pythonique