Printspampificatore di larghezza per mantenere la precisione del valore a virgola mobile

Esiste un printf larghezza di stampa che può essere applicato a un identificatore in virgola mobile che formatta automaticamente l’output al numero necessario di cifre significative in modo tale che, quando si esegue nuovamente la scansione della stringa, venga acquisito il valore in virgola mobile originale?

Ad esempio, supponiamo che io stampi un float con una precisione di 2 cifre decimali:

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

Quando eseguo la scansione dell’output 0.94 , non ho alcuna garanzia di conformità agli standard che 0.9375 valore originale di 0.9375 virgola mobile (in questo esempio, probabilmente non lo farò).

Mi piacerebbe un modo per dire a printf di stampare automaticamente il valore in virgola mobile sul numero necessario di cifre significative per garantire che possa essere scansionato fino al valore originale passato a printf .

Potrei usare alcune delle macro in float.h per ricavare la larghezza massima da passare a printf , ma esiste già un float.h per stampare automaticamente sul numero necessario di cifre significative – o almeno sulla larghezza massima?

Raccomando la soluzione esadecimale Gustedt di @Jens: usa% a.

OP vuole “stampare con la massima precisione (o almeno il decimale più significativo)”.

Un semplice esempio potrebbe essere quello di stampare un settimo come in:

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

Ma scaviamo più a fondo …

Matematicamente, la risposta è “0.142857 142857 142857 …”, ma stiamo usando numeri in virgola mobile con precisione finita. Supponiamo che IEEE 754 binario a doppia precisione . Quindi OneSeventh = 1.0/7.0 il valore sottostante. Vengono anche mostrati i numeri double point in virgola mobile rappresentabili precedenti e successivi.

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

La stampa della rappresentazione decimale esatta di un double ha usi limitati.

C ha 2 famiglie di macro in per aiutarci.
Il primo set è il numero di cifre significative da stampare in una stringa in decimale, quindi quando si esegue la scansione della stringa, otteniamo il punto di virgola mobile originale. Ci sono mostrati con il valore minimo della specifica C e un compilatore C11 di esempio .

 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) 

Il secondo set è il numero di cifre significative che una stringa può essere scansionata in un punto mobile e quindi la FP stampata, mantenendo comunque la stessa presentazione di stringa. Ci sono mostrati con il valore minimo della specifica C e un compilatore C11 di esempio . Credo che sia disponibile pre-C99.

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

La prima serie di macro sembra soddisfare l’objective di OP di cifre significative . Ma quella macro non è sempre disponibile.

 #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 

Il “+ 3” era il punto cruciale della mia precedente risposta. È centrato su se conoscendo la stringa di conversione round-trip-FP-string (set # 2 macro disponibili C89), come si determinano le cifre per FP-string-FP (set # 1 macro disponibili post C89)? In generale, aggiungere 3 è stato il risultato.

Ora quante cifre significative da stampare sono note e gestite tramite .

Per stampare N cifre decimali significative si possono usare vari formati.

Con "%e" , il campo di precisione è il numero di cifre dopo la cifra iniziale e il punto decimale. Quindi - 1 è in ordine. Nota: Questo -1 is not in the initial int -1 is not in the initial Digs = DECIMAL_DIG; `

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

Con "%f" , il campo di precisione è il numero di cifre dopo il punto decimale. Per un numero come OneSeventh/1000000.0 , uno avrebbe bisogno di OP_DBL_Digs + 6 per vedere tutte le cifre significative .

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

Nota: molti sono utilizzati per "%f" . Questo mostra 6 cifre dopo il punto decimale; 6 è l’impostazione predefinita di visualizzazione, non la precisione del numero.

La breve risposta per stampare numeri in virgola mobile senza perdita di dati (in modo che possano essere letti nuovamente esattamente allo stesso numero, eccetto NaN e Infinity):

  • Se il tuo tipo è float: usa printf("%.9g", number) .
  • Se il tuo tipo è doppio: usa printf("%.17g", number) .

NON utilizzare %f , poiché questo specifica solo quante cifre significative dopo il decimale e troncherà i numeri piccoli. Per riferimento, i numeri magici 9 e 17 possono essere trovati in float.h che definisce FLT_DECIMAL_DIG e DBL_DECIMAL_DIG .

Se sei interessato solo al bit (risp hex pattern) puoi usare il %a format. Questo ti garantisce:

La precisione predefinita è sufficiente per una rappresentazione esatta del valore se esiste una rappresentazione esatta nella base 2 e altrimenti è sufficientemente grande per distinguere i valori di tipo double.

Dovrei aggiungere che questo è disponibile solo dal C99.

No, non esiste uno specificatore della larghezza di Printf per stampare in virgola mobile con la massima precisione . Lasciami spiegare perché.

La precisione massima di float e double è variabile e dipende dal valore effettivo del float o double .

Richiama float e double sono memorizzati nel formato sign.exponent.mantissa . Ciò significa che ci sono molti più bit usati per il componente frazionario per numeri piccoli rispetto ai numeri grandi.

inserisci la descrizione dell'immagine qui

Ad esempio, float può facilmente distinguere tra 0.0 e 0.1.

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

Ma float non ha idea della differenza tra 1e27 e 1e27 + 0.1 .

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

Questo perché tutta la precisione (che è limitata dal numero di bit di mantissa) viene utilizzata per la gran parte del numero, a sinistra del decimale.

Il modificatore %.f dice solo quanti valori decimali si desidera stampare dal numero float per quanto riguarda la formattazione . Il fatto che la precisione disponibile dipende dalla dimensione del numero dipende da te come programmatore da gestire. printf non può / non gestisce quello per te.

Basta usare le macro da e lo specificatore di conversione a larghezza variabile ( ".*" ):

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

In uno dei miei commenti a una risposta, mi sono lamentato di aver desiderato a lungo un modo per stampare tutte le cifre significative in un valore in virgola mobile in forma decimale, più o meno allo stesso modo della domanda. Bene, finalmente mi sono seduto e l’ho scritto. Non è proprio perfetto, e questo è un codice demo che stampa informazioni aggiuntive, ma funziona principalmente per i miei test. Per favore fatemi sapere se voi (cioè chiunque) vorreste una copia dell’intero programma wrapper che la guida per il test.

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