Specyfikator szerokości Printf, aby zachować precyzję wartości zmiennoprzecinkowej

103

Czy istnieje printfspecyfikator szerokości, który można zastosować do specyfikatora zmiennoprzecinkowego, który automatycznie sformatowałby dane wyjściowe do wymaganej liczby cyfr znaczących, tak że podczas ponownego skanowania łańcucha uzyskiwana jest oryginalna wartość zmiennoprzecinkowa?

Na przykład załóżmy, że wypisuję a floatz dokładnością do 2miejsc dziesiętnych:

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

Kiedy skanuję dane wyjściowe 0.94, nie mam gwarancji zgodności ze standardami, że odzyskam oryginalną 0.9375wartość zmiennoprzecinkową (w tym przykładzie prawdopodobnie nie będę).

Chciałbym znaleźć sposób, printfaby automatycznie wydrukować wartość zmiennoprzecinkową na wymaganej liczbie cyfr znaczących, aby zapewnić, że można ją zeskanować z powrotem do pierwotnej wartości przekazanej do printf.

Mógłbym użyć niektórych makr w programie, float.haby uzyskać maksymalną szerokość, do której należy przejść printf, ale czy istnieje już specyfikator do automatycznego drukowania wymaganej liczby cyfr znaczących - lub przynajmniej maksymalnej szerokości?

Vilhelm Gray
źródło
4
@bobobobo Więc po prostu zalecasz, aby użyć założenia z powietrza zamiast podejścia przenośnego?
1
@ H2CO3 Nie, nie polecałbym używania „założenia z powietrza”, sugerowałbym użycie tego, printf( "%f", val );które jest już przenośne, wydajne i domyślne.
bobobobo
2
@bobobobo Aby móc dodać to do odpowiedzi, czy byłbyś w stanie zacytować klauzulę w standardzie C99, która stwierdza, że ​​instrukcja printf domyślnie wyprowadzi typ zmiennoprzecinkowy z maksymalną precyzją , jeśli nie określono precyzji?
Vilhelm Gray
1
@VilhelmGray Cóż, gdy wkracza @chux, istnieje dość skomplikowana matematyka dotycząca rzeczywistej precyzji dla twojego konkretnego double. Ponieważ twój doublestaje się bardzo duży (bardzo daleki od 1,0), w rzeczywistości staje się mniej dokładny w części dziesiętnej (część wartości mniejsza niż 1,0). Więc nie możesz mieć tutaj satysfakcjonującej odpowiedzi, ponieważ twoje pytanie zawiera fałszywe założenie (a mianowicie, że wszystkie floats / doubley są równe)
bobobobo
2
@Vilhelm Gray C11dr 5.2.4.2.2 "... liczba cyfr dziesiętnych, n taka, że ​​każda liczba zmiennoprzecinkowa z p radix b cyfr może być zaokrąglona do liczby zmiennoprzecinkowej z n cyframi dziesiętnymi iz powrotem bez zmiany do wartości, p log10 bb jest potęgą 10 ⎡1 + p log10 b⎤, w przeciwnym razie FLT_DECIMAL_DIG 6 DBL_DECIMAL_DIG 10 LDBL_DECIMAL_DIG 10 ... ”6,10,10 to wartości minimalne .
chux - Przywróć Monikę

Odpowiedzi:

92

Polecam rozwiązanie szesnastkowe @Jens Gustedt: użyj% a.

OP chce „drukować z maksymalną precyzją (lub przynajmniej do najbardziej znaczącego miejsca po przecinku)”.

Prostym przykładem byłoby wydrukowanie jednej siódmej, jak w:

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

Ale poszukajmy głębiej ...

Matematycznie odpowiedź brzmi „0,142857 142857 142857 ...”, ale używamy liczb zmiennoprzecinkowych o skończonej precyzji. Załóżmy, że plik binarny o podwójnej precyzji IEEE 754 . Więc OneSeventh = 1.0/7.0wyniki w wartości poniżej. Pokazane są również poprzedzające i następujące reprezentowalne doubleliczby zmiennoprzecinkowe.

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

Drukowanie dokładnej reprezentacji dziesiętnej a doublema ograniczone zastosowania.

C ma 2 rodziny makr, <float.h>które nam pomagają.
Pierwszy zestaw to liczba cyfr znaczących do wydrukowania w ciągu w postaci dziesiętnej, więc podczas skanowania łańcucha z powrotem otrzymujemy pierwotny zmiennoprzecinkowy. Są tam pokazane z minimalną wartością specyfikacji C i przykładowym kompilatorem 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)

Drugi zestaw to liczba cyfr znaczących, które łańcuch może być zeskanowany do postaci zmiennoprzecinkowej, a następnie wydrukowany FP, nadal zachowując tę ​​samą prezentację ciągu. Są tam pokazane z minimalną wartością specyfikacji C i przykładowym kompilatorem C11. Uważam, że dostępne przed C99.

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

Wydaje się, że pierwszy zestaw makr spełnia cel OP dotyczący znaczących cyfr. Ale to makro nie zawsze jest dostępne.

#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” było sednem mojej poprzedniej odpowiedzi. Skupia się na tym, że znając ciąg konwersji w obie strony łańcuch-ciąg FP (zestaw nr 2 dostępnych makr C89), w jaki sposób można określić cyfry dla ciągu FP-FP (zestaw makr zestaw nr 1 dostępny po C89)? Ogólnie rzecz biorąc, wynikiem było dodanie 3.

Teraz, ile znaczących cyfr do wydrukowania jest znanych i używanych <float.h>.

Aby wydrukować N znaczących cyfr dziesiętnych, można użyć różnych formatów.

W "%e"przypadku pola dokładności jest liczba cyfr po cyfrze wiodącej i kropce dziesiętnej. Tak - 1jest w porządku. Uwaga: -1to nie jest na początkuint Digs = DECIMAL_DIG;

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

W "%f"przypadku polem dokładności jest liczba cyfr po przecinku. W przypadku liczby takiej jak OneSeventh/1000000.0, należałoby OP_DBL_Digs + 6zobaczyć wszystkie znaczące cyfry.

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

Uwaga: wielu jest przyzwyczajonych do "%f". To wyświetla 6 cyfr po przecinku; 6 jest domyślnym wyświetlaniem, a nie dokładnością liczby.

chux - Przywróć Monikę
źródło
dlaczego 1.428571428571428492127e-01 a nie 1.428571428571428492127e-0 0 1, liczba cyfr po „e” powinna wynosić 3?
user1024,
12.12.5 Konwersje zmiennoprzecinkowe mówią, że domyślna precyzja dla %fwynosi 6.
Jingguo Yao
1
@Jingguo Yao Zgadzam się, że odniesienie mówi: „Precyzja określa, ile cyfr następuje po znaku dziesiętnym dla '% f'”. W „precyzja” słowo tam jest nie używany w sensie matematycznie, ale wystarczy, aby określić liczbę cyfr po przecinku. 1234567890.123 ma matematycznie 13 cyfr precyzji lub znaczących cyfr. 0,000000000123 ma 3 cyfry matematycznej precyzji, a nie 13. Liczby zmiennoprzecinkowe mają rozkład logarytmiczny. W tej odpowiedzi zastosowano cyfry znaczące i matematyczne poczucie precyzji .
chux - Przywróć Monikę
1
@Slipp D. Thompson "Są tam wyświetlane z minimalną wartością specyfikacji C i przykładowym kompilatorem C11."
chux - Przywróć Monikę
1
Rzeczywiście masz rację - moja sztuczka jest ważna tylko dla wartości o wielkości między 1,0 a 1,0 eDBL_DIG, co jest prawdopodobnie jedynym zakresem naprawdę odpowiednim do drukowania "%f". Używanie "%e"tak, jak pokazałeś, jest oczywiście lepszym podejściem pod każdym względem i skutecznie przyzwoitą odpowiedzią (chociaż być może nie jest tak dobre, jak używanie, "%a"jeśli jest dostępne, i oczywiście "%a"powinno być dostępne, jeśli `DBL_DECIMAL_DIG jest). Zawsze marzyłem o specyfikatorze formatu, który zawsze zaokrąglałby dokładnie do maksymalnej precyzji (zamiast zakodowanych 6 miejsc po przecinku).
Greg A. Woods
66

Krótka odpowiedź na drukowanie liczb zmiennoprzecinkowych bezstratnie (tak, że można je odczytać z powrotem do dokładnie tej samej liczby, z wyjątkiem NaN i Infinity):

  • Jeśli twój typ to float: użyj printf("%.9g", number).
  • Jeśli twój typ jest podwójny: użyj printf("%.17g", number).

NIE używaj %f, ponieważ określa tylko, ile cyfr znaczących po przecinku i obcinają małe liczby. Dla porównania można znaleźć magiczne liczby 9 i 17, w float.hktórych definiuje się FLT_DECIMAL_DIGi DBL_DECIMAL_DIG.

ccxvii
źródło
6
Czy byłbyś w stanie wyjaśnić %gspecyfikację?
Vilhelm Grey
14
% g wypisuje liczbę z taką liczbą cyfr, jaka jest wymagana dla dokładności, preferując składnię wykładniczą, gdy liczby są małe lub duże (1e-5 zamiast 0,00005) i pomijając zera końcowe (1 zamiast 1,00000).
ccxvii
4
@truthseeker Aby przedstawić kod binary64 IEEE 754 rzeczywiście trzeba wydrukować co najmniej 15 znaczących miejsc po przecinku. Jednak jednoznaczność wymaga 17, ponieważ precyzja zmienia się w liczbie binarnej (przy 2,4,8 itd.) I liczbie dziesiętnej (przy 10,100,1000 itd.) Nigdy nie są takie same (z wyjątkiem 1,0). Przykład: 2 doublewartości tuż nad 0.1: 1.000_0000_0000_0000_2e-01, 1.000_0000_0000_0000_3e-01konieczność 17 cyfr do odróżnienia.
chux - Przywróć Monikę
3
@chux - mylisz się co do zachowania% .16g; nie jest to adekwatne do twojego przykładu rozróżniania 1.000_0000_0000_0000_2e-01 od 1.000_0000_0000_0000_3e-01. % .17g jest potrzebne.
Don Hatch
1
@Don Hatch Zgadzam "%.16g"jest niewystarczająca, a "%.17g"i "%.16e"są wystarczające. Szczegóły %gzostały przeze mnie źle zapamiętane.
chux - Przywróć Monikę
23

Jeśli interesuje Cię tylko bit (lub wzorzec szesnastkowy), możesz użyć %aformatu. To gwarantuje Ci:

Domyślna precyzja jest wystarczająca do dokładnej reprezentacji wartości, jeśli istnieje dokładna reprezentacja w podstawie 2, a poza tym jest wystarczająco duża, aby odróżnić wartości typu double.

Muszę dodać, że jest to dostępne tylko od C99.

Jens Gustedt
źródło
16

Nie, nie ma takiego specyfikatora szerokości printf, aby drukować zmiennoprzecinkowe z maksymalną precyzją . Pozwól mi wyjaśnić, dlaczego.

Precyzja maksymalnie floati doublejest zmienna i zależy od aktualnej wartości z floatlub double.

Przypomnij sobie floati doublesą przechowywane w formacie sign.exponent.mantissa . Oznacza to, że jest o wiele więcej bitów używanych dla ułamkowego składnika dla małych liczb niż dla dużych liczb.

wprowadź opis obrazu tutaj

Na przykład floatmożna łatwo rozróżnić między 0,0 a 0,1.

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

Ale floatnie ma pojęcia o różnicy między 1e27i 1e27 + 0.1.

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

Dzieje się tak, ponieważ cała precyzja (która jest ograniczona liczbą bitów mantysy) jest wykorzystywana dla dużej części liczby po lewej stronie przecinka.

%.fModyfikator tylko mówi ile dziesiętny wartości chcesz drukować z liczby typu float o ile formatowanie idzie. Fakt, że dostępna dokładność zależy od wielkości liczby, zależy od Ciebie jako programisty . printfnie może / nie poradzi sobie z tym za Ciebie.

bobobobo
źródło
2
To doskonałe wyjaśnienie ograniczeń dokładnego drukowania wartości zmiennoprzecinkowych do określonych miejsc dziesiętnych. Uważam jednak, że mój pierwotny dobór słów był zbyt niejednoznaczny, więc zaktualizowałem pytanie, aby uniknąć terminu „maksymalna precyzja” w nadziei, że może to wyjaśnić zamieszanie.
Vilhelm Gray
To nadal zależy od wartości numeru, który drukujesz.
bobobobo
3
jest to częściowo prawda, ale nie odpowiada na pytanie i nie wiesz, o co pyta OP. Pyta, czy można zapytać o liczbę znaczących [dziesiętnych] cyfr a float, a ty zapewniasz, że nie ma czegoś takiego (tj. Że nie ma FLT_DIG), co jest błędne.
@ H2CO3 Może powinieneś edytować mój post i zagłosować (j / k). Ta odpowiedź twierdzi, że FLT_DIGnic nie znaczy. Ta odpowiedź stwierdza, że ​​liczba dostępnych miejsc dziesiętnych zależy od wartości wewnątrz zmiennej .
bobobobo
1
Czy zakładasz, że litera formatu musi być „f”? Myślę, że to nie jest wymagane. Czytam pytanie, że OP szuka jakiegoś specyfikatora formatu printf, który generuje bezstratną podróż w obie strony, więc odpowiedź @ccxvii („% .9g” dla liczby zmiennoprzecinkowej, „% .17g” dla podwójnej) jest dobry. Prawdopodobnie pytanie byłoby lepiej sformułowane poprzez usunięcie z niego słowa „szerokość”.
Don Hatch
11

Po prostu użyj makr z <float.h>i specyfikatora konwersji o zmiennej szerokości ( ".*"):

float f = 3.14159265358979323846;
printf("%.*f\n", FLT_DIG, f);
bobobobo
źródło
2
@OliCharlesworth Czy masz na myśli tak:printf("%." FLT_DIG "f\n", f);
Vilhelm Gray
3
+1, ale to działa najlepiej %e, a nie tak dobrze %f: tylko wtedy, gdy wiadomo, że wartość do wydrukowania jest bliska 1.0.
Pascal Cuoq
3
%edrukuje znaczące cyfry dla bardzo małych liczb, %fale nie. np x = 1e-100. %.5fwydruki 0.00000(całkowita utrata precesji). %.5ewydruki 1.00000e-100.
chux - Przywróć Monikę
1
@bobobobo Poza tym mylisz się, twierdząc, że „dostarcza dokładniejszych powodów”. FLT_DIGjest zdefiniowany do wartości, na jaką jest zdefiniowany z jakiegoś powodu. Jeśli jest 6, to dlatego, że floatnie jest w stanie pomieścić więcej niż 6 cyfr precyzji. Jeśli wydrukujesz go za pomocą %.7f, ostatnia cyfra nie będzie miała znaczenia. Pomyśl, zanim zagłosujesz.
5
@bobobobo Nie, %.6fnie jest równoważne, bo FLT_DIGnie zawsze jest 6. A kogo obchodzi wydajność? I / O już jest drogie jak diabli, jedna cyfra więcej lub mniej precyzji nie spowoduje wąskiego gardła.
5

Przeprowadzam mały eksperyment, aby sprawdzić, czy drukowanie z DBL_DECIMAL_DIGrzeczywiście dokładnie zachowuje binarną reprezentację liczby. Okazało się, że dla kompilatorów i bibliotek C, które wypróbowałem, DBL_DECIMAL_DIGrzeczywiście jest wymagana liczba cyfr, a drukowanie nawet o jedną cyfrę mniej stwarza poważny problem.

#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;
}

Uruchamiam to za pomocą kompilatora C firmy Microsoft 19.00.24215.1 i wersji gcc 7.4.0 20170516 (Debian 6.3.0-18 + deb9u1). Użycie jednej cyfry dziesiętnej mniejszej o połowę zmniejsza liczbę porównywanych liczb dokładnie równych. (Sprawdziłem również, że rand()użyte faktycznie daje około miliona różnych liczb). Oto szczegółowe wyniki.

Microsoft C

Przetestowane wartości 999507 z 17 cyframi: 999507 znalezione numerycznie równe, 999507 znalezione binarnie równe
Przetestowane 999507 wartości z 16 cyframi: 545389 znalezione numerycznie równe, 545389 znalezione binarnie równe

GCC

Przetestowane wartości 999485 z 17 cyframi: 999485 znalezione numerycznie równe, 999485 znalezione binarnie równe
Przetestowane 999485 wartości z 16 cyframi: 545402 znalezione numerycznie równe, 545402 znalezione binarnie równe
Diomidis Spinellis
źródło
1
„uruchom to za pomocą kompilatora C firmy Microsoft” -> Ten kompilator może mieć RAND_MAX == 32767. Zastanów się, u.s[j] = (rand() << 8) ^ rand();czy coś podobnego, aby upewnić się, że wszystkie bity mają szansę na 0 lub 1.
chux - Przywróć Monikę
Rzeczywiście, jego RAND_MAX to 32767, więc twoja propozycja jest poprawna.
Diomidis Spinellis
1
Zaktualizowałem post, aby obsłużyć RAND_MAX zgodnie z sugestią @ chux-ReinstateMonica. Wyniki są podobne do tych uzyskanych wcześniej.
Diomidis Spinellis
3

W jednym z moich komentarzy do odpowiedzi żałowałem, że od dawna chciałem w jakiś sposób wydrukować wszystkie znaczące cyfry w postaci wartości zmiennoprzecinkowej w postaci dziesiętnej, w taki sam sposób, jak zadaje pytanie. W końcu usiadłem i napisałem. Nie jest całkiem doskonały, a to jest kod demonstracyjny, który wyświetla dodatkowe informacje, ale działa głównie w moich testach. Proszę, daj mi znać, jeśli chcesz (tj. Ktoś) kopię całego programu opakowującego, który obsługuje go do testów.

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
źródło
Nie obchodzi mnie, czy odpowiada na pytanie, czy nie - robi to naprawdę imponujące. Wymagało to trochę przemyślenia i powinno zostać uznane i pochwalone. Może byłoby dobrze, gdybyś w jakiś sposób (czy to tutaj, czy też) dołączył pełny kod do testowania, ale nawet bez niego to naprawdę dobra robota. Zdobądź za to +1!
Pryftan
0

O ile mi wiadomo, nie jest dobrze rozproszony algorytm pozwalający na wyjście do niezbędnej liczby cyfr znaczących taki sposób, że podczas skanowania tył ciąg w, oryginalny zmiennoprzecinkowa jest nabywany w dtoa.cnapisany przez Daniela Gay, który jest dostępny tutaj na netlib (patrz także powiązany artykuł ). Kod ten jest używany np. W Pythonie, MySQL, Scilab i wielu innych.

Stéphane Mottelet
źródło