Penentu lebar printf untuk menjaga ketepatan nilai floating-point

103

Apakah ada printfpenentu lebar yang dapat diterapkan ke penentu titik mengambang yang secara otomatis akan memformat keluaran ke jumlah digit signifikan yang diperlukan sehingga saat memindai string kembali, nilai titik mengambang asli diperoleh?

Misalnya, saya mencetak floatdengan presisi 2tempat desimal:

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

Ketika saya memindai output 0.94, saya tidak memiliki jaminan yang sesuai standar bahwa saya akan mendapatkan 0.9375kembali nilai floating-point asli (dalam contoh ini, saya mungkin tidak akan).

Saya ingin cara memberitahu printfuntuk secara otomatis mencetak nilai floating-point ke jumlah digit signifikan yang diperlukan untuk memastikan bahwa itu dapat dipindai kembali ke nilai asli yang diteruskan printf.

Saya dapat menggunakan beberapa makro float.huntuk mendapatkan lebar maksimum untuk diteruskan printf, tetapi apakah sudah ada penentu untuk mencetak secara otomatis ke jumlah digit signifikan yang diperlukan - atau setidaknya ke lebar maksimum?

Vilhelm Gray
sumber
4
@bobobo Jadi Anda hanya merekomendasikan bahwa seseorang menggunakan asumsi out of air daripada mengambil pendekatan portabel?
1
@ H2CO3 Tidak, saya tidak akan merekomendasikan menggunakan "asumsi out of the air", saya sarankan untuk menggunakan printf( "%f", val );yang sudah portabel, efisien, dan default.
bobobobo
2
@bobobobo Agar saya dapat menambahkannya ke jawaban, apakah Anda dapat mengutip klausa dalam standar C99 yang menyatakan bahwa pernyataan printf akan menampilkan tipe float pada presisi maksimum secara default jika tidak ada presisi yang ditentukan?
Vilhelm Grey
1
@VilhelmGray Nah seperti @chux masuk ke, ada beberapa matematika yang cukup rumit untuk presisi sebenarnya untuk khusus Anda double. Karena Anda doublemenjadi sangat besar (sangat jauh dari 1.0), itu sebenarnya menjadi kurang akurat dalam bagian desimal (bagian nilai kurang dari 1.0). Jadi Anda tidak bisa mendapatkan jawaban yang memuaskan di sini, karena pertanyaan Anda memiliki asumsi yang salah di dalamnya (yaitu bahwa semua floats / doubles dibuat sama)
bobobobo
2
@Vilhelm Gray C11dr 5.2.4.2.2 "... jumlah digit desimal, n, sedemikian rupa sehingga bilangan floating-point dengan p radix b digit dapat dibulatkan menjadi bilangan floating-point dengan n digit desimal dan kembali lagi tanpa perubahan ke nilai, p log10 bb adalah pangkat 10 ⎡1 + p log10 b⎤ jika tidak, FLT_DECIMAL_DIG 6 DBL_DECIMAL_DIG 10 LDBL_DECIMAL_DIG 10 ... "6,10,10 adalah nilai minimum .
chux - Pulihkan Monica

Jawaban:

92

Saya merekomendasikan solusi heksadesimal @Jens Gustedt: gunakan% a.

OP menginginkan "mencetak dengan presisi maksimum (atau setidaknya hingga desimal paling signifikan)".

Contoh sederhananya adalah mencetak satu ketujuh seperti di:

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

Tapi mari kita gali lebih dalam ...

Secara matematis, jawabannya adalah "0.142857 142857 142857 ...", tetapi kami menggunakan angka floating point presisi hingga. Mari kita asumsikan biner presisi ganda IEEE 754 . Jadi OneSeventh = 1.0/7.0hasilnya di nilai dibawah ini. Juga ditampilkan doublebilangan floating point sebelum dan sesudahnya.

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

Mencetak representasi desimal yang tepat dari a doublememiliki kegunaan terbatas.

C memiliki 2 keluarga makro <float.h>untuk membantu kami.
Set pertama adalah jumlah digit signifikan untuk dicetak dalam string dalam desimal sehingga saat memindai string kembali, kami mendapatkan titik mengambang asli. Ada yang ditampilkan dengan nilai minimum spesifikasi C dan kompiler C11 sampel .

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)

Set kedua adalah jumlah digit signifikan sebuah string dapat dipindai menjadi floating point dan kemudian FP dicetak, masih mempertahankan presentasi string yang sama. Ada yang ditampilkan dengan nilai minimum spesifikasi C dan kompiler C11 sampel . Saya yakin tersedia pra-C99.

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

Set makro pertama tampaknya memenuhi tujuan OP untuk digit signifikan . Tetapi makro itu tidak selalu tersedia.

#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" adalah inti dari jawaban saya sebelumnya. Ini berpusat pada jika mengetahui konversi bolak-balik string-FP-string (set # 2 makro tersedia C89), bagaimana cara menentukan digit FP-string-FP (set # 1 makro tersedia pos C89)? Secara umum, tambahkan 3 adalah hasilnya.

Sekarang berapa banyak angka penting yang akan dicetak diketahui dan didorong <float.h>.

Untuk mencetak N digit desimal signifikan, seseorang dapat menggunakan berbagai format.

Dengan "%e", bidang presisi adalah jumlah digit setelah digit awal dan titik desimal. Begitu - 1juga dengan ketertiban. Catatan: Ini -1bukan di awalint Digs = DECIMAL_DIG;

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

Dengan "%f", bidang presisi adalah jumlah digit setelah koma desimal. Untuk angka seperti OneSeventh/1000000.0, seseorang perlu OP_DBL_Digs + 6melihat semua angka penting .

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

Catatan: Banyak yang terbiasa "%f". Itu menampilkan 6 digit setelah koma desimal; 6 adalah tampilan default, bukan ketepatan angka.

chux - Kembalikan Monica
sumber
mengapa 1.428571428571428492127e-01 dan bukan 1.428571428571428492127e-0 0 1, jumlah digit setelah 'e' harus 3?
pengguna1024
12.12.5 Konversi Titik Mengambang menyatakan presisi default untuk %fadalah 6.
Jingguo Yao
1
@Jingguo Yao Setuju bahwa referensi mengatakan "Presisi menentukan berapa banyak digit yang mengikuti karakter titik desimal untuk '% f'". Kata "presisi" tidak digunakan dalam pengertian matematis, tetapi hanya untuk menentukan jumlah digit setelah koma desimal. 1234567890.123, secara matematis memiliki presisi 13 digit atau digit signifikan. 0.000000000123 memiliki 3 digit ketelitian matematis, bukan 13. Bilangan floating point didistribusikan secara logaritmik. Jawaban ini menggunakan digit signifikan dan pengertian presisi matematis .
chux - Pulihkan Monica
1
@Slipp D. Thompson "Ada yang ditampilkan dengan nilai minimum spesifikasi C dan sampel compiler C11."
chux - Kembalikan Monica
1
Memang Anda benar - trik saya hanya berlaku untuk nilai dengan besaran antara 1.0 dan 1.0eDBL_DIG, yang bisa dibilang satu-satunya kisaran yang benar-benar cocok untuk dicetak dengan "%f"di tempat pertama. Menggunakan "%e"seperti yang Anda tunjukkan tentu saja merupakan pendekatan yang lebih baik secara keseluruhan dan secara efektif merupakan jawaban yang layak (meskipun mungkin tidak sebagus penggunaan "%a"jika tersedia, dan tentu saja "%a"harus tersedia jika `DBL_DECIMAL_DIG ada). Saya selalu berharap untuk penentu format yang akan selalu bulat persis dengan presisi maksimum (bukan 6 tempat desimal hard-code).
Greg A. Woods
66

Jawaban singkat untuk mencetak angka floating point tanpa kehilangan (sehingga bisa dibaca kembali ke angka yang persis sama, kecuali NaN dan Infinity):

  • Jika tipe Anda float: gunakan printf("%.9g", number).
  • Jika tipe Anda ganda: gunakan printf("%.17g", number).

JANGAN gunakan %f, karena itu hanya menentukan berapa banyak angka penting setelah desimal dan akan memotong angka kecil. Untuk referensi, angka ajaib 9 dan 17 dapat ditemukan di float.hmana mendefinisikan FLT_DECIMAL_DIGdan DBL_DECIMAL_DIG.

ccxvii.dll
sumber
6
Bisakah Anda menjelaskan %gpenspesifikasinya?
Vilhelm Gray
14
% g mencetak bilangan dengan digit sebanyak yang diperlukan untuk presisi, lebih memilih sintaks eksponensial saat angkanya kecil atau besar (1e-5 daripada 0,00005) dan melewatkan nol di belakangnya (1 daripada 1,00000).
ccxvii
4
@truthseeker Untuk mewakili kode biner64 IEEE 754 memang perlu mencetak setidaknya 15 tempat desimal yang signifikan. Tetapi ketidakjelasan membutuhkan 17 karena presisi berubah dalam angka biner (pada 2,4,8, dll.) Dan angka desimal (pada 10,100,1000, dll.) Tidak pernah pada angka yang sama (kecuali 1,0). Contoh: 2 doublenilai di atas 0.1: 1.000_0000_0000_0000_2e-01, 1.000_0000_0000_0000_3e-01kebutuhan 17 digit untuk membedakan.
chux
3
@chux - Anda salah paham tentang perilaku% .16g; ini tidak cukup untuk contoh Anda dalam membedakan 1.000_0000_0000_0000_2e-01 dari 1.000_0000_0000_0000_3e-01. % .17g dibutuhkan.
Don Hatch
1
@Don Hatch Saya setuju "%.16g"tidak cukup dan "%.17g"dan "%.16e"cukup. Detailnya %g, salah ingat oleh saya.
chux
23

Jika Anda hanya tertarik pada bit (pola resp hex) Anda dapat menggunakan %aformat. Ini menjamin Anda:

Presisi default cukup untuk representasi nilai yang tepat jika representasi yang tepat di basis 2 ada dan jika tidak cukup besar untuk membedakan nilai tipe ganda.

Saya harus menambahkan bahwa ini hanya tersedia sejak C99.

Jens Gustedt
sumber
16

Tidak, tidak ada penentu lebar printf untuk mencetak floating-point dengan presisi maksimum . Izinkan saya menjelaskan alasannya.

Ketepatan maksimum dari floatdan doubleadalah variabel , dan bergantung pada nilai sebenarnya dari floatatau double.

Ingat floatdan doubledisimpan dalam format sign.exponent.mantissa . Ini berarti ada lebih banyak bit yang digunakan untuk komponen pecahan untuk bilangan kecil daripada bilangan besar.

masukkan deskripsi gambar di sini

Misalnya, floatdapat dengan mudah membedakan antara 0,0 dan 0,1.

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

Tetapi floattidak tahu perbedaan antara 1e27dan 1e27 + 0.1.

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

Ini karena semua presisi (yang dibatasi oleh jumlah bit mantissa) digunakan untuk sebagian besar bilangan, di kiri desimal.

The %.fpengubah hanya mengatakan berapa banyak nilai desimal Anda ingin mencetak dari nomor float sejauh format pergi. Fakta bahwa akurasi yang tersedia tergantung pada ukuran nomor itu terserah Anda sebagai programmer untuk menangani. printftidak bisa / tidak menangani itu untuk Anda.

bobobobo
sumber
2
Ini adalah penjelasan yang sangat baik tentang batasan mencetak nilai floating point secara akurat ke tempat desimal tertentu. Namun, saya yakin saya terlalu rancu dengan pilihan kata asli saya, jadi saya telah memperbarui pertanyaan saya untuk menghindari istilah "presisi maksimum" dengan harapan hal itu dapat menjernihkan kebingungan.
Vilhelm Grey
Itu masih tergantung pada nilai nomor yang Anda cetak.
bobobobo
3
ini sebagian benar, tetapi tidak menjawab pertanyaan dan Anda bingung tentang apa yang ditanyakan OP. Dia menanyakan apakah seseorang dapat menanyakan jumlah signifikan [desimal] digit yang floatdisediakan, dan Anda menegaskan bahwa tidak ada hal seperti itu (yaitu tidak ada FLT_DIG), yang salah.
@ H2CO3 Mungkin Anda harus mengedit posting saya dan downvote (j / k). Jawaban ini menegaskan FLT_DIGtidak berarti apa-apa. Jawaban ini menegaskan jumlah tempat desimal yang tersedia bergantung pada nilai di dalam float .
bobobobo
1
Apakah Anda berasumsi bahwa format surat harus "f"? Saya tidak berpikir itu diperlukan. Saya membaca pertanyaannya adalah bahwa OP mencari beberapa penentu format printf yang menghasilkan perjalanan bolak-balik non-lossy, jadi jawaban @ccxvii ("% .9g" untuk float, "% .17g" untuk double) adalah a bagus. Mungkin pertanyaannya akan lebih baik dibuat kata-katanya dengan menghilangkan kata "lebar" dari situ.
Don Hatch
11

Cukup gunakan makro dari <float.h>dan penentu konversi lebar-variabel ( ".*"):

float f = 3.14159265358979323846;
printf("%.*f\n", FLT_DIG, f);
bobobobo
sumber
2
@OliCharlesworth Maksud Anda seperti itu:printf("%." FLT_DIG "f\n", f);
Vilhelm Grey
3
+1 tetapi ini berfungsi paling baik untuk %e, tidak terlalu baik untuk %f: hanya jika diketahui bahwa nilai yang akan dicetak mendekati 1.0.
Pascal Cuoq
3
%emencetak angka yang signifikan untuk angka yang sangat kecil dan %ftidak. mis x = 1e-100. %.5fcetakan 0.00000(kehilangan presesi total). %.5ecetakan 1.00000e-100.
chux - Pulihkan Monica
1
@bobobo Juga, Anda salah karena "menghasilkan alasan yang lebih akurat". FLT_DIGdidefinisikan ke nilai yang didefinisikan karena suatu alasan. Jika 6, itu karena floattidak mampu menahan lebih dari 6 digit presisi. Jika Anda mencetaknya dengan menggunakan %.7f, digit terakhir tidak akan ada artinya. Berpikirlah sebelum Anda memberi suara negatif.
5
@bobobo Tidak, %.6ftidak setara, karena FLT_DIGtidak selalu 6. Dan siapa yang peduli dengan efisiensi? I / O sudah mahal sekali, satu digit lebih atau kurang presisi tidak akan membuat bottleneck.
5

Saya menjalankan percobaan kecil untuk memverifikasi bahwa pencetakan dengan DBL_DECIMAL_DIGmemang benar-benar mempertahankan representasi biner nomor tersebut. Ternyata untuk kompiler dan pustaka C yang saya coba, DBL_DECIMAL_DIGjumlah digitnya memang diperlukan, dan mencetak bahkan dengan satu digit kurang menciptakan masalah yang signifikan.

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

Saya menjalankan ini dengan kompiler C Microsoft 19.00.24215.1 dan gcc versi 7.4.0 20170516 (Debian 6.3.0-18 + deb9u1). Menggunakan satu digit desimal kurang membagi dua jumlah angka yang perbandingannya sama persis. (Saya juga memverifikasi bahwa yang rand()digunakan memang menghasilkan sekitar satu juta angka yang berbeda.) Berikut adalah detail hasil.

Microsoft C

Uji 999507 nilai dengan 17 digit: 999507 ditemukan numerik sama, 999507 ditemukan biner sama
Uji 999507 nilai dengan 16 digit: 545389 ditemukan numerik sama, 545389 ditemukan biner sama

GCC

Uji 999485 nilai dengan 17 digit: 999485 ditemukan numerik sama, 999485 ditemukan biner sama
Uji 999485 nilai dengan 16 digit: 545402 ditemukan numerik sama, 545402 ditemukan biner sama
Diomidis Spinellis
sumber
1
"jalankan ini dengan kompiler C Microsoft" -> Kompiler itu mungkin punya RAND_MAX == 32767. Pertimbangkan u.s[j] = (rand() << 8) ^ rand();atau sejenisnya untuk memastikan semua bit mendapat peluang menjadi 0 atau 1.
chux - Reinstate Monica
Memang RAND_MAX-nya adalah 32767, jadi proposal Anda benar.
Diomidis Spinellis
1
Saya memperbarui posting untuk menangani RAND_MAX seperti yang disarankan oleh @ chux-ReinstateMonica. Hasilnya mirip dengan yang diperoleh sebelumnya.
Diomidis Spinellis
3

Dalam salah satu komentar saya untuk sebuah jawaban, saya menyesali bahwa saya sudah lama menginginkan cara untuk mencetak semua digit signifikan dalam nilai floating point dalam bentuk desimal, dengan cara yang sama seperti pertanyaan yang diajukan. Akhirnya saya duduk dan menulisnya. Ini tidak cukup sempurna, dan ini adalah kode demo yang mencetak informasi tambahan, tetapi sebagian besar berfungsi untuk pengujian saya. Tolong beritahu saya jika Anda (yaitu siapa saja) menginginkan salinan seluruh program pembungkus yang mendorongnya untuk pengujian.

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
sumber
Saya tidak peduli apakah itu menjawab pertanyaan atau tidak - ini sangat mengesankan untuk dilakukan. Butuh beberapa pemikiran dan harus diakui dan dipuji. Mungkin akan lebih baik jika Anda memasukkan entah bagaimana (apakah di sini atau yang lain) kode lengkap untuk pengujian tetapi bahkan tanpa itu ini benar-benar pekerjaan yang baik. Dapatkan +1 untuk itu!
Pryftan
0

Sepengetahuan saya, ada algoritma yang tersebar dengan baik yang memungkinkan untuk menghasilkan jumlah digit signifikan yang diperlukan sehingga ketika memindai string kembali, nilai floating point asli diperoleh secara dtoa.ctertulis oleh Daniel Gay, yang tersedia di sini di Netlib (lihat juga kertas terkait ). Kode ini digunakan misalnya di Python, MySQL, Scilab, dan banyak lainnya.

Stéphane Mottelet
sumber