Especificador de ancho de impresión para mantener la precisión del valor de punto flotante

Resuelto Vilhelm Gray asked hace 11 años • 9 respuestas

¿Existe un printfespecificador 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 floatcon una precisión de 2decimales:

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 printfde 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.hderivar 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?

Vilhelm Gray avatar May 30 '13 22:05 Vilhelm Gray
Aceptado

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.0resultados en el valor a continuación. También se muestran los doublenú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í - 1está en orden. Nota: Esto -1no 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 + 6ver 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.

chux - Reinstate Monica avatar Nov 11 '2013 01:11 chux - Reinstate Monica

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.hlos que definen FLT_DECIMAL_DIGy DBL_DECIMAL_DIG.

ccxvii avatar Jan 16 '2014 12:01 ccxvii

Si solo está interesado en la broca (resp. patrón hexadecimal), puede usar el %aformato. 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.

Jens Gustedt avatar May 30 '2013 15:05 Jens Gustedt

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 floaty doublees variable y depende del valor real de floato double.

Se recuperan floaty doublese 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.

ingrese la descripción de la imagen aquí

Por ejemplo, floatse 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 floatno tiene idea de la diferencia entre 1e27y 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 %.fmodificador 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 . printfNo puedo/no puedo manejar eso por ti.

bobobobo avatar Nov 18 '2013 13:11 bobobobo