Printf-Breitenbezeichner zur Aufrechterhaltung der Genauigkeit des Gleitkommawerts

102

Gibt es einen printfBreitenbezeichner, der auf einen Gleitkomma- Bezeichner angewendet werden kann, der die Ausgabe automatisch auf die erforderliche Anzahl signifikanter Stellen formatiert, sodass beim erneuten Einscannen der Zeichenfolge der ursprüngliche Gleitkommawert erfasst wird?

Angenommen, ich drucke a floatmit einer Genauigkeit von 2Dezimalstellen:

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

Wenn ich die Ausgabe scanne 0.94, habe ich keine standardkonforme Garantie, dass ich den ursprünglichen 0.9375Gleitkommawert zurückerhalte (in diesem Beispiel werde ich dies wahrscheinlich nicht tun).

Ich möchte eine Möglichkeit angeben printf, den Gleitkommawert automatisch auf die erforderliche Anzahl von signifikanten Stellen zu drucken, um sicherzustellen, dass er auf den ursprünglichen Wert zurückgescannt werden kann, an den er übergeben wurde printf.

Ich konnte einige der Makros in float.hauf die maximale Breite ableiten zu passieren printf, aber gibt es bereits ein Spezifizierer automatisch auf die erforderliche Anzahl von Druck signifikanten Stellen - oder zumindest auf die maximale Breite?

Vilhelm Gray
quelle
4
@bobobobo Sie empfehlen also
1
@ H2CO3 Nein, ich würde nicht empfehlen, "eine Annahme aus der Luft" zu verwenden. Ich würde vorschlagen, eine zu verwenden, printf( "%f", val );die bereits portabel, effizient und standardmäßig ist.
Bobobobo
2
@bobobobo Damit ich es zu den Antworten hinzufügen kann, können Sie die Klausel im C99-Standard zitieren, die besagt, dass die printf-Anweisung den Float-Typ standardmäßig mit maximaler Genauigkeit ausgibt, wenn keine Genauigkeit angegeben wird?
Vilhelm Gray
1
@VilhelmGray Nun, da @chux eintritt, gibt es einige ziemlich komplizierte Berechnungen hinsichtlich der tatsächlichen Präzision für Ihre spezielle double. Wenn Sie doubleextrem groß werden (sehr weit von 1,0 entfernt), wird es im Dezimalbereich (Wertbereich kleiner als 1,0) tatsächlich weniger genau . Sie können hier also keine wirklich zufriedenstellende Antwort haben, da Ihre Frage eine falsche Annahme enthält (nämlich, dass alle floats / doubles gleich sind)
Bobobobo
2
@Vilhelm Gray C11dr 5.2.4.2.2 "... Anzahl der Dezimalstellen, n, so dass jede Gleitkommazahl mit p Radix b-Ziffern auf eine Gleitkommazahl mit n Dezimalstellen gerundet und ohne Änderung wieder zurückgerundet werden kann p log10 bb ist eine Potenz von 10 ⎡1 + p log10 b⎤, ansonsten FLT_DECIMAL_DIG 6 DBL_DECIMAL_DIG 10 LDBL_DECIMAL_DIG 10 ... "Die 6,10,10 sind die Mindestwerte .
chux - Wiedereinstellung Monica

Antworten:

91

Ich empfehle @Jens Gustedt hexadezimale Lösung: Verwenden Sie% a.

OP möchte "mit maximaler Genauigkeit (oder zumindest mit der höchstwertigen Dezimalstelle) drucken".

Ein einfaches Beispiel wäre, ein Siebtel wie folgt zu drucken:

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

Aber lasst uns tiefer graben ...

Mathematisch lautet die Antwort "0.142857 142857 142857 ...", aber wir verwenden Gleitkommazahlen mit endlicher Genauigkeit. Nehmen wir an, IEEE 754 Binär mit doppelter Genauigkeit . Also die OneSeventh = 1.0/7.0Ergebnisse im Wert unten. Ebenfalls gezeigt sind die vorhergehenden und folgenden darstellbaren doubleGleitkommazahlen.

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

Das Drucken der exakten Dezimaldarstellung von a doublehat nur begrenzte Verwendungsmöglichkeiten.

C hat 2 Makrofamilien <float.h>, die uns helfen.
Der erste Satz gibt die Anzahl der signifikanten Stellen an, die in einer Zeichenfolge in Dezimalzahl gedruckt werden sollen. Wenn Sie die Zeichenfolge zurückscannen, erhalten Sie den ursprünglichen Gleitkommawert. Es werden mit dem Mindestwert der C-Spezifikation und einem Beispiel- C11-Compiler angezeigt .

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)

Der zweite Satz ist die Anzahl der signifikanten Stellen, die eine Zeichenfolge in einen Gleitkommawert gescannt und dann das FP gedruckt werden kann, wobei die gleiche Zeichenfolgenpräsentation beibehalten wird. Es werden mit dem Mindestwert der C-Spezifikation und einem Beispiel- C11-Compiler angezeigt . Ich glaube verfügbar vor C99.

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

Der erste Satz von Makros scheint das OP-Ziel von signifikanten Ziffern zu erfüllen . Dieses Makro ist jedoch nicht immer verfügbar.

#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

Die "+ 3" war der Kern meiner vorherigen Antwort. Wenn man die Round-Trip-Konvertierungszeichenfolge-FP-Zeichenfolge kennt (Set # 2 Makros verfügbar C89), wie würde man die Ziffern für FP-String-FP bestimmen (Set # 1 Makros verfügbar nach C89)? Im Allgemeinen war Add 3 das Ergebnis.

Nun ist bekannt, über wie viele wichtige Ziffern gedruckt werden soll <float.h>.

Um N signifikante Dezimalstellen zu drucken, kann man verschiedene Formate verwenden.

Mit "%e"ist das Genauigkeitsfeld die Anzahl der Stellen nach der führenden Ziffer und dem Dezimalpunkt. So - 1ist es in Ordnung. Hinweis: Dies -1ist nicht in der Initialeint Digs = DECIMAL_DIG;

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

Mit "%f"ist das Genauigkeitsfeld die Anzahl der Stellen nach dem Dezimalpunkt. Für eine Zahl wie OneSeventh/1000000.0müsste OP_DBL_Digs + 6man alle signifikanten Ziffern sehen.

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

Hinweis: Viele sind es gewohnt "%f". Das zeigt 6 Stellen nach dem Dezimalpunkt an; 6 ist die Standardeinstellung für die Anzeige, nicht die Genauigkeit der Zahl.

chux - Monica wieder einsetzen
quelle
Warum ist 1.428571428571428492127e-01 und nicht 1.428571428571428492127e-0 0 1, die Anzahl der Ziffern nach 'e' sollte 3 sein?
user1024
12.12.5 Gleitkommakonvertierungen besagt, dass die Standardgenauigkeit für %f6 ist.
Jingguo Yao
1
@Jingguo Yao Stimmen Sie zu, dass in der Referenz angegeben wird: "Die Genauigkeit gibt an, wie viele Stellen dem Dezimalzeichen für '% f' folgen." Das Wort "Präzision" wird dort nicht mathematisch verwendet, sondern lediglich zur Definition der Anzahl der Nachkommastellen. 1234567890.123, hat mathematisch 13 Stellen Genauigkeit oder signifikante Stellen. 0,000000000123 hat 3 Stellen mathematischer Genauigkeit, nicht 13. Gleitkommazahlen sind logarithmisch verteilt. Diese Antwort verwendet signifikante Stellen und den mathematischen Sinn für Genauigkeit .
chux
1
@ Slipp D. Thompson "Es werden mit dem Mindestwert der C-Spezifikation und einem Beispiel- C11-Compiler angezeigt ."
chux
1
In der Tat haben Sie Recht - mein Trick gilt nur für Werte mit einer Größe zwischen 1,0 und 1,0eDBL_DIG. Dies ist wohl der einzige Bereich, mit "%f"dem überhaupt gedruckt werden kann . Die Verwendung, "%e"wie Sie gezeigt haben, ist natürlich ein rundum besserer Ansatz und effektiv eine anständige Antwort (obwohl sie möglicherweise nicht so gut ist wie die Verwendung "%a", wenn sie verfügbar ist, und natürlich "%a"verfügbar sein sollte, wenn `DBL_DECIMAL_DIG verfügbar ist). Ich habe mir immer einen Formatbezeichner gewünscht, der immer genau auf die maximale Genauigkeit rundet (anstelle der fest codierten 6 Dezimalstellen).
Greg A. Woods
65

Die kurze Antwort zum verlustfreien Drucken von Gleitkommazahlen (so dass sie mit Ausnahme von NaN und Infinity auf genau dieselbe Zahl zurückgelesen werden können):

  • Wenn Ihr Typ float ist: verwenden Sie printf("%.9g", number).
  • Wenn Ihr Typ doppelt ist: verwenden Sie printf("%.17g", number).

NICHT verwenden %f, da dies nur angibt, wie viele signifikante Stellen nach der Dezimalstelle stehen und kleine Zahlen abgeschnitten werden. Als Referenz finden Sie die magischen Zahlen 9 und 17, in float.hdenen FLT_DECIMAL_DIGund definiert sind DBL_DECIMAL_DIG.

ccxvii
quelle
6
Könnten Sie den %gSpezifizierer erklären ?
Vilhelm Gray
14
% g druckt die Zahl mit so vielen Ziffern, wie für die Genauigkeit erforderlich sind, und bevorzugt die exponentielle Syntax, wenn die Zahlen klein oder groß sind (1e-5 statt 0,00005), und überspringt alle nachgestellten Nullen (1 statt 1,00000).
ccxvii
4
@truthseeker Um einen IEEE 754-Binary64- Code darzustellen, müssen in der Tat mindestens 15 signifikante Dezimalstellen gedruckt werden . Die Eindeutigkeit benötigt jedoch 17, da sich die Genauigkeit einer Binärzahl (bei 2,4,8 usw.) und einer Dezimalzahl (bei 10.100.1000 usw.) niemals auf derselben Zahl befindet (außer 1,0). Beispiel: die 2 doubleWerte knapp über 0.1: 1.000_0000_0000_0000_2e-01, 1.000_0000_0000_0000_3e-01müssen 17 Ziffern zu unterscheiden.
chux
3
@chux - Sie irren sich über das Verhalten von% .16g; Es ist nicht ausreichend für Ihr Beispiel, 1.000_0000_0000_0000_2e-01 von 1.000_0000_0000_0000_3e-01 zu unterscheiden. % .17g wird benötigt.
Don Hatch
1
@ Don Hatch Ich stimme zu, "%.16g"ist unzureichend und "%.17g"und "%.16e"sind ausreichend. Die Details von %gwurden von mir falsch in Erinnerung behalten.
chux
23

Wenn Sie nur an dem Bit (bzw. Hex-Muster) interessiert sind, können Sie das %aFormat verwenden. Dies garantiert Ihnen:

Die Standardgenauigkeit reicht für eine genaue Darstellung des Werts aus, wenn eine genaue Darstellung in Basis 2 vorhanden ist und ansonsten ausreichend groß ist, um Werte vom Typ double zu unterscheiden.

Ich muss hinzufügen, dass dies erst seit C99 verfügbar ist.

Jens Gustedt
quelle
15

Nein, es gibt keinen solchen Druckbreitenbezeichner, um Gleitkommazahlen mit maximaler Genauigkeit zu drucken . Lassen Sie mich erklären, warum.

Die maximale Genauigkeit von floatund doubleist variabel und hängt vom tatsächlichen Wert des floatoder ab double.

Rückruf floatund doublewerden im Format sign.exponent.mantissa gespeichert. Dies bedeutet, dass für die Bruchkomponente für kleine Zahlen viel mehr Bits verwendet werden als für große Zahlen.

Geben Sie hier die Bildbeschreibung ein

Zum Beispiel floatkann leicht zwischen 0,0 und 0,1 unterschieden werden.

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

Hat floataber keine Ahnung vom Unterschied zwischen 1e27und 1e27 + 0.1.

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

Dies liegt daran, dass die gesamte Genauigkeit (die durch die Anzahl der Mantissenbits begrenzt ist) für den großen Teil der Zahl links von der Dezimalstelle verbraucht wird.

Der %.fModifikator gibt nur an, wie viele Dezimalwerte Sie bei der Formatierung aus der Gleitkommazahl drucken möchten . Die Tatsache, dass die verfügbare Genauigkeit von der Größe der Zahl abhängt, liegt bei Ihnen als Programmierer . printfkann / kann das nicht für dich erledigen.

Bobobobo
quelle
2
Dies ist eine hervorragende Erklärung für die Einschränkungen beim genauen Ausdrucken von Gleitkommawerten auf bestimmte Dezimalstellen. Ich glaube jedoch, dass ich mit meiner ursprünglichen Wortwahl zu zweideutig war, deshalb habe ich meine Frage aktualisiert, um den Begriff "maximale Präzision" zu vermeiden, in der Hoffnung, dass dies die Verwirrung beseitigen könnte.
Vilhelm Gray
Dies hängt immer noch vom Wert der Nummer ab, die Sie drucken.
Bobobobo
3
Dies ist teilweise richtig, aber es beantwortet die Frage nicht und Sie sind verwirrt darüber, was OP fragt. Er fragt, ob man die Anzahl der signifikanten [Dezimal-] Stellen abfragen kann float, die Sie bereitstellen, und Sie behaupten, dass es so etwas nicht gibt (dh dass es keine gibt FLT_DIG), was falsch ist.
@ H2CO3 Vielleicht solltest du meinen Beitrag bearbeiten und abstimmen (j / k). Diese Antwort FLT_DIGbesagt, dass sie nichts bedeutet. Diese Antwort gibt an, dass die Anzahl der verfügbaren Dezimalstellen vom Wert im Float abhängt .
Bobobobo
1
Gehen Sie davon aus, dass der Formatbuchstabe "f" sein muss? Ich denke nicht, dass das erforderlich ist. Ich lese die Frage so, dass das OP nach einem printf-Formatbezeichner sucht , der einen verlustfreien Roundtrip erzeugt. Daher lautet die Antwort von @ccxvii ("% .9g" für float, "% .17g" für double) a gut. Wahrscheinlich wäre die Frage besser formuliert, wenn das Wort "Breite" entfernt würde.
Don Hatch
11

Verwenden Sie einfach die Makros von <float.h>und den Konvertierungsspezifizierer mit variabler Breite ( ".*"):

float f = 3.14159265358979323846;
printf("%.*f\n", FLT_DIG, f);
Bobobobo
quelle
2
@OliCharlesworth Meinst du so:printf("%." FLT_DIG "f\n", f);
Vilhelm Gray
3
+1, aber das funktioniert am besten für %e, nicht so gut für %f: Nur wenn bekannt ist, dass der zu druckende Wert nahe liegt 1.0.
Pascal Cuoq
3
%eGibt signifikante Ziffern für sehr kleine Zahlen aus und %fnicht. zB x = 1e-100. %.5fDrucke 0.00000(ein totaler Präzessionsverlust). %.5edruckt 1.00000e-100.
chux
1
@bobobobo Sie irren sich auch darin, dass es "genauere Gründe liefert". FLT_DIGwird auf den Wert definiert, für den es aus einem bestimmten Grund definiert ist. Wenn es 6 ist, liegt das daran, dass floatnicht mehr als 6 Stellen Genauigkeit gespeichert werden können. Wenn Sie es mit drucken %.7f, hat die letzte Ziffer keine Bedeutung. Denken Sie nach, bevor Sie abstimmen.
5
@bobobobo Nein, %.6fist nicht gleichwertig, weil FLT_DIGes nicht immer 6 ist. Und wen interessiert die Effizienz? E / A ist bereits höllisch teuer, eine mehr oder weniger genaue Ziffer führt nicht zu einem Engpass.
4

Ich DBL_DECIMAL_DIGführe ein kleines Experiment durch, um zu überprüfen, ob beim Drucken mit tatsächlich die binäre Darstellung der Zahl erhalten bleibt. Es stellte sich heraus, dass für die Compiler und C-Bibliotheken, die ich ausprobiert habe, DBL_DECIMAL_DIGtatsächlich die Anzahl der erforderlichen Stellen erforderlich ist und das Drucken mit nur einer Stelle weniger ein erhebliches Problem darstellt.

#include <float.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

union {
    short s[4];
    double d;
} u;

void
test(int digits)
{
    int i, j;
    char buff[40];
    double d2;
    int n, num_equal, bin_equal;

    srand(17);
    n = num_equal = bin_equal = 0;
    for (i = 0; i < 1000000; i++) {
        for (j = 0; j < 4; j++)
            u.s[j] = (rand() << 8) ^ rand();
        if (isnan(u.d))
            continue;
        n++;
        sprintf(buff, "%.*g", digits, u.d);
        sscanf(buff, "%lg", &d2);
        if (u.d == d2)
            num_equal++;
        if (memcmp(&u.d, &d2, sizeof(double)) == 0)
            bin_equal++;
    }
    printf("Tested %d values with %d digits: %d found numericaly equal, %d found binary equal\n", n, digits, num_equal, bin_equal);
}

int
main()
{
    test(DBL_DECIMAL_DIG);
    test(DBL_DECIMAL_DIG - 1);
    return 0;
}

Ich führe dies mit dem Microsoft C-Compiler 19.00.24215.1 und der gcc-Version 7.4.0 20170516 (Debian 6.3.0-18 + deb9u1) aus. Die Verwendung einer Dezimalstelle weniger halbiert die Anzahl der Zahlen, die genau gleich sind. (Ich habe auch überprüft, dass rand()bei Verwendung tatsächlich etwa eine Million verschiedene Zahlen erzeugt werden.) Hier sind die detaillierten Ergebnisse.

Microsoft C.

Getestete 999507-Werte mit 17 Ziffern: 999507 numerisch gleich, 999507 binär gleich gefunden
Getestete 999507-Werte mit 16 Ziffern: 545389 numerisch gleich, 545389 binär gleich

GCC

Getestete 999485-Werte mit 17 Ziffern: 999485 als numerisch gleich, 999485 als binär gleich befunden
Getestete 999485-Werte mit 16 Ziffern: 545402 numerisch gleich, 545402 binär gleich gefunden
Diomidis Spinellis
quelle
1
"Führen Sie dies mit dem C-Compiler von Microsoft aus" -> Dieser Compiler hat möglicherweise RAND_MAX == 32767. Überlegen Sie u.s[j] = (rand() << 8) ^ rand();oder ähnliches, um
sicherzustellen
In der Tat ist sein RAND_MAX 32767, also ist Ihr Vorschlag korrekt.
Diomidis Spinellis
1
Ich habe den Beitrag aktualisiert, um RAND_MAX zu behandeln, wie von @ chux-ReinstateMonica vorgeschlagen. Die Ergebnisse ähneln denen, die zuvor erhalten wurden.
Diomidis Spinellis
2

In einem meiner Kommentare zu einer Antwort beklagte ich mich, dass ich schon lange eine Möglichkeit haben wollte, alle signifikanten Ziffern in einem Gleitkommawert in Dezimalform zu drucken, ähnlich wie in der Frage. Nun, ich habe mich endlich hingesetzt und es geschrieben. Es ist nicht ganz perfekt und dies ist ein Demo-Code, der zusätzliche Informationen druckt, aber meistens für meine Tests funktioniert. Bitte lassen Sie mich wissen, wenn Sie (dh jemand) eine Kopie des gesamten Wrapper-Programms wünschen, das es zum Testen antreibt.

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]);
}
Greg A. Woods
quelle
Es ist mir egal, ob es die Frage beantwortet oder nicht - das ist wirklich beeindruckend. Es bedurfte einiger Überlegungen und sollte anerkannt und gelobt werden. Vielleicht wäre es gut, wenn Sie irgendwie (ob hier oder sonst) den vollständigen Code zum Testen einfügen würden, aber auch ohne ihn ist dies wirklich ein guter Job. Habe eine +1 dafür!
Pryftan
0

Mein Wissen gibt es einen gut diffusen Algorithmus zu ermöglichen Ausgang auf die erforderliche Anzahl von signifikanten Stellen , so dass beim Scannen der Zeichenfolge wieder in, wird der ursprüngliche Fließkommawert erworben in dtoa.cgeschrieben von Daniel Homosexuell, die verfügbar ist , hier auf Netlib (siehe auch das dazugehörige Papier ). Dieser Code wird zB in Python, MySQL, Scilab und vielen anderen verwendet.

Stéphane Mottelet
quelle