programing

부동 소수점 값의 정밀도를 유지하기 위한 폭 지정자 인쇄

prostudy 2022. 5. 12. 21:55
반응형

부동 소수점 값의 정밀도를 유지하기 위한 폭 지정자 인쇄

a가 있는가printf문자열을 다시 스캔할 때 원래 부동소수 값을 획득할 수 있도록 필요한 수의 유의한 숫자로 출력을 자동 포맷할 수 있는 부동소수 지정자에 적용할 수 있는 폭 지정기

예를 들어, a를 인쇄한다고 가정합시다.float2소수 자릿수:

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

a의 정확한 소수점 표시 인쇄double용도가 제한되어 있다.

C에는 2개의 매크로가 있다.<float.h>우리를 돕기 위해
첫 번째 세트는 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 문자열(세트 #2 매크로 사용 가능 C89)을 알고 있는 경우 FP 문자열-FP(세트 #1 매크로 사용 가능 C89 이후)의 자릿수를 어떻게 결정할 것인가?일반적으로 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 float: :사를 한다.printf("%.9g", number).
  • 유형이 이중인 경우: 사용printf("%.17g", number).

사용 안 함%f이는 소수점 이후 유의한 자릿수만 명시하고 작은 숫자는 잘리기 때문이다.참고로, 마법의 숫자 9와 17은 에서 찾을 수 있다.float.h어떤 것이 정의되어 있는지FLT_DECIMAL_DIG그리고DBL_DECIMAL_DIG.

아니, 부동 소수점을 최대한 정밀하게 인쇄할 수 있는 인쇄 f 너비 지정자가 없다.왜 그런지 설명해 줄게.

최 의 float그리고double가변성이며, 실제 값에 따라 달라짐float, 또는double.

리콜float그리고doublesign.content.csibissa 형식으로 저장됨.이것은 큰 숫자에 비해 작은 숫자에 대한 부분 성분에 사용되는 비트가 더 많다는 것을 의미한다.

여기에 이미지 설명을 입력하십시오.

예를 들어,float0.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

모든 정밀도(맨티사 비트 수에 의해 제한되는)가 십진수 왼쪽의 큰 숫자에 다 소모되기 때문이다.

%.f수식어는 형식에 따라 부동 소수점 숫자에서 인쇄할 소수점 값을 말한다.이용 가능한 정확도가 숫자의 크기에 따라 달라진다는 사실은 프로그래머로서 당신에게 달려있다. printf당신을 위해 그것을 처리할 수 없다/그것을 할 수 없다.

내가 알기로는, 문자열을 다시 스캔할 때 원래의 부동소수 값이 다음에서 획득되도록 필요한 수의 유의한 숫자로 출력할 수 있는 잘 확산된 알고리즘이 있다.dtoa.c여기 Netlib에서 이용할 수 있는 David Gay에 의해 쓰여졌다(관련 논문도 참조).이 코드는 Python, MySQL, Scilab 및 기타 여러 곳에서 사용된다.

대답에 대한 나의 논평 중 하나에서 나는 오랫동안 모든 중요한 숫자를 십진수 형태로, 질문이 요구하는 것과 같은 방식으로, 부동 소수점 값으로 인쇄할 수 있는 어떤 방법을 원해왔다고 한탄했다.흠, 마침내 나는 앉아서 그것을 썼다.완벽하진 않아. 그리고 이건 추가 정보를 출력하는 데모 코드야. 하지만 대부분 내 테스트에 효과가 있어.테스트를 위해 포장지 프로그램을 구동하는 전체 사본을 원하시는 분(즉, 누구라도)이 있으시면 알려주십시오.

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

비트((각 hex턴 반복)을할 수 .%a형식을 갖추다이를 통해 다음을 보장할 수 있다.

베이스 2에 정확한 표현이 존재하며, 그 외의 경우 타입 2의 값을 구별할 수 있을 정도로 충분히 큰 경우, 값을 정확하게 표현할 수 있는 기본 정밀도가 충분하다.

이건 C99 이후에만 사용 가능하다는 점을 덧붙여야겠습니다.

는 작은 하는 것이 은색인쇄라는 을 증명한다.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;
}

마이크로소프트의 C 컴파일러 19.00.24215.1 및 gcc 버전 7.4.0 20170516(Debian 6.3.0-18+deb9u1)과 함께 이것을 실행한다.소수점 이하 1자리 사용으로 정확히 비교하는 숫자의 반을 반반으로 한다.(또한 확인했다.rand()실제로 사용된 것과 같이 약 백만 개의 서로 다른 숫자를 산출한다.)자세한 결과는 다음과 같다.

마이크로소프트 C

17자리 숫자로 999507 값 테스트: 999507은 숫자, 999507은 이진수가 같음16자리 999507 값 테스트: 545389는 숫자, 545389는 2진수 동일함

GCC

17자리 숫자 999485 테스트: 999485는 숫자, 999485는 2진수 동일함16자리 숫자로 999485개 값 테스트: 545402는 숫자, 545402는 2진수 동일함

다음 매크로만 사용하십시오.<float.h>및 가변 폭 변환 지정자(".*"):

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

참조URL: https://stackoverflow.com/questions/16839658/printf-width-specifier-to-maintain-precision-of-floating-point-value

반응형