C语言 printf 宽度说明符以保持浮点值的精度

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16839658/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 06:30:56  来源:igfitidea点击:

Printf width specifier to maintain precision of floating-point value

cfloating-pointprintfc99floating-point-precision

提问by Vilhelm Gray

Is there a printfwidth specifier which can be applied to a floating point specifier that would automatically format the output to the necessary number of significant digitssuch that when scanning the string back in, the original floating point value is acquired?

是否有一个printf宽度说明符可以应用于浮点说明符,该说明符会自动将输出格式化为必要数量的有效数字,以便在扫描回字符串时,获得原始浮点值?

For example, suppose I print a floatto a precision of 2decimal places:

例如,假设我float将 a打印到2小数位精度:

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

When I scan the output 0.94, I have no standards-compliant guarantee that I'll get the original 0.9375floating-point value back (in this example, I probably won't).

当我扫描输出时0.94,我没有符合标准的保证我会得到原始的0.9375浮点值(在这个例子中,我可能不会)。

I would like a way tell printfto automatically print the floating-point value to the necessary number of significant digitsto ensure that it can be scanned back to the original value passed to printf.

我想要一种方法告诉printf自动将浮点值打印到必要数量的有效数字,以确保它可以被扫描回传递给printf.

I could use some of the macros in float.hto derive the maximum widthto pass to printf, but is there already a specifier to automatically print to the necessary number of significant digits-- or at least to the maximum width?

我可以使用其中的一些宏float.h推导出要传递给的最大宽度printf,但是是否已经有一个说明符可以自动打印到必要数量的有效数字——或者至少是最大宽度?

采纳答案by chux - Reinstate Monica

I recommend @Jens Gustedt hexadecimal solution: use %a.

我推荐@Jens Gustedt 十六进制解决方案:使用 %a。

OP wants “print with maximum precision (or at least to the most significant decimal)”.

OP 想要“以最大精度(或至少到最重要的小数)打印”。

A simple example would be to print one seventh as in:

一个简单的例子是打印七分之一,如下所示:

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


But let's dig deeper ...

但是让我们深入挖掘......

Mathematically, the answer is "0.142857 142857 142857 ...", but we are using finite precision floating point numbers. Let's assume IEEE 754 double-precision binary. So the OneSeventh = 1.0/7.0results in the value below. Also shown are the preceding and following representable doublefloating point numbers.

在数学上,答案是“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

Printing the exactdecimal representation of a doublehas limited uses.

打印 a 的精确十进制表示的double用途有限。

C has 2 families of macros in <float.h>to help us.
The first set is the number of significantdigits to print in a string in decimal so when scanning the string back, we get the original floating point. There are shown with the C spec's minimumvalue and a sampleC11 compiler.

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)

The second set is the number of significantdigits a string may be scanned into a floating point and then the FP printed, still retaining the same string presentation. There are shown with the C spec's minimumvalue and a sampleC11 compiler. I believe available pre-C99.

第二组是一个字符串可能被扫描成浮点数然后打印 FP的有效数字的数量,仍然保留相同的字符串表示。显示了 C 规范的最小值示例C11 编译器。我相信在 C99 之前可用。

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

The first set of macros seems to meet OP's goal of significantdigits. But that macrois not always available.

第一组宏似乎符合 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

The "+ 3" was the crux of my previous answer. Its centered on if knowing the round-trip conversion string-FP-string (set #2 macros available C89), how would one determine the digits for FP-string-FP (set #1 macros available post C89)? In general, add 3 was the result.

“+ 3”是我之前回答的关键。它的中心是如果知道往返转换字符串-FP-string(设置#2 宏可用C89),如何确定FP-string-FP 的数字(设置#1 宏可用后C89)?一般来说,结果是加 3。

Now how many significantdigits to print is known and driven via <float.h>.

现在要打印多少有效数字是已知的并通过<float.h>.

To print N significantdecimal digits one may use various formats.

要打印 N 个有效的十进制数字,可以使用各种格式。

With "%e", the precisionfield is the number of digits afterthe lead digit and decimal point. So - 1is in order. Note: This -1is not in the initial int Digs = DECIMAL_DIG;

"%e"精度字段是前导数字和小数点的位数。顺理成章- 1。注意:这-1不是在初始int Digs = DECIMAL_DIG;

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

With "%f", the precisionfield is the number of digits afterthe decimal point. For a number like OneSeventh/1000000.0, one would need OP_DBL_Digs + 6to see all the significantdigits.

使用"%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

Note: Many are use to "%f". That displays 6 digits after the decimal point; 6 is the display default, not the precision of the number.

注意:许多用于"%f". 显示小数点后6位;6 是显示默认值,而不是数字的精度。

回答by ccxvii

The short answer to print floating point numbers losslessly (such that they can be read back in to exactly the same number, except NaN and Infinity):

无损打印浮点数的简短答案(这样它们可以被读回完全相同的数字,除了 NaN 和 Infinity):

  • If your type is float: use printf("%.9g", number).
  • If your type is double: use printf("%.17g", number).
  • 如果您的类型是 float:使用printf("%.9g", number).
  • 如果您的类型是 double:使用printf("%.17g", number).

Do NOT use %f, since that only specifies how many significant digits after the decimal and will truncate small numbers. For reference, the magic numbers 9 and 17 can be found in float.hwhich defines FLT_DECIMAL_DIGand DBL_DECIMAL_DIG.

不要使用%f,因为它只指定小数点后有多少有效数字并会截断小数。作为参考,幻数 9 和 17 可以在float.h其中找到定义FLT_DECIMAL_DIGDBL_DECIMAL_DIG

回答by Jens Gustedt

If you are only interested in the bit (resp hex pattern) you could use the %aformat. This guarantees you:

如果您只对位(resp 十六进制模式)感兴趣,则可以使用该%a格式。这保证您:

The default precision suffices for an exact representation of the value if an exact representation in base 2 exists and otherwise is sufficiently large to distinguish values of type double.

如果存在以 2 为基数的精确表示,则默认精度足以精确表示值,否则足够大以区分 double 类型的值。

I'd have to add that this is only available since C99.

我必须补充一点,这仅在 C99 之后可用。

回答by bobobobo

No, there is no such printf width specifier to print floating-point with maximum precision. Let me explain why.

不,没有这样的printf 宽度说明符以最大精度打印浮点数。让我解释一下原因。

The maximum precision of floatand doubleis variable, and dependent on the actual valueof the floator double.

的最大精度floatdouble变量,以及依赖于实际值floatdouble

Recall floatand doubleare stored in sign.exponent.mantissaformat. This means that there are many more bits used for the fractional component for small numbersthan for big numbers.

调用floatdoublesign.exponent.mantissa格式存储。这意味着用于小数的小数部分比用于大数的位多得多。

enter image description here

在此处输入图片说明

For example, floatcan easily distinguish between 0.0 and 0.1.

例如,float可以轻松区分 0.0 和 0.1。

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

But floathas no idea of the difference between 1e27and 1e27 + 0.1.

但是,float有没有之间的差异的想法1e271e27 + 0.1

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

This is because all the precision(which is limited by the number of mantissa bits) is used up for the large part of the number, left of the decimal.

这是因为所有精度(受尾数位数的限制)都被用于小数点左边的大部分数字。

The %.fmodifier just says how many decimal values you want to print from the float number as far as formattinggoes. The fact that the accuracy available depends on the size of the numberis up to you as the programmerto handle. printfcan't/doesn't handle that for you.

%.f修改只是说你要多少十进制值尽可能从浮点数打印格式去。该事实可用的精度取决于数量的大小达到你作为程序员来处理。 printf不能/不能为你处理。

回答by bobobobo

Simply use the macros from <float.h>and the variable-width conversion specifier (".*"):

只需使用宏 from<float.h>和可变宽度转换说明符 ( ".*"):

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

回答by Diomidis Spinellis

I run a small experiment to verify that printing with DBL_DECIMAL_DIGdoes indeed exactly preserve the number's binary representation. It turned out that for the compilers and C libraries I tried, DBL_DECIMAL_DIGis indeed the number of digits required, and printing with even one digit less creates a significant problem.

我运行了一个小实验来验证打印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;
}

I run this with Microsoft's C compiler 19.00.24215.1 and gcc version 7.4.0 20170516 (Debian 6.3.0-18+deb9u1). Using one less decimal digit halves the number of numbers that compare exactly equal. (I also verified that rand()as used indeed produces about one million different numbers.) Here are the detailed results.

我使用 Microsoft 的 C 编译器 19.00.24215.1 和 gcc 版本 7.4.0 20170516 (Debian 6.3.0-18+deb9u1) 运行它。使用少一位十进制数字将完全相等的数字数量减半。(我还验证了rand()as used 确实产生了大约一百万个不同的数字。)这是详细的结果。

Microsoft C

微软C

Tested 999507 values with 17 digits: 999507 found numericaly equal, 999507 found binary equal
Tested 999507 values with 16 digits: 545389 found numericaly equal, 545389 found binary equal

GCC

海湾合作委员会

Tested 999485 values with 17 digits: 999485 found numericaly equal, 999485 found binary equal
Tested 999485 values with 16 digits: 545402 found numericaly equal, 545402 found binary equal

回答by Greg A. Woods

In one of my comments to an answer I lamented that I've long wanted some way to print all the significant digits in a floating point value in decimal form, in much the same way the as the question asks. Well I finally sat down and wrote it. It's not quite perfect, and this is demo code that prints additional information, but it mostly works for my tests. Please let me know if you (i.e. anyone) would like a copy of the whole wrapper program which drives it for testing.

在我对答案的评论之一中,我感叹我一直想要某种方式以十进制形式打印浮点值中的所有有效数字,与问题所问的方式大致相同。好吧,我终于坐下来写了。它不是很完美,这是打印附加信息的演示代码,但它主要适用于我的测试。如果您(即任何人)想要驱动它进行测试的整个包装程序的副本,请告诉我。

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

回答by Stéphane Mottelet

To my knowledge, there is a well diffused algorithm allowing to output to the necessary number of significant digits such that when scanning the string back in, the original floating point value is acquiredin dtoa.cwritten by Daniel Gay, which is available hereon Netlib (see also the associated paper). This code is used e.g. in Python, MySQL, Scilab, and many others.

据我所知,有一个很好扩散算法允许输出的显著位数必要的数量,使得扫描中,原浮点值获取的字符串返回时,dtoa.c由丹尼尔·盖伊,这是可以写在这里利用在Netlib(见还有相关的论文)。此代码用于例如 Python、MySQL、Scilab 和许多其他代码。