[c] 부동 소수점 값의 정밀도를 유지하기위한 Printf 너비 지정자

문자열을 다시 스캔 할 때 원래 부동 소수점 값을 얻 printf도록 출력을 필요한 유효 자릿수 로 자동 형식화하는 부동 소수점 지정자에 적용 할 수 있는 너비 지정자가 있습니까?

예를 들어, 소수 자릿수 float의 정밀도로 a 를 인쇄한다고 가정 2합니다.

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

출력을 스캔 할 때 0.94원래 0.9375부동 소수점 값을 되 찾을 것이라는 표준 준수 보장이 없습니다 (이 예제에서는 아마도 그렇지 않을 것입니다).

에 전달 된 원래 값으로 다시 스캔 할 수 있도록 printf부동 소수점 값을 필요한 유효 자릿수 에 자동으로 인쇄 하는 방법을 알고 싶습니다 printf.

의 일부 매크로 float.h사용하여 전달할 최대 너비도출printf 있지만 필요한 유효 자릿수 또는 최소한 최대 너비 까지 자동으로 인쇄하는 지정자가 이미 있습니까?



답변

@Jens Gustedt 16 진수 솔루션을 권장합니다. % a를 사용합니다.

OP는 “최대 정밀도 (또는 최소한 최상위 소수까지)로 인쇄”를 원합니다.

간단한 예는 다음과 같이 7 분의 1을 인쇄하는 것입니다.

#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

정확한 십진 표현을 인쇄 하는 double것은 제한된 용도로 사용됩니다.

C는 <float.h>우리를 돕기 위해 2 개의 매크로 계열을 가지고 있습니다.
첫 번째 세트는 문자열에서 10 진수로 인쇄 할 유효 자릿수이므로 문자열을 다시 스캔 할 때 원래 부동 소수점을 얻습니다. 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 컴파일러 와 함께 표시됩니다 . 나는 C99 이전에 사용할 수 있다고 생각합니다.

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- 문자열 (C89에서 사용할 수있는 세트 # 2 매크로)을 알고 있다면 FP- 문자열 -FP (C89 이후에 사용 가능한 세트 # 1 매크로)의 자릿수를 어떻게 결정합니까? 일반적으로 3을 더하는 것이 결과입니다.

이제 인쇄 할 유효 자릿수는 <float.h>.

N 개의 유효 십진수 를 인쇄하려면 다양한 형식을 사용할 수 있습니다.

"%e"상기 정밀 필드는 자리수 리드 자리와 소수점. 그래서 - 1순서입니다. 참고 : 이것은 -1초기가 아닙니다.int 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).
  • 유형이 double 인 경우 printf("%.17g", number).

%f소수점 이하 유효 자릿수 만 지정하고 작은 숫자를자를 수 있으므로 사용하지 마십시오 . 참고로, 마법의 숫자 9와 17에서 찾을 수 float.h있는 정의 FLT_DECIMAL_DIGDBL_DECIMAL_DIG.


답변

비트 (각각 16 진수 패턴)에만 관심이있는 경우 %a형식을 사용할 수 있습니다 . 이는 다음을 보장합니다.

기본 정밀도는 2 진법의 정확한 표현이 존재하고 그렇지 않으면 double 유형의 값을 구별 할 수있을만큼 충분히 큰 경우 값의 정확한 표현에 충분합니다.

나는 이것이 C99 이후로만 가능하다는 것을 덧붙여 야 할 것입니다.


답변

아니요, 최대 정밀도로 부동 소수점을 인쇄하는 그러한 printf 너비 지정 자는 없습니다 . 이유를 설명하겠습니다.

float및 의 최대 정밀도 double가변적 이며 또는 의 실제 값 에 따라 다릅니다 .floatdouble

리콜 floatsign.exponent.mantissa 형식으로 double저장 됩니다. 이것은 큰 숫자보다 작은 숫자의 분수 구성 요소에 더 많은 비트가 사용 된다는 것을 의미합니다 .

여기에 이미지 설명 입력

예를 들어 float0.0과 0.1을 쉽게 구분할 수 있습니다.

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

그러나 float사이의 차이의 아무 생각이 없습니다 1e271e27 + 0.1.

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

이는 모든 정밀도 (가수 비트 수에 의해 제한됨)가 소수점 왼쪽 숫자의 많은 부분에 사용되기 때문입니다.

%.f수정 그냥 당신이 멀리로 플로트 번호에서 인쇄 할 얼마나 많은 소수 값 말한다 포맷이 간다. 사실 가능한 정확도 수의 크기에 따라 달라은 최대입니다 프로그래머 같이 처리 할 수. printf당신을 위해 그것을 처리 할 수 ​​없습니다 /하지 않습니다.


답변

다음의 매크로 <float.h>와 가변 너비 변환 지정자 ( ".*")를 사용하면됩니다.

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


답변

나는 인쇄 DBL_DECIMAL_DIG가 실제로 숫자의 이진 표현을 정확하게 보존 하는지 확인하기 위해 작은 실험을 실행합니다 . 내가 시도한 컴파일러와 C 라이브러리의 DBL_DECIMAL_DIG경우 실제로 필요한 자릿수이며 한 자릿수로 인쇄해도 심각한 문제가 발생 한다는 것이 밝혀졌습니다 .

#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

union {
    short s[4];
    double d;
} u;

void
test(int digits)
{
    int i, j;
    char buff[40];
    double d2;
    int n, num_equal, bin_equal;

    srand(17);
    n = num_equal = bin_equal = 0;
    for (i = 0; i < 1000000; i++) {
        for (j = 0; j < 4; j++)
            u.s[j] = (rand() << 8) ^ rand();
        if (isnan(u.d))
            continue;
        n++;
        sprintf(buff, "%.*g", digits, u.d);
        sscanf(buff, "%lg", &d2);
        if (u.d == d2)
            num_equal++;
        if (memcmp(&u.d, &d2, sizeof(double)) == 0)
            bin_equal++;
    }
    printf("Tested %d values with %d digits: %d found numericaly equal, %d found binary equal\n", n, digits, num_equal, bin_equal);
}

int
main()
{
    test(DBL_DECIMAL_DIG);
    test(DBL_DECIMAL_DIG - 1);
    return 0;
}

Microsoft의 C 컴파일러 19.00.24215.1 및 gcc 버전 7.4.0 20170516 (Debian 6.3.0-18 + deb9u1)으로 실행합니다. 소수를 하나 더 적게 사용하면 정확히 동일하게 비교되는 숫자의 수가 절반으로 줄어 듭니다. (저는 또한 rand()실제로 사용 된 대로 약 백만 개의 서로 다른 숫자를 생성 함을 확인했습니다 .) 다음은 자세한 결과입니다.

마이크로 소프트 C

17 자리 숫자로 999507 개의 값을 테스트했습니다. 999507은 숫자가 같고 999507은 이진수가 같음
16 자리 숫자로 999507 개의 값을 테스트했습니다. 545389는 숫자가 같고 545389는 이진수가 같음

GCC

17 자리 숫자로 999485 값을 테스트했습니다. 999485는 숫자가 같고 999485는 이진수가 같음
16 자리 숫자로 999485 값을 테스트했습니다. 545402는 수치가 같고 545402는 이진수가 같음


답변

답변에 대한 내 의견 중 하나에서 나는 질문이 묻는 것과 거의 같은 방식으로 부동 소수점 값의 모든 유효 숫자를 십진수 형식으로 인쇄하는 방법을 오랫동안 원했다고 한탄했습니다. 나는 마침내 자리에 앉아 그것을 썼다. 완벽하지는 않으며 추가 정보를 인쇄하는 데모 코드이지만 대부분 내 테스트에서 작동합니다. 테스트를 위해 그것을 구동하는 전체 래퍼 프로그램의 사본을 원하시면 저에게 알려주십시오.

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