Спецификатор ширины Printf для поддержания точности значения с плавающей запятой
есть printf
спецификатор ширины, который может быть применен к спецификатору с плавающей запятой, который автоматически форматирует вывод до необходимого числа значащих цифр такое, что при сканировании строки обратно в исходное значение с плавающей запятой приобретается?
например, предположим, что я печать float
в точности 2
после запятой:
float foobar = 0.9375;
printf("%.2f", foobar); // prints out 0.94
когда я сканирую вывод 0.94
, у меня нет стандартов-уступчивый гарантирую, что получу оригинал 0.9375
значение с плавающей запятой назад (в этом примере я, вероятно, не буду).
Я хотел бы так сказать printf
для автоматической печати значения с плавающей запятой на необходимое число значащих цифр чтобы убедиться, что он может быть отсканирован обратно к исходному значению, переданному в printf
.
я мог бы использовать некоторые макросы в float.h
to извлечь максимальную ширину перейти к printf
, но это есть уже спецификатор для автоматической печати на необходимое число значащих цифр -- или хотя бы до максимальной ширины?
6 ответов
Я рекомендую шестнадцатеричное решение @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
печати точно десятичное представление 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. Я считаю доступным предварительно С99.
FLT_DIG 6, 6 (float)
DBL_DIG 10, 15 (double)
LDBL_DIG 10, 18 (long double)
первый набор макросов, похоже, соответствует цели OP значительное цифр. Но это! .. --33-->макрос не всегда доступен.
#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-string (set #2 macros available C89), как определить цифры для FP-string-FP (set #1 macros available post C89)? В общем, add 3 был результат.
сколько значительное цифры для печати известны и управляются через <float.h>
.
напечатать N значительное десятичные цифры можно использовать различные форматы.
С "%e"
, the точность поле-это количество цифр после ведущая цифра и десятичная точка.
Так что - 1
в порядке. Примечание: Это -1 is not in the initial
int Digs = DECIMAL_DIG;'
printf("%.*e\n", OP_DBL_Digs - 1, OneSeventh);
// 1.4285714285714285e-01
С "%f"
, the точность поле-это количество цифр после запятой.
Для такого числа, как 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: использовать
printf("%.9g", number)
. - если ваш тип double: используйте
printf("%.17g", number)
.
не использовать %f
, так как это указывает только, сколько значащих цифр после десятичного знака и будет усекать небольшие числа. Для справки, магические числа 9 и 17 можно найти в float.h
что определяет FLT_DECIMAL_DIG
и DBL_DECIMAL_DIG
.
Если вас интересует только бит (шестнадцатеричный шаблон resp), вы можете использовать . Это гарантирует вам:
в точность по умолчанию достаточна для точного представления значения, если точное представление в базе 2 существует и в противном случае достаточно велико, чтобы различать значения типа double.
Я должен добавить, что это доступно только с C99.
нет таких ширины printf specificer для печати с плавающей точкой с максимальной точностью. Позвольте объяснить почему.
максимальная точность float
и double
is переменная, и зависит от фактическое значение на float
или double
.
Напомним float
и double
хранящиеся в знак.показатель.мантисса!--16-->. Это значит, что есть много биты, используемые для дробной компоненты для малых чисел чем для больших чисел.
например, float
легко отличить между 0.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
модификатор просто говорит, сколько десятичных значений вы хотите напечатать из числа float, насколько форматирование идет. Дело в том, что точность доступная зависит от размера числа до ты как программист для обработки. printf
не может / не справляется с этим для вас.
просто используйте макросы из <float.h>
и спецификатор преобразования переменной ширины (".*"
):
float f = 3.14159265358979323846;
printf("%.*f\n", FLT_DIG, f);
в одном из моих комментариев к ответу я посетовал, что я давно хотел каким-то образом напечатать все значащие цифры в значении с плавающей запятой в десятичной форме, почти так же, как задается вопрос. Наконец я сел и написал. Это не совсем идеально, и это демо-код, который печатает дополнительную информацию, но он в основном работает для моих тестов. Пожалуйста, дайте мне знать, если вы (т. е. кто-нибудь) хотели бы получить копию всей программы-оболочки, которая управляет ею для тестирование.
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]);
}