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

12 minutos de lectura

avatar de usuario
Vilhelm gris

Hay un printf especificador de ancho que se puede aplicar a un especificador de punto flotante que formatearía automáticamente la salida al número necesario de dígitos significantes de tal manera que al escanear la cadena nuevamente, se adquiere el valor de punto flotante original?

Por ejemplo, supongamos que imprimo un float a una precisión de 2 lugares decimales:

float foobar = 0.9375;
printf("%.2f", foobar);    // prints out 0.94

Cuando escaneo la salida 0.94no tengo ninguna garantía que cumpla con los estándares de que obtendré el original 0.9375 valor de punto flotante de nuevo (en este ejemplo, probablemente no lo haré).

quisiera una forma de decir printf para imprimir automáticamente el valor de coma flotante en el número necesario de dígitos significantes para asegurarse de que se puede escanear de nuevo al valor original pasado a printf.

Podría usar algunas de las macros en float.h para derivar el ancho máximo para pasar a printfpero ¿ya existe un especificador para imprimir automáticamente en el número necesario de dígitos significantes — o al menos al ancho máximo?

  • @bobobobo Entonces eres solo recomendando que uno use una suposición improvisada en lugar de adoptar el enfoque portátil?

    usuario529758

    10 de noviembre de 2013 a las 21:23

  • @H2CO3 No, no recomendaría usar “una suposición fuera del aire”, sugeriría usar printf( "%f", val ); que ya es portátil, eficiente y predeterminado.

    – bobobobo

    11 de noviembre de 2013 a las 18:32

  • @bobobobo Para que pueda agregarlo a las respuestas, ¿podría citar la cláusula en el estándar C99 que establece que la declaración printf generará el tipo flotante en máxima precisión por defecto si no se especifica precisión?

    – Vilhelm Grey

    11 de noviembre de 2013 a las 19:20

  • @VilhelmGray Bueno, a medida que entra @chux, hay algunas matemáticas bastante complicadas en cuanto a la precisión real para su particular double. Como tu double se vuelve extremadamente grande (muy lejos de 1.0), en realidad se vuelve menos precisa en la parte decimal (parte de valor inferior a 1,0). Así que realmente no puede tener una respuesta satisfactoria aquí, porque su pregunta tiene una suposición falsa (a saber, que todos floats/doubles son creados iguales)

    – bobobobo

    11 de noviembre de 2013 a las 19:53

  • @Vilhelm Gray C11dr 5.2.4.2.2 “… número de dígitos decimales, n, tal que cualquier número de punto flotante con p radix b dígitos se puede redondear a un número de punto flotante con n dígitos decimales y viceversa sin cambios al valor, p log10 bb es una potencia de 10 ⎡1 + p log10 b⎤ de lo contrario FLT_DECIMAL_DIG 6 DBL_DECIMAL_DIG 10 LDBL_DECIMAL_DIG 10 …” Los 6,10,10 son los mínimo valores.

    – chux – Reincorporar a Monica

    22 de noviembre de 2013 a las 16:15

La respuesta corta para imprimir números de coma flotante sin pérdidas (de modo que se puedan volver a leer exactamente en el mismo número, excepto NaN e Infinity):

  • Si su tipo es flotante: use printf("%.9g", number).
  • Si su tipo es doble: use printf("%.17g", number).

No utilice %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 que define FLT_DECIMAL_DIG y DBL_DECIMAL_DIG.

  • ¿Serías capaz de explicar el %g especificador?

    – Vilhelm Grey

    16 de enero de 2014 a las 14:56


  • %g imprime el número con tantos dígitos como sea necesario para mayor precisión, prefiriendo la sintaxis exponencial cuando los números son pequeños o grandes (1e-5 en lugar de .00005) y omitiendo los ceros finales (1 en lugar de 1.00000).

    – ccxvii

    16 de enero de 2014 a las 23:49

  • En mi compilador (C++Builder XE), es DBL_DIG en lugar de DBL_DECIMAL_DIG y el valor es 15 en lugar de 17.

    -Albert Wiersch

    7 abr 2014 a las 0:40

  • La longitud de la mantisa de valor doble es de 53 bits (1 bit está implícito). La precisión del valor doble es, por lo tanto, 53 / log2(10) = 15,95 decimales. Entonces, si desea representar el número IEEE 754 en formato decimal sin ambigüedades, necesita al menos ceil (53 / log2 (10)) = 16 lugares decimales. En mis programas estoy usando 17 lugares decimales solo para estar seguro. No sé exactamente qué valor es correcto 16 o 17. Pero 15 lugares seguramente son insuficientes.

    – buscador de la verdad

    21 de noviembre de 2014 a las 11:37


  • @chux – Estás equivocado sobre el comportamiento de %.16g; es no adecuado para su ejemplo de distinguir 1.000_0000_0000_0000_2e-01 de 1.000_0000_0000_0000_3e-01. Se necesita %.17g.

    – Don Escotilla

    14 de junio de 2016 a las 1:03

  • Esta es una excelente explicación de las limitaciones de imprimir con precisión valores de coma flotante en lugares decimales específicos. Sin embargo, creo que fui demasiado ambiguo con mi elección original de palabras, por lo que actualicé mi pregunta para evitar el término “máxima precisión” con la esperanza de que pueda aclarar la confusión.

    – Vilhelm Grey

    18 de noviembre de 2013 a las 19:24


  • Todavía depende del valor del número que está imprimiendo.

    – bobobobo

    18 de noviembre de 2013 a las 19:43

  • esto es parcialmente cierto, pero no responde la pregunta y está confundido en cuanto a lo que OP está preguntando. Él está preguntando si uno puede consultar el número de significativos [decimal] dígitos un float proporciona, y usted afirma que no existe tal cosa (es decir, que no hay FLT_DIG), Cuál está mal.

    usuario529758

    23 de diciembre de 2013 a las 9:20

  • ¿Está asumiendo que la letra de formato tiene que ser “f”? No creo que sea necesario. Mi lectura de la pregunta es que el OP está buscando algunos especificador de formato printf que produce un viaje de ida y vuelta sin pérdidas, por lo que la respuesta de @ccxvii (“%.9g” para float, “%.17g” para double) es buena. Probablemente la pregunta estaría mejor redactada eliminando la palabra “ancho”.

    – Don Escotilla

    23/09/2016 a las 19:46

  • Esto no es lo que estaba haciendo la pregunta.

    – Jarrod Smith

    10 de junio de 2019 a las 5:56

avatar de usuario
Stéphane Mottelet

Que yo sepa, existe un algoritmo bien difundido que permite salida al número necesario de dígitos significativos de modo que al escanear la cadena de nuevo, se adquiera el valor de punto flotante original en dtoa.c escrito por David Gay, que está disponible aquí en Netlib (ver también el asociado papel). Este código se usa, por ejemplo, en Python, MySQL, Scilab y muchos otros.

  • En mi humilde opinión, esta es la verdadera respuesta correcta. Esta debería ser la respuesta más votada en la parte superior.

    – Yuki

    26 de marzo de 2021 a las 23:36

  • David Gay, no Daniel Gay. (David M. Gay para ser específico. No estoy seguro de qué significa M).

    – Marca VY

    18 de septiembre de 2021 a las 4:00

avatar de usuario
Jens Gustedt

Si solo está interesado en el bit (patrón hexadecimal resp), 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, de lo contrario, es lo suficientemente grande como para distinguir valores de tipo doble.

Debo agregar que esto solo está disponible desde C99.

  • En mi humilde opinión, esta es la verdadera respuesta correcta. Esta debería ser la respuesta más votada en la parte superior.

    – Yuki

    26 de marzo de 2021 a las 23:36

  • David Gay, no Daniel Gay. (David M. Gay para ser específico. No estoy seguro de qué significa M).

    – Marca VY

    18 de septiembre de 2021 a las 4:00

avatar de usuario
Greg A. Maderas

En uno de mis comentarios a una respuesta, lamenté que hace tiempo que quería alguna forma de imprimir todos los dígitos significativos en un valor de coma flotante en forma decimal, de la misma manera que la pregunta. Bueno, finalmente me senté y lo escribí. No es del todo perfecto, y este es un código de demostración que imprime información adicional, pero funciona principalmente para mis pruebas. Por favor, hágamelo saber si usted (es decir, alguien) desea una copia de todo el programa contenedor que lo controla para la prueba.

static unsigned int
ilog10(uintmax_t v);

/*
 * Note:  As presented this demo code prints a whole line including information
 * about how the form was arrived with, as well as in certain cases a couple of
 * interesting details about the number, such as the number of decimal places,
 * and possibley the magnitude of the value and the number of significant
 * digits.
 */
void
print_decimal(double d)
{
        size_t sigdig;
        int dplaces;
        double flintmax;

        /*
         * If we really want to see a plain decimal presentation with all of
         * the possible significant digits of precision for a floating point
         * number, then we must calculate the correct number of decimal places
         * to show with "%.*f" as follows.
         *
         * This is in lieu of always using either full on scientific notation
         * with "%e" (where the presentation is always in decimal format so we
         * can directly print the maximum number of significant digits
         * supported by the representation, taking into acount the one digit
         * represented by by the leading digit)
         *
         *        printf("%1.*e", DBL_DECIMAL_DIG - 1, d)
         *
         * or using the built-in human-friendly formatting with "%g" (where a
         * '*' parameter is used as the number of significant digits to print
         * and so we can just print exactly the maximum number supported by the
         * representation)
         *
         *         printf("%.*g", DBL_DECIMAL_DIG, d)
         *
         *
         * N.B.:  If we want the printed result to again survive a round-trip
         * conversion to binary and back, and to be rounded to a human-friendly
         * number, then we can only print DBL_DIG significant digits (instead
         * of the larger DBL_DECIMAL_DIG digits).
         *
         * Note:  "flintmax" here refers to the largest consecutive integer
         * that can be safely stored in a floating point variable without
         * losing precision.
         */
#ifdef PRINT_ROUND_TRIP_SAFE
# ifdef DBL_DIG
        sigdig = DBL_DIG;
# else
        sigdig = ilog10(uipow(FLT_RADIX, DBL_MANT_DIG - 1));
# endif
#else
# ifdef DBL_DECIMAL_DIG
        sigdig = DBL_DECIMAL_DIG;
# else
        sigdig = (size_t) lrint(ceil(DBL_MANT_DIG * log10((double) FLT_RADIX))) + 1;
# endif
#endif
        flintmax = pow((double) FLT_RADIX, (double) DBL_MANT_DIG); /* xxx use uipow() */
        if (d == 0.0) {
                printf("z = %.*s\n", (int) sigdig + 1, "0.000000000000000000000"); /* 21 */
        } else if (fabs(d) >= 0.1 &&
                   fabs(d) <= flintmax) {
                dplaces = (int) (sigdig - (size_t) lrint(ceil(log10(ceil(fabs(d))))));
                if (dplaces < 0) {
                        /* XXX this is likely never less than -1 */
                        /*
                         * XXX the last digit is not significant!!! XXX
                         *
                         * This should also be printed with sprintf() and edited...
                         */
                        printf("R = %.0f [%d too many significant digits!!!, zero decimal places]\n", d, abs(dplaces));
                } else if (dplaces == 0) {
                        /*
                         * The decimal fraction here is not significant and
                         * should always be zero  (XXX I've never seen this)
                         */
                        printf("R = %.0f [zero decimal places]\n", d);
                } else {
                        if (fabs(d) == 1.0) {
                                /*
                                 * This is a special case where the calculation
                                 * is off by one because log10(1.0) is 0, but
                                 * we still have the leading '1' whole digit to
                                 * count as a significant digit.
                                 */
#if 0
                                printf("ceil(1.0) = %f, log10(ceil(1.0)) = %f, ceil(log10(ceil(1.0))) = %f\n",
                                       ceil(fabs(d)), log10(ceil(fabs(d))), ceil(log10(ceil(fabs(d)))));
#endif
                                dplaces--;
                        }
                        /* this is really the "useful" range of %f */
                        printf("r = %.*f [%d decimal places]\n", dplaces, d, dplaces);
                }
        } else {
                if (fabs(d) < 1.0) {
                        int lz;

                        lz = abs((int) lrint(floor(log10(fabs(d)))));
                        /* i.e. add # of leading zeros to the precision */
                        dplaces = (int) sigdig - 1 + lz;
                        printf("f = %.*f [%d decimal places]\n", dplaces, d, dplaces);
                } else {                /* d > flintmax */
                        size_t n;
                        size_t i;
                        char *df;

                        /*
                         * hmmmm...  the easy way to suppress the "invalid",
                         * i.e. non-significant digits is to do a string
                         * replacement of all dgits after the first
                         * DBL_DECIMAL_DIG to convert them to zeros, and to
                         * round the least significant digit.
                         */
                        df = malloc((size_t) 1);
                        n = (size_t) snprintf(df, (size_t) 1, "%.1f", d);
                        n++;                /* for the NUL */
                        df = realloc(df, n);
                        (void) snprintf(df, n, "%.1f", d);
                        if ((n - 2) > sigdig) {
                                /*
                                 * XXX rounding the integer part here is "hard"
                                 * -- we would have to convert the digits up to
                                 * this point back into a binary format and
                                 * round that value appropriately in order to
                                 * do it correctly.
                                 */
                                if (df[sigdig] >= '5' && df[sigdig] <= '9') {
                                        if (df[sigdig - 1] == '9') {
                                                /*
                                                 * xxx fixing this is left as
                                                 * an exercise to the reader!
                                                 */
                                                printf("F = *** failed to round integer part at the least significant digit!!! ***\n");
                                                free(df);
                                                return;
                                        } else {
                                                df[sigdig - 1]++;
                                        }
                                }
                                for (i = sigdig; df[i] != '.'; i++) {
                                        df[i] = '0';
                                }
                        } else {
                                i = n - 1; /* less the NUL */
                                if (isnan(d) || isinf(d)) {
                                        sigdig = 0; /* "nan" or "inf" */
                                }
                        }
                        printf("F = %.*s. [0 decimal places, %lu digits, %lu digits significant]\n",
                               (int) i, df, (unsigned long int) i, (unsigned long int) sigdig);
                        free(df);
                }
        }

        return;
}


static unsigned int
msb(uintmax_t v)
{
        unsigned int mb = 0;

        while (v >>= 1) { /* unroll for more speed...  (see ilog2()) */
                mb++;
        }

        return mb;
}

static unsigned int
ilog10(uintmax_t v)
{
        unsigned int r;
        static unsigned long long int const PowersOf10[] =
                { 1LLU, 10LLU, 100LLU, 1000LLU, 10000LLU, 100000LLU, 1000000LLU,
                  10000000LLU, 100000000LLU, 1000000000LLU, 10000000000LLU,
                  100000000000LLU, 1000000000000LLU, 10000000000000LLU,
                  100000000000000LLU, 1000000000000000LLU, 10000000000000000LLU,
                  100000000000000000LLU, 1000000000000000000LLU,
                  10000000000000000000LLU };

        if (!v) {
                return ~0U;
        }
        /*
         * By the relationship "log10(v) = log2(v) / log2(10)", we need to
         * multiply "log2(v)" by "1 / log2(10)", which is approximately
         * 1233/4096, or (1233, followed by a right shift of 12).
         *
         * Finally, since the result is only an approximation that may be off
         * by one, the exact value is found by subtracting "v < PowersOf10[r]"
         * from the result.
         */
        r = ((msb(v) * 1233) >> 12) + 1;

        return r - (v < PowersOf10[r]);
}

  • No me importa si responde a la pregunta o no, esto es realmente impresionante de hacer. Tomó un poco de reflexión y debe ser reconocido y elogiado. Tal vez sería bueno si incluyera de alguna manera (ya sea aquí o de otra manera) el código completo para la prueba, pero incluso sin él, este es realmente un buen trabajo. ¡Ten un +1 para eso!

    – Priftan

    15 oct 2019 a las 14:58

  • @ GregA.Woods Por supuesto que tienes razón. Lamento haber comprado un no problema. Comentario eliminado. (Finalmente encontré algo de tiempo para mirar tu respuesta en profundidad).

    – chux – Reincorporar a Monica

    5 de abril a las 3:09


  • @ GregA.Woods Code tiene problemas con los números negativos, ya que el lugar del último dígito está desviado por uno. Tal vez use un snprintf(df, n, "% .1f", d); (espacio añadido) para fijar la longitud del búfer, ya sea + o -.

    – chux – Reincorporar a Monica

    5 de abril a las 3:13


  • Ah, sí, números negativos. ¡Gracias por tu comentario! Haré una nota en la fuente original e intentaré mejorarla cuando tenga algo de tiempo libre.

    – Greg A. Woods

    5 abr a las 17:55

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad