Спецификатор ширины печати для поддержания точности значения с плавающей запятой
Существует ли printf
ширины printf
который может быть применен к спецификатору с плавающей точкой, который автоматически форматирует вывод до необходимого количества значащих цифр , чтобы при сканировании строки обратно было получено исходное значение с плавающей запятой?
Например, предположим, что я печатаю float
с точностью до 2
знаков после запятой:
float foobar = 0.9375; printf("%.2f", foobar); // prints out 0.94
Когда я сканирую вывод 0.94
, у меня нет стандартной совместимости с гарантией того, что я 0.9375
исходное 0.9375
плавающей запятой 0.9375
(в этом примере я, вероятно, не буду).
- Плавающая точка и целочисленные вычисления на современном оборудовании
- Изменение режима округления с плавающей запятой
- Почему числа с плавающей точкой неточны?
- Как представить номер FLOAT в памяти на C
- Как представить 0,1 в арифметике с плавающей точкой и десятичной запятой
Мне бы хотелось, чтобы printf
автоматически распечатал значение с плавающей запятой на необходимое количество значимых цифр, чтобы убедиться, что его можно отсканировать назад к исходному значению, переданному printf
.
Я мог бы использовать некоторые macros в float.h
чтобы получить максимальную ширину для перехода к printf
, но есть ли уже спецификатор для автоматической печати на нужное количество значимых цифр – или, по крайней мере, до максимальной ширины?
- В каком порядке следует добавлять поплавки, чтобы получить наиболее точный результат?
- Безопасно ли проверять значения с плавающей запятой на равенство 0?
- Разделение целых чисел на Java
- Оптимизация для быстрого умножения, но медленное добавление: FMA и doubleedouble
- Печать чисел с плавающей запятой из x86-64, по-видимому, требует сохранения% rbp
- Драйвер Microsoft ACE изменяет точность с плавающей запятой в остальной части моей программы
- плавать до неожиданного поведения
- Как числа с плавающей запятой сохраняются в памяти?
Я рекомендую использовать шестнадцатеричное решение @Jens Gustedt: используйте% a.
OP хочет «распечатать с максимальной точностью (или, по крайней мере, до самого значительного десятичного знака)».
Простым примером можно было бы напечатать один седьмой, как в:
#include 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 семейства макросов в
чтобы помочь нам.
Первый набор – это число значащих цифр для печати в строке в десятичном значении, поэтому при сканировании строки назад мы получаем исходную плавучую точку. Показаны минимальное значение C spec и образец 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 spec и образец C11-компилятора. Я считаю, что ansible pre-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-строка (набор макросов №2 для C89), как определить цифры для FP-string-FP (установить macros №1 после C89)? В общем, добавление 3 было результатом.
Теперь сколько значащих цифр для печати известно и управляется с помощью
.
Чтобы напечатать 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):
- Если ваш тип float: используйте
printf("%.9g", number)
. - Если ваш тип double: используйте
printf("%.17g", number)
.
НЕ используйте %f
, так как это определяет только сколько значащих цифр после десятичного числа и усечет небольшие числа. Для справки магические числа 9 и 17 можно найти в float.h
который определяет FLT_DECIMAL_DIG
и DBL_DECIMAL_DIG
.
Если вас интересует только бит (resp hex pattern), вы можете использовать формат %a
. Это гарантирует вам:
По умолчанию точность достаточна для точного представления значения, если существует точное представление в базе 2 и в противном случае достаточно велико, чтобы различать значения типа double.
Я должен добавить, что это доступно только с C99.
Нет, такой спецификатор ширины печати не существует для максимальной точности печати с плавающей запятой . Позвольте мне объяснить, почему.
Максимальная точность float
и double
является переменной и зависит от фактического значения float
или double
.
Вызов float
и double
хранятся в формате 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
не имеет представления о разнице между 1e27
и 1e27 + 0.1
.
r = 1e27; printf( "%.6f\n", r ) ; // 999999988484154753734934528.000000 r+=0.1 ; printf( "%.6f\n", r ) ; // still 999999988484154753734934528.000000
Это связано с тем, что вся точность (которая ограничена количеством бит мантиссы) используется для большей части числа, слева от десятичной.
Модификатор %.f
просто указывает, сколько десятичных значений вы хотите распечатать с номера поплавка до форматирования . Тот факт, что доступная точность зависит от размера номера , зависит от вас, как программиста . printf
не может / не обрабатывает это для вас.
Просто используйте macros из
и спецификатор преобразования переменной ширины ( ".*"
):
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) * * * 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]); }