tipos sintaxis imprimir formato datos caracteres cadenas cadena arreglo c floating-point printf rationale c-standard-library

imprimir - sintaxis de printf y scanf



¿Por qué no se definió un especificador para `float` en` printf`? (8)

Cuando se inventó C, todos los valores de punto flotante se convirtieron a un tipo común (es decir, double ) antes de usarse en cálculos o pasarse a funciones (incluido) printf , por lo que no era necesario que printf hiciera ninguna distinción entre los tipos de punto flotante .

En aras de promover la eficiencia y la precisión aritmética, el estándar de punto flotante IEEE-754 definió un tipo de 80 bits que era más grande que un double normal de 64 bits pero que podía procesarse más rápido. La intención era que dada una expresión como a=b+c+d; sería más rápido y más preciso convertir todo a un tipo de 80 bits, sumar tres números de 80 bits y convertir el resultado a un tipo de 64 bits, que calcular la suma (b+c) como un 64 -bit type y luego agregue eso a d .

En el interés de soportar el nuevo tipo, ANSI C definió un nuevo tipo long double que las implementaciones podrían referirse al nuevo tipo de 80 bits o un double 64 bits. Desafortunadamente, aunque el propósito del tipo IEEE-754 de 80 bits era que todos los valores se promocionaran automáticamente al nuevo tipo de la forma en que habían sido promovidos al double , ANSI lo hizo para que el nuevo tipo pase a printf u otros métodos variados de manera diferente a otros tipos de coma flotante, lo que hace insostenible dicha promoción automática.

En consecuencia, ambos tipos de punto flotante que existían cuando se creó C pueden usar el mismo especificador de formato %f , pero el long double que se creó después requiere un especificador de formato %Lf diferente (con una L mayúscula ).

Parece que podría haber sido, hay (al menos en C99) modificadores de longitud que se pueden aplicar a int : %hhd , %hd , %ld y %lld significan caracteres con signed char , short , long y long long . Incluso hay un modificador de longitud aplicable al double : %Lf significa long double .

La pregunta es ¿por qué omitieron float ? Siguiendo el patrón, podría haber sido %hf .


Debido a promociones de argumento por defecto .

printf() es una función de argumento variable ( ... en su firma), todos los argumentos float se promueven al double .

C11 §6.5.2.2 Llamadas de función

6 Si la expresión que denota la función llamada tiene un tipo que no incluye un prototipo, las promociones de enteros se realizan en cada argumento, y los argumentos que tienen tipo float se promueven al double . Estos se denominan promociones de argumento por defecto.

7 La notación de puntos suspensivos en un declarador de prototipo de función hace que la conversión de tipo de argumento se detenga después del último parámetro declarado. Las promociones de argumento predeterminadas se realizan en argumentos finales.


Debido a las promociones de argumento predeterminadas al llamar a funciones variadic, los valores float se convierten implícitamente en double antes de la llamada a la función, y no hay forma de pasar un valor float a printf . Como no hay forma de pasar un valor float a printf , no es necesario un especificador de formato explícito para los valores float .

Dicho esto, AntoineL trajo un punto interesante en un comentario que %lf (actualmente utilizado en scanf para corresponder a un argumento tipo double * ) puede haber sido una vez " long float ", que era un sinónimo de tipo en días anteriores a C89 , de acuerdo con la página 42 de la justificación C99 . Según esa lógica, podría tener sentido que %f estuviera destinado a representar un valor float que se ha convertido en un double .

Con respecto a los modificadores de longitud hh y h , %hhu y %hu presentan un caso de uso bien definido para estos especificadores de formato: puede imprimir el byte menos significativo de un gran unsigned int unsigned short o un unsigned short sin unsigned short sin conversión, por ejemplo:

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

No está particularmente bien definido lo que resultará en una conversión estrecha de int a char o short , pero al menos está definido por la implementación, lo que significa que la implementación es necesaria para documentar esta decisión.

Siguiendo el patrón, debería haber sido %hf .

Siguiendo el patrón que ha observado, %hf debería convertir los valores fuera del rango de float nuevo a float . Sin embargo, ese tipo de conversión de reducción de double a float da como resultado un comportamiento indefinido , y no existe un unsigned float . El patrón que ves no tiene sentido.

Para ser formalmente correcto, %lf no denota un argumento long double , y si pasara un argumento long double estaría invocando un comportamiento indefinido . Es explícito de la documentación que:

l (ell) ... no tiene efecto en un siguiente especificador de conversión a , A , e , E , f , F , g o G

Me sorprende que nadie más haya captado esto. %lf denota un argumento double , al igual que %f . Si desea imprimir un long double , use %Lf (mayúscula).

En adelante, debería tener sentido que %lf para printf y scanf correspondan a argumentos double y double * ... %f es excepcional solo debido a las promociones de argumento predeterminadas, por las razones mencionadas anteriormente.

... y %Ld tampoco significa long . Lo que eso significa es un comportamiento indefinido .


Debido a que en las llamadas a funciones variadas en C, cualquier argumento float se promueve (es decir, se convierte) a un double , por lo que printf obtiene un double y usaría va_arg(arglist, double) para va_arg(arglist, double) en su implementación.

En el pasado (C89 y K&R C) cada argumento float se convertía en un double . El estándar actual omite esta promoción para funciones de aridad fija que tienen un prototipo explícito. Está relacionado con (y los detalles se explican en) las convenciones ABI y de llamada de la implementación. En términos prácticos, un valor float menudo se carga en un registro de punto flotante doble cuando se pasa como argumento, pero los detalles pueden variar. Lea la especificación ABI de Linux x86-64 como ejemplo.

Además, no hay ninguna razón práctica para dar una cadena de control de formato específico para float ya que puede ajustar el ancho de la salida (por ejemplo, con %8.5f ) según lo deseado, y %hd es mucho más útil (casi necesario) en scanf que en printf

Más allá de eso, supongo que la razón (para omitir %hf especificando float -promovido a double en la persona que llama- en printf ) es histórica : al principio, C era un lenguaje de programación del sistema, no uno de HPC (Fortran fue preferido en HPC tal vez hasta finales de la década de 1990) y la float no era muy importante; fue (y sigue siendo) considerado como short , una forma de reducir el consumo de memoria. Y las FPU actuales son lo suficientemente rápidas (en computadoras de escritorio o servidores) para evitar el uso de float excepto como un medio para usar menos memoria. Básicamente, debe creer que cada float está en algún lugar (tal vez dentro de la FPU o la CPU) convertido a double .

En realidad, su pregunta podría parafrasearse como: ¿ %hd qué existe %hd para printf (donde es básicamente inútil, ya que printf está obteniendo un int cuando lo pasa short ; sin embargo, scanf necesita!). No sé por qué, pero imagino que en la programación del sistema podría ser más útil.

Podría pasar tiempo presionando el próximo estándar ISO C para que %hf aceptado por printf para float (promovido a double en las llamadas de printf , como los short se promocionan a int ), con un comportamiento indefinido cuando el valor de precisión doble está fuera de límite para float -s, y simétricamente %hf aceptado por scanf para punteros float . Buena suerte en eso.


Del estándar ISO C11, 6.5.2.2 Function calls /6 y /7 , discutiendo llamadas a funciones en el contexto de expresiones (mi énfasis):

6 / Si la expresión que denota la función llamada tiene un tipo que no incluye un prototipo, las promociones de enteros se realizan en cada argumento, y los argumentos que tienen tipo flotante se promueven al doble. Estos se denominan promociones de argumento por defecto.

7 / Si la expresión que denota la función llamada tiene un tipo que incluye un prototipo, los argumentos se convierten implícitamente, como por asignación, a los tipos de los parámetros correspondientes, tomando el tipo de cada parámetro como la versión no calificada de Su tipo declarado. La notación de puntos suspensivos en un declarador de prototipo de función hace que la conversión de tipo de argumento se detenga después del último parámetro declarado. Las promociones de argumento predeterminadas se realizan en argumentos finales.

Esto significa que cualquier argumento float después de ... en el prototipo se convierte a double y la familia de llamadas printf se define de esa manera ( 7.21.6.11 et seq):

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

Entonces, dado que no hay forma de que las llamadas a la familia printf() reciban realmente un flotante, no tiene mucho sentido tener un especificador (o modificador) de formato especial para ello.


Leyendo la justificación de C, debajo de fscanf, se puede encontrar lo siguiente:

Una nueva característica de C99: los modificadores de longitud hh y ll se agregaron en C99. ll admite el nuevo tipo long long int. hh agrega la capacidad de tratar los tipos de caracteres igual que todos los demás tipos de enteros; Esto puede ser útil para implementar macros como SCNd8 en (ver 7.18).

Entonces, supuestamente, se agregó hh con el fin de proporcionar soporte para todos los nuevos tipos stdint.h . Esto podría explicar por qué se agregó un modificador de longitud para enteros pequeños pero no para flotadores pequeños.

No explica por qué C90 inconsistentemente tenía h pero no hh . El lenguaje especificado en C90 no siempre es coherente, así de simple. Y versiones posteriores han heredado la inconsistencia.


Teniendo en cuenta que scanf tiene especificadores de formato separados para flotante, doble o doble largo, no veo por qué printf y funciones similares no se implementaron de manera similar, pero así es como terminaron C / C ++ y los estándares.

Puede haber un problema con el tamaño mínimo para una operación push o pop según el procesador y el modo actual, pero esto podría haberse manejado con relleno predeterminado, similar a la alineación predeterminada de variables locales o variables en una estructura. Microsoft dejó de admitir los long double de 80 bits (10 bytes) cuando pasó de compiladores de 16 bits a 32/64 bits, ahora trata long double la misma forma que los double (64 bits / 8 bytes). Podrían haberlos acolchado a límites de 12 o 16 bytes según sea necesario, pero esto no se hizo.


%hhd , %hd , %ld y %lld a printf para que las cadenas de formato sean más consistentes con scanf , a pesar de que son redundantes para printf debido a las promociones de argumento predeterminadas.

Entonces, ¿por qué no se agregó %hf para float ? Eso es fácil: observando el comportamiento de scanf , float ya tiene un especificador de formato. Es %f . Y el especificador de formato para double es %lf .

Ese %lf es exactamente lo que C99 agregó a printf . Antes de C99, el comportamiento de %lf era indefinido (por omisión de cualquier definición en el estándar). A partir de C99, es sinónimo de %f .