Especificador de ancho de impresión para mantener la precisión del valor de punto flotante
¿Existe un printf
especificador de ancho que pueda aplicarse a un especificador de punto flotante que formatee automáticamente la salida con el número necesario de dígitos significativos? de modo que al volver a escanear la cadena, se adquiera el valor de punto flotante original?
Por ejemplo, supongamos que imprimo a float
con una precisión de 2
decimales:
float foobar = 0.9375;
printf("%.2f", foobar); // prints out 0.94
Cuando escaneo el resultado 0.94
, no tengo ninguna garantía de que obtendré el original conforme a los estándares.0.9375
valor de punto flotante original (en este ejemplo, probablemente no lo haga).
Me gustaría encontrar una forma printf
de imprimir automáticamente el valor de punto flotante con la cantidad necesaria de dígitos significativos para garantizar que se pueda escanear nuevamente al valor original pasado aprintf
.
Podría usar algunas de las macros para float.h
derivar el ancho máximo al que pasar printf
, pero ¿existe ya un especificador para imprimir automáticamente la cantidad necesaria de dígitos significativos , o al menos hasta el ancho máximo?
Recomiendo la solución hexadecimal de @Jens Gustedt: use %a.
OP quiere "imprimir con la máxima precisión (o al menos hasta el decimal más significativo)".
Un ejemplo sencillo sería imprimir un séptimo como en:
#include <float.h>
int Digs = DECIMAL_DIG;
double OneSeventh = 1.0/7.0;
printf("%.*e\n", Digs, OneSeventh);
// 1.428571428571428492127e-01
Pero profundicemos más...
Matemáticamente, la respuesta es "0,142857 142857 142857 ...", pero estamos utilizando números de coma flotante de precisión finita. Supongamos binario de doble precisión IEEE 754 . Entonces los OneSeventh = 1.0/7.0
resultados en el valor a continuación. También se muestran los double
números de coma flotante representables anteriores y siguientes.
OneSeventh before = 0.1428571428571428 214571170656199683435261249542236328125
OneSeventh = 0.1428571428571428 49212692681248881854116916656494140625
OneSeventh after = 0.1428571428571428 769682682968777953647077083587646484375
Imprimir la representación decimal exactadouble
de a tiene usos limitados.
C tiene 2 familias de macros <float.h>
para ayudarnos.
El primer conjunto es el número de dígitos significativos que se imprimirán en una cadena en formato decimal, de modo que al escanear la cadena hacia atrás, obtenemos el punto flotante original. Se muestran con el valor mínimo de la especificación C y un compilador C11 de muestra .
FLT_DECIMAL_DIG 6, 9 (float) (C11)
DBL_DECIMAL_DIG 10, 17 (double) (C11)
LDBL_DECIMAL_DIG 10, 21 (long double) (C11)
DECIMAL_DIG 10, 21 (widest supported floating type) (C99)
El segundo conjunto es el número de dígitos significativos que se puede escanear una cadena en un punto flotante y luego imprimir el FP, conservando aún la misma presentación de la cadena. Se muestran con el valor mínimo de la especificación C y un compilador C11 de muestra . Creo que está disponible antes de C99.
FLT_DIG 6, 6 (float)
DBL_DIG 10, 15 (double)
LDBL_DIG 10, 18 (long double)
El primer conjunto de macros parece cumplir con el objetivo de OP de dígitos significativos . Pero esa macro no siempre está disponible.
#ifdef DBL_DECIMAL_DIG
#define OP_DBL_Digs (DBL_DECIMAL_DIG)
#else
#ifdef DECIMAL_DIG
#define OP_DBL_Digs (DECIMAL_DIG)
#else
#define OP_DBL_Digs (DBL_DIG + 3)
#endif
#endif
El "+ 3" fue el quid de mi respuesta anterior. Se centra en si, conociendo la cadena de conversión de ida y vuelta-cadena FP (conjunto de macros n.° 2 disponibles en C89), ¿cómo se determinarían los dígitos para cadena FP-FP (conjunto de macros n.° 1 disponibles después de C89)? En general, el resultado fue sumar 3.
Ahora se conoce y controla a través de cuántos dígitos significativos imprimir <float.h>
.
Para imprimir N dígitos decimales significativos se pueden utilizar varios formatos.
Con "%e"
, el campo de precisión es el número de dígitos después del dígito inicial y el punto decimal. Así - 1
está en orden. Nota: Esto -1
no está en la inicial.int Digs = DECIMAL_DIG;
printf("%.*e\n", OP_DBL_Digs - 1, OneSeventh);
// 1.4285714285714285e-01
Con "%f"
, el campo de precisión es el número de dígitos después del punto decimal. Para un número como OneSeventh/1000000.0
, sería necesario OP_DBL_Digs + 6
ver todos los dígitos significativos .
printf("%.*f\n", OP_DBL_Digs , OneSeventh);
// 0.14285714285714285
printf("%.*f\n", OP_DBL_Digs + 6, OneSeventh/1000000.0);
// 0.00000014285714285714285
Nota: Muchos están acostumbrados a "%f"
. Eso muestra 6 dígitos después del punto decimal; 6 es el valor predeterminado de visualización, no la precisión del número.
La respuesta corta para imprimir números de punto flotante sin pérdidas (de modo que puedan volver a leerse exactamente en el mismo número, excepto NaN e Infinity):
- Si su tipo es flotante: use
printf("%.9g", number)
. - Si tu tipo es doble: usa
printf("%.17g", number)
.
NO use %f
, ya que eso solo especifica cuántos dígitos significativos después del decimal y truncará los números pequeños. Como referencia, los números mágicos 9 y 17 se pueden encontrar en float.h
los que definen FLT_DECIMAL_DIG
y DBL_DECIMAL_DIG
.
Si solo está interesado en la broca (resp. patrón hexadecimal), puede usar el %a
formato. Esto te garantiza:
La precisión predeterminada es suficiente para una representación exacta del valor si existe una representación exacta en base 2 y, por lo demás, es lo suficientemente grande para distinguir valores de tipo doble.
Debo agregar que esto solo está disponible desde C99.
No, no existe tal especificador de ancho de impresión para imprimir puntos flotantes con la máxima precisión . Déjame explicarte por qué.
La precisión máxima de float
y double
es variable y depende del valor real de float
o double
.
Se recuperan float
y double
se almacenan en formato sign.exponent.mantissa . Esto significa que se utilizan muchos más bits para el componente fraccionario de números pequeños que para números grandes.
Por ejemplo, float
se puede distinguir fácilmente entre 0,0 y 0,1.
float r = 0;
printf( "%.6f\n", r ) ; // 0.000000
r+=0.1 ;
printf( "%.6f\n", r ) ; // 0.100000
Pero float
no tiene idea de la diferencia entre 1e27
y 1e27 + 0.1
.
r = 1e27;
printf( "%.6f\n", r ) ; // 999999988484154753734934528.000000
r+=0.1 ;
printf( "%.6f\n", r ) ; // still 999999988484154753734934528.000000
Esto se debe a que toda la precisión (que está limitada por el número de bits de mantisa) se agota para la mayor parte del número, a la izquierda del decimal.
El %.f
modificador simplemente dice cuántos valores decimales desea imprimir del número flotante en lo que respecta al formato . El hecho de que la precisión disponible dependa del tamaño del número depende de usted como programador . printf
No puedo/no puedo manejar eso por ti.