Спецификатор ширины Printf для поддержания точности значения с плавающей запятой


есть printf спецификатор ширины, который может быть применен к спецификатору с плавающей запятой, который автоматически форматирует вывод на необходимое число значащих цифр таким образом, что при сканировании строки обратно в исходное значение с плавающей запятой получается?

например, предположим, что я печать float в точности 2 после запятой:

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

когда я сканирую выход 0.94, у меня нет стандартов-уступчивый гарантия, что я получу оригинал 0.9375 значение с плавающей запятой назад (в этом примере я, вероятно, не буду).

Я хотел бы так сказать printf для автоматической печати значения с плавающей запятой на необходимое число значащих цифр чтобы убедиться, что он может быть отсканирован обратно к исходному значению, переданному в printf.

я мог бы использовать некоторые макросы в float.h до извлечь максимальную ширину перейти к printf, но это есть уже есть спецификатор для автоматической печати на нужное число значащих цифр -- или хотя бы до максимальной ширины?

6 64

6 ответов:

Я рекомендую @ Jens Gustedt шестнадцатеричное решение: используйте %a.

OP хочет " печатать с максимальной точностью (или, по крайней мере, с наиболее значимым десятичным числом)".

простым примером может быть печать одной седьмой, как в:

#include <float.h>
int Digs = DECIMAL_DIG;
double OneSeventh = 1.0/7.0;
printf("%.*e\n", Digs, OneSeventh);
// 1.428571428571428492127e-01

но давайте копать глубже ...

математически, ответ "0.142857 142857 142857 ...", но мы используем числа с плавающей запятой конечной точности. Предположим двойная точность IEEE 754 двоичный. Так что OneSeventh = 1.0/7.0 приводит к значению ниже. Также показаны предыдущие и следующие представимые double числа с плавающей точкой.

OneSeventh before = 0.1428571428571428 214571170656199683435261249542236328125
OneSeventh        = 0.1428571428571428 49212692681248881854116916656494140625
OneSeventh after  = 0.1428571428571428 769682682968777953647077083587646484375

печати точно десятичное представление a double имеет ограниченное применение.

C имеет 2 семейства макросов в <float.h>, чтобы помочь нам.
Первый набор-это число значительное цифры для печати в строке в десятичном формате, поэтому при сканировании строки обратно, мы получаем оригинальная плавающая точка. Там показаны со спецификацией C минимум значение и пример компилятор C11.

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)

второй набор-это число значительное цифры строка может быть отсканирована с плавающей запятой, а затем напечатана FP, все еще сохраняя то же представление строки. Там показаны со спецификацией C минимум значение и пример компилятор C11. Я считаю, что доступно предварительно С99.

FLT_DIG   6, 6 (float)
DBL_DIG  10, 15 (double)
LDBL_DIG 10, 18 (long double)

первый набор макросов, похоже, соответствует цели OP значительное цифр. Но это макрос не всегда доступен.

#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

"+ 3" было сутью моего предыдущего ответа. Его центр сосредоточен на том, чтобы знать строку преобразования туда и обратно-FP-string (набор макросов #2, доступных C89), как можно определить цифры для FP-string-FP (набор макросов #1, доступных после C89)? В общем, добавить 3 был результат.

сколько значительное цифры для печати известны и управляются через <float.h>.

напечатать N значительное десятичные цифры можно использовать различные форматы.

С "%e" на точность поле-это количество цифр после ведущая цифра и десятичная точка. Так что - 1 в порядке. Примечание: Это -1 is not in the initialint Digs = DECIMAL_DIG;'

printf("%.*e\n", OP_DBL_Digs - 1, OneSeventh);
// 1.4285714285714285e-01

С "%f" на точность поле-это количество цифр после запятой. Для такого числа, как OneSeventh/1000000.0, одно OP_DBL_Digs + 6 чтобы увидеть все значительное цифр.

printf("%.*f\n", OP_DBL_Digs    , OneSeventh);
// 0.14285714285714285
printf("%.*f\n", OP_DBL_Digs + 6, OneSeventh/1000000.0);
// 0.00000014285714285714285

Примечание: многие из них используют "%f". Это отображает 6 цифр после десятичной точки; 6-это дисплей по умолчанию, а не точность числа.

короткий ответ для печати чисел с плавающей запятой без потерь (такие, что они могут быть прочитаны вернемся к точно такому же числу, кроме NaN и Infinity):

  • если ваш тип float: использовать printf("%.9g", number).
  • если ваш тип двойной: используйте printf("%.17g", number).

не использовать %f, так как это только указывает, сколько значащих цифр после десятичной и будет усекать небольшие числа. Для справки, магические числа 9 и 17 можно найти в float.h что определяет FLT_DECIMAL_DIG и DBL_DECIMAL_DIG.

Если вы заинтересованы только в бит (resp hex pattern) вы можете использовать . Это гарантирует вам:

в точность по умолчанию достаточна для точного представления значения, если точное представление в базе 2 существует и в противном случае достаточно велико, чтобы различать значения типа double.

Я должен добавить, что это доступно только с C99.

нет, такого нет спецификатор ширины Printf для печати с плавающей запятой с максимальной точностью. Позвольте мне объяснить почему.

максимальная точность float и double - это переменная, и зависит от фактическое значение на float или double.

Напомним float и double хранящиеся в знак.показатель.мантисса. Это значит, что есть много биты, используемые для дробного компонента для малых чисел чем для больших чисел.

enter image description here

например, float можно легко различить между 0.0 и 0.1.

float r = 0;
printf( "%.6f\n", r ) ; // 0.000000
r+=0.1 ;
printf( "%.6f\n", r ) ; // 0.100000

но float понятия не имеет о разнице между 1e27 и 1e27 + 0.1.

r = 1e27;
printf( "%.6f\n", r ) ; // 999999988484154753734934528.000000
r+=0.1 ;
printf( "%.6f\n", r ) ; // still 999999988484154753734934528.000000

это так точность (ограниченное количество мантиссы) используется для большей части числа, слева от десятичный.

The %.f модификатор просто говорит, сколько десятичных значений вы хотите напечатать из числа с плавающей точкой, насколько форматирование идет. Дело в том, что точность доступная зависит от размера номера до ты как программист для обработки. printf не могу / не справляется с этим для вас.

просто используйте макросы из <float.h> и спецификатор преобразования переменной ширины (".*"):

float f = 3.14159265358979323846;
printf("%.*f\n", FLT_DIG, f);

в одном из моих комментариев к ответу я пожаловался, что давно хотел каким-то образом напечатать все значащие цифры в значении с плавающей запятой в десятичной форме, во многом так же, как задается вопрос. Ну я, наконец, сел и написал его. Это не совсем идеально, и это демо-код, который печатает дополнительную информацию, но он в основном работает для моих тестов. Пожалуйста, дайте мне знать, если вы (т. е. кто-нибудь) хотели бы получить копию всей программы-оболочки, которая управляет ею тестирование.

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]);
}