Printf宽度说明符可保持浮点值的精度


103

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

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

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

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

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

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


4
@bobobobo因此,您只是建议人们

1
@ H2CO3不,我不建议使用“空前的假设”,我建议使用printf( "%f", val );已经可移植,高效且默认的方法。
bobobobo

2
@bobobobo为了使我可以将其添加到答案中,您是否可以引用C99标准中的子句,该子句指出如果未指定精度,则printf语句将默认以最大精度输出float类型?
Vilhelm Gray

1
@VilhelmGray随着@chux的深入,关于您特定对象的实际精度有一些非常复杂的数学运算double。当您double变得非常大(非常远离1.0)时,它实际上在小数部分(值部分小于1.0)中的准确性降低。因此,您在这里不能真正得到满意的答案,因为您的问题中存在一个错误的假设(即所有floats / doubles均相等)
bobobobo

2
@Vilhelm Gray C11dr 5.2.4.2.2“ ...十进制数字n,以便具有p个基数b数字的任何浮点数都可以四舍五入为n十进制数字的浮点数,然后再次返回而无需更改到该值,p log10 bb是10的幂+ p log10b⎤,否则FLT_DECIMAL_DIG 6 DBL_DECIMAL_DIG 10 LDBL_DECIMAL_DIG 10 ...““ 6,10,10是最小值
chux-恢复莫妮卡

Answers:


92

我建议@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编译器。相信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是显示默认值,而不是数字的精度。


为什么1.428571428571428492127e-01而不是1.428571428571428492127e-0 0 1,“ e”后的位数应为3?
user1024 2015年

12.12.5浮点转换表示了默认精度%f为6
蒋经国姚明

1
@Jingguo Yao同意该引用说:“精度指定'%f'的小数点后跟多少个数字”。此处的“精度”一词在数学上不是用的,而只是用来定义小数点后的位数。1234567890.123,数学上具有13位精度或有效数字。0.000000000123具有3位数的数学精度,而不是13。浮点数是对数分布的。此答案使用有效数字和数学精度
chux-恢复莫妮卡2015年

1
@Slipp D. Thompson“显示了C规范的最小值和一个示例 C11编译器。”
chux-恢复莫妮卡2015年

1
确实您是对的-我的技巧只对介于1.0到1.0eDBL_DIG之间的值有效,可以说这是真正适合打印的唯一范围"%f"。使用"%e"你表现当然是一个更好的办法全面和有效的一个体面的答案(虽然也许它不如使用"%a"可能是,如果它是可用的,当然"%a"应可如果`DBL_DECIMAL_DIG是)。我一直希望有一个格式说明符,该说明符始终舍入到最大精度(而不是硬编码的6个小数位)。
格雷格·A·伍兹

66

简单无误地打印浮点数的答案(这样,可以将它们读回为完全相同的数字,但NaN和Infinity除外):

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

不要使用%f,因为它仅指定小数点后多少位有效数字,并将截断小数字。作为参考,可以找到float.h其中定义FLT_DECIMAL_DIG和的魔术数字9和17 DBL_DECIMAL_DIG


6
您能解释说明%g符吗?
Vilhelm Gray 2014年

14
%g使用精确度所需的位数显示数字,当数字较小或较大(1e-5而不是.00005)时,首选指数格式,并跳过任何尾随零(1而不是1.00000)。
ccxvii 2014年

4
@truthseeker要表示IEEE 754 binary64代码,确实确实需要打印至少 15个有效的小数位。但是明确性需要17,因为二进制数(分别为2,4,8等)和十进制数(分别为10,100,1000等)的精度变化永远不会是相同的数字(1.0除外)。示例:double上面的2个值0.11.000_0000_0000_0000_2e-011.000_0000_0000_0000_3e-01需要17位数字来区分。
chux-恢复莫妮卡2015年

3
@chux-您误会了%.16g的行为;它是不是足够你从1.000_0000_0000_0000_3e-01区分1.000_0000_0000_0000_2e-01的例子。%.17g是必需的。
唐·哈奇

1
@Don Hatch我同意这"%.16g"是不够的,"%.17g"并且 "%.16e"已经足够。的详细信息%g被我误记了。
chux-恢复莫妮卡

23

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

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

我必须补充一点,这仅自C99起可用。


16

不,没有这样的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

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

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

这是因为所有精度(受尾数位数限制)已用尽了大部分数字,即小数点左边。

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


2
这是对将浮点值精确打印到特定小数位的局限性的很好解释。但是,我相信我对最初的单词选择不太清楚,因此我更新了我的问题,以避免使用“最大精度”一词,希望它可以消除混乱。
Vilhelm Gray 2013年

它仍然取决于您要打印的数字的值。
bobobobo

3
这在一定程度上是对的,但它不能回答问题,您对OP的要求感到困惑。他在问是否可以查询a float提供的有效[十进制]数字的数量,并且您断言没有这样的事情(即没有FLT_DIG),这是错误的。

@ H2CO3也许您应该编辑我的帖子并下注(j / k)。这个答案断言FLT_DIG没有任何意义。这个答案断言可用的小数位数取决于float内的值
bobobobo

1
您是否假设格式字母必须为“ f”?我认为这不是必需的。我的问题的解读是,在OP正在寻找一些能产生无损往返的printf格式说明,所以@ccxvii的回答(‘%0.9克’浮法‘%0.17克’双)是好一个 通过从中删除“宽度”一词,可能会更好地表达该问​​题。
唐·哈奇

11

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

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

2
@OliCharlesworth您的意思是这样的:printf("%." FLT_DIG "f\n", f);
Vilhelm Gray

3
+1,但这最适合%e,但不适用于%f:仅当知道打印值接近时1.0
Pascal Cuoq

3
%e打印非常小的数字的有效数字,%f而不显示。例如x = 1e-100%.5f打印0.00000(完全进动)。 %.5e版画1.00000e-100
chux-恢复莫妮卡

1
@bobobobo另外,您错了,因为它“产生了更准确的原因”。FLT_DIG被定义为它被定义为的值,这是有原因的。如果是6,那是因为float不能保持超过6位的精度。如果使用进行打印%.7f,则最后一位没有意义。拒绝投票之前请三思。

5
@bobobobo不,%.6f不是等效的,因为FLT_DIG并不总是6。谁在乎效率?I / O已经非常昂贵,地狱般,多一点或少一点的精度都不会成为瓶颈。

5

我进行了一个小实验,以验证使用印刷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发现二进制相等

海湾合作委员会

测试了带有17位数字的999485值:999485发现数值相等,999485发现二进制相等
测试了16位数字的999485值:545402发现数值相等,545402发现二进制相等

1
“使用Microsoft的C编译器运行它”->该编译器可能具有RAND_MAX == 32767。考虑u.s[j] = (rand() << 8) ^ rand();等,以使某些所有位得到一个机会,在为0或1
恢复莫妮卡- chux

实际上,其RAND_MAX为32767,因此您的建议正确。
Diomidis Spinellis

1
我更新了帖子,以按照@ chux-ReinstateMonica的建议处理RAND_MAX。结果类似于之前获得的结果。
Diomidis Spinellis

3

在对答案的评论之一中,我感叹我一直想以某种方式以十进制形式将所有有效数字以浮点值的形式打印出来,与问题所要求的方式几乎相同。好吧,我终于坐下来写下了。它不是很完美,这是演示代码,可以打印其他信息,但是大多数情况下可以用于我的测试。请让我知道您(即任何人)是否想要整个包装程序的副本,该程序可以驱动该包装程序进行测试。

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

我不在乎它是否回答了问题-这确实令人印象深刻。它花了一些思考,应该得到承认和赞扬。如果您以某种方式(无论是在这里还是在其他地方)包含完整的测试代码,也许会很好,但是即使没有它,这确实是一件好事。为此+1!
Pryftan

0

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.