web-dev-qa-db-fra.com

Pourquoi un spécificateur pour `float` n'a-t-il pas été défini dans` printf`?

Il semble que cela aurait pu exister, il existe (au moins en C99) des modificateurs de longueur qui peuvent être appliqués à int: %hhd, %hd, %ld et %lld signifier signed char, short, long et long long. Il existe même un modificateur de longueur applicable à double: %Lf veux dire long double.

La question est pourquoi ont-ils omis float? Suivant le modèle, il aurait pu être %hf.

69
skyking

Parce que dans les appels de fonctions variadiques C, tout argument float est promu (c'est-à-dire converti) en double, donc printf obtient un double et utiliserait va_arg(arglist, double) pour l'intégrer dans son implémentation.

Dans le passé (C89 et K&R C), chaque argument float était converti en double. La norme actuelle omet cette promotion pour les fonctions d'arité fixe qui ont un prototype explicite. Elle est liée à (et les détails sont expliqués dans) le ABI & conventions d'appel de l'implémentation. En pratique, une valeur float serait souvent chargée dans un registre à virgule flottante double lorsqu'elle est passée en argument, mais les détails peuvent varier. Lisez l'exemple Linux spécification ABI x86-64 .

De plus, il n'y a aucune raison pratique de donner une chaîne de contrôle de format spécifique pour float puisque vous pouvez ajuster la largeur de la sortie (par exemple avec %8.5f) comme souhaité, et %hd est beaucoup plus utile (presque nécessaire) dans scanf que dans printf

Au-delà, je suppose que la raison (pour omettre %hf spécifiant float -promoté à double dans l'appelant- dans printf) est historique : au début, C était un langage de programmation système, pas un langage HPC (Fortran était préféré dans HPC peut-être jusqu'à la fin des années 1990) et float n'était pas très important; il a été (et est toujours) pensé comme short, un moyen de réduire la consommation de mémoire. Et les FPU d'aujourd'hui sont assez rapides (sur les ordinateurs de bureau ou les serveurs) pour éviter d'utiliser float sauf comme moyen d'utiliser moins de mémoire. Vous devriez croire que chaque float est quelque part (peut-être à l'intérieur le FPU ou le CPU) converti en double.

En fait, votre question pourrait être paraphrasée comme suit: pourquoi %hd existe pour printf (où il est pratiquement inutile, car printf obtient un int lorsque vous le lui passez short; cependant scanf en a besoin!). Je ne sais pas pourquoi, mais j'imagine que dans la programmation système, cela pourrait être plus utile.

Vous pourriez passer du temps à faire pression sur la prochaine norme ISO C pour obtenir %hf accepté par printf pour float (promu en double lors de printf appels, comme short- s promu en int), avec un comportement indéfini lorsque la valeur de double précision est hors limites pour float- s, et symétriquement %hf accepté par scanf pour float pointeurs. Bonne chance là-dessus.

47

En raison de promotions d'argument par défaut.

printf() est une fonction d'argument variable (... dans sa signature), tous les arguments float sont promus en double.

C11 §6.5.2.2 Appels de fonction

6 Si l'expression qui dénote la fonction appelée a un type qui n'inclut pas de prototype, les promotions entières sont effectuées sur chaque argument et les arguments qui ont le type float sont promus en double. Celles-ci sont appelées les promotions d'argument par défaut.

7 La notation Ellipsis dans un déclarant de prototype de fonction provoque l'arrêt de la conversion du type d'argument après le dernier paramètre déclaré. Les promotions d'argument par défaut sont effectuées sur les arguments de fin.

15
Yu Hao

En raison des promotions d'argument par défaut lors de l'appel de fonctions variadiques, les valeurs float sont implicitement converties en double avant l'appel de la fonction et il n'y a aucun moyen de passer une valeur float à printf. Puisqu'il n'y a aucun moyen de passer une valeur float à printf, il n'est pas nécessaire d'avoir un spécificateur de format explicite pour les valeurs float.

Cela dit, AntoineL a soulevé un point intéressant dans un commentaire que %lf (actuellement utilisé dans scanf pour correspondre à un type d'argument double *) a peut-être déjà représenté "long float ", qui était un synonyme de type avant C89, selon la page 42 de la justification C99 . Selon cette logique, il pourrait être logique que %f était censé représenter une valeur float qui a été convertie en double.


Concernant les modificateurs de longueur hh et h, %hhu et %hu présente un cas d'utilisation bien défini pour ces spécificateurs de format: vous pouvez imprimer l'octet le moins significatif d'un grand unsigned int ou unsigned short sans distribution, par exemple:

printf("%hhu\n", UINT_MAX); // This will print (unsigned char)  UINT_MAX
printf("%hu\n",  UINT_MAX); // This will print (unsigned short) UINT_MAX

Il n'est pas particulièrement bien défini ce qui se traduira par une conversion rétrécissante de int vers char ou short, mais c'est au moins défini par l'implémentation, ce qui signifie que l'implémentation est requise pour documenter réellement cette décision.

Suivant le modèle, il aurait dû être %hf.

En suivant le schéma que vous avez observé, %hf devrait convertir les valeurs en dehors de la plage de float en float. Cependant, ce type de conversion de double à floatentraîne un comportement indéfini , et il n'y a rien de tel qu'un unsigned float. Le motif que vous voyez n'a pas de sens.


Pour être formellement correct, %lf ne désigne pas un long double argument, et si vous deviez passer un long double argument vous invoqueriez un comportement indéfini . Il est explicite de la documentation que:

l (ell) ... n'a aucun effet sur un a, A, e, E, f, F, g ou G spécificateur de conversion.

Je suis surpris que personne d'autre n'ait compris cela? %lf désigne un argument double, tout comme %f. Si vous souhaitez imprimer un long double, utilisation %Lf (ell majuscule).

Il devrait désormais être logique que %lf pour printf et scanf correspondent à double et double * arguments... %f n'est exceptionnel qu'en raison des promotions d'argument par défaut, pour les raisons mentionnées précédemment.

... et %Ld ne signifie pas non plus long. Ce que cela signifie est comportement indéfini .

10
autistic

À partir de la norme ISO C11, 6.5.2.2 Function calls /6 Et /7, Discutant des appels de fonction dans le contexte des expressions (c'est moi qui souligne):

6/Si l'expression qui dénote la fonction appelée a un type qui n'inclut pas de prototype, les promotions entières sont effectuées sur chaque argument, et les arguments de type float sont promus à doubler. Celles-ci sont appelées les promotions d'argument par défaut.

7/Si l'expression qui dénote la fonction appelée a un type qui inclut un prototype, les arguments sont implicitement convertis, comme par affectation, en types des paramètres correspondants, en prenant le type de chaque paramètre comme la version non qualifiée de son type déclaré. La notation Ellipsis dans un déclarateur de prototype de fonction provoque l'arrêt de la conversion du type d'argument après le dernier paramètre déclaré. Les promotions d'argument par défaut sont effectuées sur les arguments de fin.

Cela signifie que tous les arguments float après le ... Dans le prototype sont convertis en double et la famille d'appels printf est définie de cette façon (7.21.6.11 Et suiv.):

int fprintf(FILE * restrict stream, const char * restrict format, ...);

Donc, comme il n'y a aucun moyen pour que les appels de la famille printf()- reçoivent réellement un flottant , cela n'a aucun sens d'avoir un format spécial spécificateur (ou modificateur) pour cela.

5
paxdiablo

Étant donné que scanf a des spécificateurs de format séparés pour float, double ou long double, je ne vois pas pourquoi printf et des fonctions similaires n'ont pas été implémentées de la même manière, mais c'est ainsi que C/C++ et les normes ont fini.

Il peut y avoir un problème avec la taille minimale pour une opération Push ou pop selon le processeur et le mode actuel, mais cela aurait pu être traité avec un remplissage par défaut, similaire à l'alignement par défaut des variables locales ou des variables dans une structure. Microsoft a abandonné la prise en charge de 80 bits (10 octets) long doubles quand il est passé de compilateurs 16 bits à 32/64 bits, traitant désormais long doubles identique à doubles (64 bits/8 octets). Ils auraient pu les ajuster à des limites de 12 ou 16 octets au besoin, mais cela n'a pas été fait.

2
rcgldr

En lisant la justification C, ci-dessous fscanf, les éléments suivants peuvent être trouvés:

Une nouvelle fonctionnalité de C99: les modificateurs de longueur hh et ll ont été ajoutés dans C99. ll prend en charge le nouveau type long long int. hh ajoute la possibilité de traiter les types de caractères de la même manière que tous les autres types entiers; cela peut être utile pour implémenter des macros telles que SCNd8 dans (voir 7.18).

Donc, supposément, le hh a été ajouté dans le but de fournir un support pour tous les nouveaux stdint.h les types. Cela pourrait expliquer pourquoi un modificateur de longueur a été ajouté pour les petits entiers mais pas pour les petits flottants.

Cela n'explique pas pourquoi C90 avait de manière incohérente h mais pas hh. Le langage tel que spécifié dans C90 n'est pas toujours cohérent, aussi simple que cela. Et les versions ultérieures ont hérité de l'incohérence.

2
Lundin

%hhd, %hd, %ld et %lld ont été ajoutés à printf pour rendre les chaînes de format plus cohérentes avec scanf, même si elles sont redondantes pour printf en raison des promotions d'argument par défaut.

Alors, pourquoi n'était pas %hf ajouté pour float? C'est simple: en regardant le comportement de scanf, float a déjà un spécificateur de format. C'est %f. Et le spécificateur de format pour double est %lf.

Cette %lf est exactement ce que C99 a ajouté à printf. Avant C99, le comportement de %lf n'était pas défini (par omission de toute définition dans la norme). Depuis C99, c'est un synonyme de %f.

2
user743382

Lorsque C a été inventé, toutes les valeurs à virgule flottante ont été converties en un type commun (c'est-à-dire double) avant d'être utilisées dans les calculs ou transmises à des fonctions (y compris) printf, donc il n'y avait pas besoin de printf pour faire des distinctions entre les types à virgule flottante.

Dans le but de promouvoir l'efficacité et la précision arithmétiques, la norme à virgule flottante IEEE-754 a défini un type 80 bits qui était plus grand qu'un double 64 bits normal mais pouvait être traité plus rapidement. L'intention était que, étant donné une expression comme a=b+c+d; il serait à la fois plus rapide et plus précis de tout convertir en un type 80 bits, d'ajouter trois nombres 80 bits et de convertir le résultat en un type 64 bits, que de calculer la somme (b+c) en tant que type 64 bits, puis ajoutez-le à d.

Afin de prendre en charge le nouveau type, ANSI C a défini un nouveau type long double quelles implémentations auraient pu faire référence au nouveau type 80 bits ou à un 64 bits double. Malheureusement, même si le objectif du type 80 bits IEEE-754 devait avoir toutes les valeurs automatiquement promues au nouveau type comme elles avaient été promues en double, ANSI l'a fait ainsi le nouveau type est passé à printf ou à d'autres méthodes variadiques différemment des autres types à virgule flottante, rendant ainsi cette promotion automatique intenable.

Par conséquent, les deux types à virgule flottante qui existaient lorsque C a été créé peuvent utiliser le même %f spécificateur de format, mais le long double qui a été créé par la suite nécessite un autre %Lf spécificateur de format (avec un majusculeL).

2
supercat