Especificador de largura de impressão para manter a precisão do valor de ponto flutuante

Existe um especificador de largura de printf que pode ser aplicado a um especificador de ponto flutuante que formata automaticamente a saída para o número necessário de dígitos significativos, de modo que, ao varrer a seqüência de caracteres de volta, o valor de ponto flutuante original seja adquirido?

Por exemplo, suponha que eu imprima um float com uma precisão de 2 casas decimais:

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

Quando eu escaneio a saída 0.94 , não tenho nenhuma garantia de conformidade com os padrões, de modo que recuperarei o valor de ponto flutuante original de 0.9375 (neste exemplo, provavelmente não 0.9375 ).

Eu gostaria de uma maneira dizer printf para imprimir automaticamente o valor de ponto flutuante para o número necessário de dígitos significativos para garantir que ele possa ser digitalizado de volta para o valor original passado para printf .

Eu poderia usar algumas das macros em float.h para derivar a largura máxima para passar para printf , mas já existe um especificador para imprimir automaticamente o número necessário de dígitos significativos – ou pelo menos até a largura máxima?

Eu recomendo a solução hexadecimal @Jens Gustedt: use% a.

OP quer “imprimir com precisão máxima (ou pelo menos para o decimal mais significativo)”.

Um exemplo simples seria imprimir um sétimo como em:

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

Mas vamos cavar mais fundo …

Matematicamente, a resposta é “0.142857 142857 142857 …”, mas estamos usando números de ponto flutuante de precisão finita. Vamos supor que o binário de precisão dupla IEEE 754 . Portanto, o OneSeventh = 1.0/7.0 resulta no valor abaixo. Também são mostrados os números de ponto flutuante double representáveis ​​anteriores e seguintes.

 OneSeventh before = 0.1428571428571428 214571170656199683435261249542236328125 OneSeventh = 0.1428571428571428 49212692681248881854116916656494140625 OneSeventh after = 0.1428571428571428 769682682968777953647077083587646484375 

Imprimir a representação decimal exata de um double tem usos limitados.

C tem 2 famílias de macros em para nos ajudar.
O primeiro conjunto é o número de dígitos significativos a serem impressos em uma string em decimal para que, ao examinar a string, obtemos o ponto flutuante original. São mostrados com o valor mínimo da especificação C e um compilador C11 de amostra .

 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) 

O segundo conjunto é o número de dígitos significativos que uma seqüência de caracteres pode ser digitalizada em um ponto flutuante e, em seguida, o FP impresso, ainda mantendo a mesma apresentação de seqüência de caracteres. São mostrados com o valor mínimo da especificação C e um compilador C11 de amostra . Eu acredito disponível pré-C99.

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

O primeiro conjunto de macros parece atender ao objective do OP de dígitos significativos . Mas essa macro nem sempre está disponível.

 #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 

O “+ 3” foi o cerne da minha resposta anterior. É centrado em saber se a string de conversão de ida e volta-FP-string (conjunto # 2 macros disponíveis C89), como se determinaria os dígitos para FP-string-FP (conjunto # 1 macros disponíveis post C89)? Em geral, adicionar 3 foi o resultado.

Agora, quantos dígitos significativos imprimir são conhecidos e orientados por .

Para imprimir N dígitos decimais, pode-se usar vários formatos.

Com "%e" , o campo de precisão é o número de dígitos após o dígito da liderança e o ponto decimal. Então - 1 está em ordem. Nota: Este -1 is not in the initial ín digs -1 is not in the initial = DECIMAL_DIG; `

 printf("%.*e\n", OP_DBL_Digs - 1, OneSeventh); // 1.4285714285714285e-01 

Com "%f" , o campo de precisão é o número de dígitos após o ponto decimal. Para um número como OneSeventh/1000000.0 , seria necessário OP_DBL_Digs + 6 para ver todos os dígitos significativos .

 printf("%.*f\n", OP_DBL_Digs , OneSeventh); // 0.14285714285714285 printf("%.*f\n", OP_DBL_Digs + 6, OneSeventh/1000000.0); // 0.00000014285714285714285 

Nota: Muitos são usados ​​para "%f" . Isso exibe 6 dígitos após o ponto decimal; 6 é o padrão de exibição, não a precisão do número.

A resposta curta para imprimir números de ponto flutuante sem perdas (de forma que eles possam ser lidos de volta para exatamente o mesmo número, exceto NaN e Infinity):

  • Se o seu tipo for float: use printf("%.9g", number) .
  • Se o seu tipo for duplo: use printf("%.17g", number) .

NÃO use %f , pois isso apenas especifica quantos dígitos significativos após o decimal e truncará números pequenos. Para referência, os números mágicos 9 e 17 podem ser encontrados em float.h que define FLT_DECIMAL_DIG e DBL_DECIMAL_DIG .

Se você está interessado apenas no bit (padrão hexagonal), você pode usar o formato %a . Isso garante você:

A precisão padrão é suficiente para uma representação exata do valor se existir uma representação exata na base 2 e, caso contrário, é suficientemente grande para distinguir valores do tipo double.

Eu teria que acrescentar que isso só está disponível desde C99.

Não, não existe esse especificador de largura Printf para imprimir ponto flutuante com precisão máxima . Deixe-me explicar por quê.

A precisão máxima de float e double é variável e depende do valor real do float ou double .

Recall float e double são armazenados no formato sign.exponent.mantissa . Isso significa que há muito mais bits usados ​​para o componente fracionário para números pequenos do que para números grandes.

insira a descrição da imagem aqui

Por exemplo, float pode facilmente distinguir entre 0,0 e 0,1.

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

Mas float não tem idéia da diferença entre 1e27 e 1e27 + 0.1 .

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

Isso ocorre porque toda a precisão (que é limitada pelo número de bits mantissa) é usada para a maior parte do número, à esquerda do decimal.

O modificador %.f apenas informa quantos valores decimais você deseja imprimir a partir do número flutuante, no que se refere à formatação . O fato de que a precisão disponível depende do tamanho do número depende de você como programador . printf não pode / não lida com isso para você.

Basta usar as macros de e o especificador de conversão de largura variável ( ".*" ):

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

Em um dos meus comentários a uma resposta, lamentei que há muito tempo queria imprimir todos os dígitos significativos em um valor de ponto flutuante na forma decimal, da mesma maneira que a pergunta. Bem, eu finalmente sentei e escrevi. Não é perfeito, e esse é um código de demonstração que imprime informações adicionais, mas funciona principalmente para meus testes. Por favor, deixe-me saber se você (ou seja, alguém) gostaria de uma cópia de todo o programa wrapper que o leva para testes.

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