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 aldouble
. 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óna
,A
,e
,E
,f
,F
,g
oG
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
.