Printf宽度说明符,用于保持浮点值的精度

Vil*_*ray 84 c floating-point printf c99 floating-point-precision

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

例如,假设我打印float2小数位数的精度:

float foobar = 0.9375;
printf("%.2f", foobar);    // prints out 0.94
Run Code Online (Sandbox Code Playgroud)

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

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

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

chu*_*ica 77

我推荐@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
Run Code Online (Sandbox Code Playgroud)

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

在数学上,答案是"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
Run Code Online (Sandbox Code Playgroud)

打印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)
Run Code Online (Sandbox Code Playgroud)

第二组是字符串可以扫描到浮点然后FP打印的有效位数,仍保留相同的字符串表示.显示了C规范的最小值样本 C11编译器.我相信可用前C99.

FLT_DIG   6, 6 (float)
DBL_DIG  10, 15 (double)
LDBL_DIG 10, 18 (long double)
Run Code Online (Sandbox Code Playgroud)

第一组宏似乎符合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
Run Code Online (Sandbox Code Playgroud)

"+ 3"是我之前回答的关键.它的核心是,如果知道往返转换字符串-FP-string(设置#2宏可用C89),如何确定FP-string-FP的数字(在C89之后设置#1宏)?通常,结果是添加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
Run Code Online (Sandbox Code Playgroud)

使用时"%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
Run Code Online (Sandbox Code Playgroud)

注意:很多人都习惯了"%f".在小数点后显示6位数; 6是显示默认值,而不是数字的精度.


ccx*_*vii 54

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

  • 如果您的类型是浮点数:使用printf("%.9g", number).
  • 如果您的类型是双倍:使用printf("%.17g", number).

请勿使用%f,因为它仅指定小数点后的有效位数,并将截断小数字.作为参考,可以找到幻数9和17,float.h其中定义FLT_DECIMAL_DIGDBL_DECIMAL_DIG.

  • %g打印数字的精度需要尽可能多的数字,当数字很小或很大(1e-5而不是.00005)时,更喜欢指数语法,并跳过任何尾随零(1而不是1.00000). (10认同)
  • 你能解释`%g`说明符吗? (5认同)
  • @truthseeker要表示[IEEE 754 binary64](http://en.wikipedia.org/wiki/Double-precision_floating-point_format)代码确实需要打印_at至少15个有效小数位.但是明确性需要17,因为二进制数(2,4,8等)的精度变化和十进制数(10,100,1000等)永远不会是相同的数字(1.0除外).例如:在`0.1`之上的2`double`值:`1.000_0000_0000_0000_2e-01`,`1.000_0000_0000_0000_3e-01`需要17位数来区分. (4认同)
  • @chux - 你误解了%.16g的行为; 对于区分1.000_0000_0000_0000_20000-01和1.000_0000_0000_0000_0000_3e-01的示例,它不够*.%.17g是必需的. (3认同)
  • @ccxvii`DBL_DECIMAL_DIG`提供的数字,如17,是_total_所需的有效位数.`%.16g`和`%.16e`在小数点前打印1位,之后打印16位小数.所以`printf("%.*g",DBL_DECIMAL_DIG - 1,number)就足够了.当然,"%.17g"中的额外数字是可以的. (2认同)

Jen*_*edt 22

如果您只对位(resp hex模式)感兴趣,可以使用该%a格式.这保证您:

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

我必须补充说,这只有在C99之后才可用.


bob*_*obo 12

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

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

调用floatdoublesign.exponent.mantissa格式存储.这意味着对于小数而不是大数,小数分量使用的位数要多得多.

在此输入图像描述

例如,float可以很容易地区分0.0和0.1.

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

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

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

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

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

  • 这在一定程度上是正确的,但它不能回答问题,您对OP的要求感到困惑。他在问是否可以查询浮点数提供的有效[十进制]数字的数量,并且您断言没有这种东西(即没有FLT_DIG),这是错误的。 (3认同)
  • 这是对将浮点值精确打印到特定小数位的限制的极好解释.但是,我相信我对原来选择的词语过于模糊,所以我更新了我的问题以避免使用"最高精度"一词,希望它可以消除这种混乱. (2认同)

小智 9

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

float f = 3.14159265358979323846;
printf("%.*f\n", FLT_DIG, f);
Run Code Online (Sandbox Code Playgroud)

  • @bobobobo不,`%.6f`不等同,因为`FLT_DIG`并不总是6.谁关心效率?I/O已经很昂贵了,一个数字或多或少的精度不会成为瓶颈. (5认同)
  • 您可以通过预处理器字符串化避免运行时开销,我认为...... (4认同)
  • +1但这最适合'%e`,对于'%f`来说效果不太好:只有当它知道要打印的值接近于"1.0"时才会这样. (3认同)
  • `%e`为非常小的数字打印有效数字,'%f`不打印.例如`x = 1e-100`.`%.5f`打印'0.00000`(完全失去进动).`%.5e`打印`1.00000e-100`. (3认同)
  • @OliCharlesworth你的意思是这样的:`printf("%."FLT_DIG"f \n",f);` (2认同)

Sté*_*let 7

据我所知,有一种广泛传播的算法,允许输出必要数量的有效数字,这样当扫描回字符串时,原始浮点值将由David dtoa.cGay 编写,可在 Netlib 上找到(请参阅还有相关论文)。此代码用于 Python、MySQL、Scilab 等许多其他语言中。


Dio*_*lis 6

我运行了一个小实验来验证打印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;
}
Run Code Online (Sandbox Code Playgroud)

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

微软C

用 17 位测试了 999507 个值:999507 发现数值相等,999507 发现二进制相等
测试了 999507 个 16 位值:545389 发现数值相等,545389 发现二进制相等

海湾合作委员会

用 17 位测试了 999485 个值:999485 发现数值相等,999485 发现二进制相等
测试了 999485 个 16 位值:545402 发现数值相等,545402 发现二进制相等


Gre*_*ods 5

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

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]);
}
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

205384 次

最近记录:

6 年,1 月 前