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

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

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

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

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

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

我可以使用float.h一些macros来派生出传递给printf 的最大宽度 ,但是是否已经存在一个说明符来自动打印到必要数量的有效数字 – 或者至less是最大宽度?

我推荐@Jens Gustedthex解决scheme:使用%a。

OP希望“以最大精度(或至less到最高有效小数)打印”。

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

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

但是让我们深入挖掘…

在math上,答案是“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>有两个macros系列来帮助我们。
第一组是在十进制string中打印的有效位数,所以当扫描string时,我们得到原始的浮点数。 用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) 

第二组是一个string可以被扫描到一个浮点,然后FP打印的有效位数,仍然保持相同的string表示。 用C规范的最小值样本 C11编译器显示。 我相信可用的前C99。

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

第一套macros似乎符合OP的有效数字的目标。 但是这个macros并不总是可用的。

 #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”是我以前答案的关键。 它的中心是如果知道往返转换string-FP-string(设置#2macros可用C89),如何确定FP-string-FP的数字(设置C89后可用的#1macros)? 一般来说,加3就是结果。

现在有多less有效数字可以通过<float.h>和驱动。

要打印N个重要的小数位,可以使用各种格式。

使用"%e"精度字段是前导数字和小数点后面的位数。 所以- 1是为了。 注意:这个-1 is not in the initial 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除外),以便能够读回完全相同的数字:

  • 如果你的types是float:use printf("%.9g", number)
  • 如果你的types是double:use printf("%.17g", number)

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

如果你只对位(resphex模式)感兴趣,你可以使用%a格式。 这保证你:

如果基数2中的确切表示存在,那么缺省精度就足够精确地表示值,否则足够大以区分doubletypes的值。

我不得不补充说,这是自C99以来才可用。

不,没有这样的Printf宽度特定的打印浮点的最大精度 。 让我解释一下为什么。

floatdouble的最大精度是可变的 ,并且取决于floatdouble实际值

回忆floatdouble存储在sign.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修饰符只是说格式化的时候 ,你想从浮点数打印多less个十进制值。 事实上, 可用准确性取决于数量的大小由程序员来处理。 printf不能/不会为你处理。

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

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

在我的回答之一,我感叹,我一直想以某种方式打印浮点值的十进制forms的所有有效数字,就像问题所要求的几乎一样。 那么我终于坐下来写下来了。 这不是很完美,这是演示代码,打印额外的信息,但它主要适用于我的testing。 请让我知道,如果你(即任何人)想要整个包装程序的副本,驱动它进行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) * * * NB: 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))))); /* ie 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", * ie 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]); }