Kayan nokta değerinin hassasiyetini korumak için Printf genişlik belirticisi


103

printfBir kayan nokta tanımlayıcısına uygulanabilecek, çıktıyı gerekli sayıda anlamlı basamağa otomatik olarak biçimlendirecek bir genişlik belirleyicisi var mı , böylece dizeyi geri tararken, orijinal kayan nokta değeri elde ediliyor mu?

Örneğin, ondalık basamak floathassasiyetine a yazdırdığımı varsayalım 2:

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

Çıktıyı taradığımda, 0.94orijinal 0.9375kayan nokta değerini geri alacağıma dair standartlara uygun bir garantim yok (bu örnekte, muhtemelen almayacağım).

printfKayan nokta değerini, geçirilen orijinal değere geri taranabilmesini sağlamak için gerekli sayıda önemli basamağa otomatik olarak yazdırmanın bir yolunu söylemek istiyorum printf.

I makro bazıları kullanabilirsiniz float.hiçin maksimum genişliği elde etmek geçmesine printfama otomatik gerekli sayıda yazdırmak için belirteci zaten orada anlamlı basamağa - maksimum genişliğine ya da en azından?


4
@bobobobo Yani , taşınabilir yaklaşımı kullanmak yerine havadan bir varsayım kullanılmasını mı öneriyorsunuz?

1
@ H2CO3 Hayır, "havadan yapılmış bir varsayım" kullanmanızı önermem printf( "%f", val );, zaten taşınabilir, verimli ve varsayılan olanı kullanmanızı öneririm .
bobobobo

2
@bobobobo Yanıtlara ekleyebilmem için, C99 standardında , herhangi bir hassasiyet belirtilmezse , printf ifadesinin float türünü varsayılan olarak maksimum kesinlikte çıkaracağını belirten cümlecikten alıntı yapabilir misiniz ?
Vilhelm Grey

1
@VilhelmGray @chux işin içine girdikçe, sizin özeliniz için gerçek hassasiyetle ilgili oldukça karmaşık bir matematik var double. Son doublederece büyüdükçe (1.0'dan çok uzakta), aslında ondalık kısımda doğruluğu azalır (değer kısmı 1.0'dan küçük). Yani burada gerçekten tatmin edici bir cevaba sahip olamazsınız, çünkü sorunuzun içinde yanlış bir varsayıma sahip (yani tüm floats / double
s'lerin

2
@Vilhelm Gray C11dr 5.2.4.2.2 "... ondalık basamak sayısı, n, öyle ki p radix b basamaklı herhangi bir kayan nokta sayısı n ondalık basamaklı bir kayan noktalı sayıya yuvarlanabilir ve değişiklik olmadan tekrar geri alınabilir değerine göre, p log10 bb, 10 ⎡1 + p log10 b⎤ kuvvetidir, aksi halde FLT_DECIMAL_DIG 6 DBL_DECIMAL_DIG 10 LDBL_DECIMAL_DIG 10 ... "6,10,10 minimum değerlerdir.
chux

Yanıtlar:


92

@Jens Gustedt onaltılık çözümü öneriyorum:% a kullanın.

OP, "maksimum hassasiyetle (veya en azından en önemli ondalık basamağa kadar) yazdırmak" istiyor.

Basit bir örnek, yedide birini aşağıdaki gibi yazdırmak olabilir:

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

Ama daha derine inelim ...

Matematiksel olarak cevap "0.142857 142857 142857 ...", ancak sonlu hassas kayan noktalı sayılar kullanıyoruz. IEEE 754 çift duyarlıklı ikili varsayalım . Yani OneSeventh = 1.0/7.0sonuçlar aşağıdaki değerde. Ayrıca, önceki ve sonraki gösterilebilir doublekayan nokta sayıları da gösterilmiştir .

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

A'nın tam ondalık gösterimini yazdırmanın doublesınırlı kullanımı vardır.

C'nin <float.h>bize yardımcı olacak 2 makro ailesi vardır .
İlk küme, bir dizede ondalık olarak yazdırılacak anlamlı basamakların sayısıdır, bu nedenle dizeyi geri tararken, orijinal kayan noktayı elde ederiz. C spesifikasyonunun minimum değeri ve örnek bir C11 derleyicisi ile gösterilmiştir.

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)

İkinci küme, bir dizinin bir kayan noktaya taranabileceği ve ardından FP'nin yazdırıldığı, hala aynı dizi sunumunu koruyan önemli basamakların sayısıdır . C spesifikasyonunun minimum değeri ve örnek bir C11 derleyicisi ile gösterilmiştir. C99 öncesi mevcut olduğuna inanıyorum.

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

İlk makro kümesi, OP'nin önemli basamak hedefini karşılamaktadır . Ancak bu makro her zaman mevcut değildir.

#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" önceki cevabımın en önemli noktasıydı. Eğer gidiş-dönüş dönüşüm dizisi-FP-dizesini (set # 2 makroları mevcut C89) biliyorsanız, FP-string-FP için basamakları nasıl belirlenir (C89 sonrası mevcut küme # 1 makroları)? Genel olarak, sonuç 3'tür.

Şimdi yazdırılacak kaç önemli basamak biliniyor ve yönlendiriliyor <float.h>.

N önemli ondalık basamağı yazdırmak için çeşitli formatlar kullanılabilir.

İle "%e", kesinlik alanı, ön basamak ve ondalık noktadan sonraki basamak sayısıdır . Yani - 1sırada bulunuyor. Not: Bu -1başlangıçta değilint Digs = DECIMAL_DIG;

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

İle "%f", kesinlik alanı ondalık noktadan sonraki basamak sayısıdır . Gibi bir sayı için OneSeventh/1000000.0, OP_DBL_Digs + 6tüm önemli basamakları görmeniz gerekir .

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

Not: Birçoğu için kullanılır "%f". Bu, ondalık noktadan sonra 6 basamak görüntüler; Sayının kesinliği değil, varsayılan değer 6'dır.


neden 1.428571428571428492127e-01 ve 1.428571428571428492127e-0 0 1 değil, 'e'den sonraki hane sayısı 3 olmalıdır?
user1024

12.12.5 Kayan Nokta Dönüşümleri için varsayılan hassasiyetin %f6 olduğunu söylüyor .
Jingguo Yao

1
@Jingguo Yao Referansın "% f" için ondalık nokta karakterini takip eden basamak sayısını belirtir "ifadesine katılıyorum. Kelime "hassas" Orada olup değil matematiksel anlamda kullanılır, ancak sadece noktadan sonraki basamak sayısını tanımlamak için. 1234567890.123, matematiksel olarak 13 basamaklı duyarlık veya anlamlı basamak içerir. 0.000000000123, 13 değil, 3 basamaklı matematiksel hassasiyete sahiptir. Kayan nokta sayıları logaritmik olarak dağıtılmıştır. Bu yanıt, anlamlı basamaklar ve matematiksel kesinlik anlamını kullanır .
chux

1
@Slipp D. Thompson "C spec's minimum değeri ve örnek bir C11 derleyicisi ile gösterilmiştir."
chux

1
Aslında haklısınız - benim hünerim yalnızca 1.0 ve 1.0 eDBL_DIG arasındaki değerler için geçerlidir, bu da muhtemelen "%f"ilk etapta yazdırmak için gerçekten uygun olan tek aralıktır . Gösterdiğiniz "%e"gibi kullanmak elbette daha iyi bir yaklaşımdır ve etkili bir cevaptır (yine de "%a"eğer mevcutsa kullanmak kadar iyi değildir ve tabii ki "%a"DBL_DECIMAL_DIG ise mevcut olmalıdır). Her zaman tam olarak maksimum hassasiyete yuvarlayacak bir biçim belirleyicisi olmasını dilemiştim (sabit kodlanmış 6 ondalık basamak yerine).
Greg A. Woods

66

Kayan nokta sayılarını kayıpsız yazdırmanın kısa cevabı (NaN ve Infinity dışında tam olarak aynı sayıya tekrar okunabilecek şekilde):

  • Türünüz float ise: kullanın printf("%.9g", number).
  • Türünüz double ise: kullanın printf("%.17g", number).

KULLANMAYIN %f, çünkü bu yalnızca ondalıktan sonra kaç tane anlamlı basamak olduğunu belirtir ve küçük sayıları keser. Referans için, sihirli sayılar 9 ve 17 bulunabilir float.hayırt eden FLT_DECIMAL_DIGve DBL_DECIMAL_DIG.


6
Tanımlayıcıyı açıklayabilir %gmisin?
Vilhelm Grey

14
% g, sayıyı hassasiyet için gerektiği kadar basamakla yazdırır, sayılar küçük veya çok büyük olduğunda üstel sözdizimini tercih eder (.00005 yerine 1e-5) ve sondaki sıfırları atlayarak (1.00000 yerine 1).
ccxvii

4
@truthseeker Bir IEEE 754 binary64 kodunu temsil etmek için gerçekten de en az 15 ondalık basamak basmak gerekir . Ancak bir ikili sayıdaki (2,4,8'de vb.) Ve ondalık bir sayıdaki (10,100,1000'de vb.) Kesinlik değiştiği için kesinlik 17'ye ihtiyaç duyar (1.0 hariç). Örnek: 2 doubleyukarıdaki değerler 0.1: 1.000_0000_0000_0000_2e-01, 1.000_0000_0000_0000_3e-01gerek 17 basamaklı ayırt etmek.
chux - Monica'yı eski durumuna getir

3
@chux -% .16g'nin davranışı konusunda yanılıyorsunuz; öyle değil 1.000_0000_0000_0000_3e-01 den 1.000_0000_0000_0000_2e-01 ayırt sizin örneğin yeterli. % .17g gereklidir.
Don Hatch

1
@Don Hatch Katılıyorum "%.16g"yetersizdir ve "%.17g"ve "%.16e"yeterlidir. Detayları %gbenim tarafımdan yanlış hatırlandı.
chux - Monica'yı eski durumuna getir

23

Eğer sadece bit ile ilgileniyorsanız (veya onaltılık model) %aformatı kullanabilirsiniz . Bu size şunları garanti eder:

Varsayılan kesinlik, 2 tabanında tam bir gösterim varsa ve aksi takdirde double türünün değerlerini ayırt etmek için yeterince büyükse, değerin tam bir gösterimi için yeterlidir.

Bunun yalnızca C99'dan beri mevcut olduğunu eklemeliyim.


16

Hayır, kayan noktayı maksimum hassasiyetle yazdırmak için böyle bir printf genişlik belirticisi yoktur . Nedenini açıklayayım.

Maksimum hassas floatve doublebir değişken ve bağımlı gerçek değer arasında floatya da double.

Hatırlama floatve doublesaklanır sign.exponent.mantissa formatında. Bu , küçük sayılar için kesirli bileşen için büyük sayılardan çok daha fazla bit kullanıldığı anlamına gelir .

görüntü açıklamasını buraya girin

Örneğin, float0,0 ile 0,1 arasında kolaylıkla ayrım yapabilir.

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

Ancak ve floatarasındaki fark hakkında hiçbir fikri yok .1e271e27 + 0.1

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

Bunun nedeni, tüm kesinliğin (mantis bitlerinin sayısıyla sınırlandırılan) sayının büyük kısmı için ondalık basamağın solunda kullanılmasıdır.

%.fDeğiştirici sadece kadarıyla şamandıra numarasından yazdırmak istediğiniz kaç ondalık değerleri diyor biçimlendirme gider. Aslında mevcut doğruluk sayısının büyüklüğüne bağlıdır kalmıştır programcı olarak size işlemek için. printfbunu sizin için halledemez / halledemez.


2
Bu, kayan nokta değerlerini belirli ondalık basamaklara doğru şekilde yazdırmanın sınırlamalarının mükemmel bir açıklamasıdır. Bununla birlikte, orijinal kelime seçimim konusunda çok belirsiz olduğuma inanıyorum, bu nedenle, karışıklığı gidermek için "maksimum kesinlik" terimini kullanmaktan kaçınmak için sorumu güncelledim.
Vilhelm Grey

Yine de yazdırdığınız numaranın değerine bağlıdır.
bobobobo

3
bu kısmen doğrudur, ancak soruyu yanıtlamaz ve OP'nin ne sorduğu konusunda kafanız karışır. Birinin a'nın sağladığı anlamlı [ondalık] basamakların sayısını sorgulayıp sorgulayamayacağını soruyor floatve siz böyle bir şeyin olmadığını (yani, olmadığını FLT_DIG) iddia ediyorsunuz , bu da yanlış.

@ H2CO3 Belki postamı düzenlemeli ve olumsuz oy vermelisin (j / k). Bu cevap FLT_DIG, hiçbir şey ifade etmediğini iddia ediyor . Bu cevap, mevcut ondalık basamakların sayısının kayan nokta içindeki değere bağlı olduğunu ileri sürer .
bobobobo

1
Biçim harfinin "f" olması gerektiğini mi varsayıyorsunuz? Bunun gerekli olduğunu sanmıyorum. Sorunun Benim okuma OP arıyor olmasıdır bazı 'ın, olmayan bir kayıplı gidiş-dönüş üreten Printf biçim belirteci cevap çok @ccxvii ( '% .9g' float için, '% .17g' çift için) bir İyi bir. Muhtemelen soru "genişlik" kelimesini ondan kaldırarak daha iyi ifade edilebilir.
Don Hatch

11

Makrolarını <float.h>ve değişken genişlikli dönüştürme belirticisini ( ".*") kullanın:

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

2
@OliCharlesworth Öyle mi demek istiyorsun:printf("%." FLT_DIG "f\n", f);
Vilhelm Grey

3
+1, ancak bu en %eçok %fişe yarar, şunlar için pek iyi değildir : yalnızca yazdırılacak değerin yakın olduğunu biliyorsa 1.0.
Pascal Cuoq

3
%eçok küçük sayılar için önemli basamaklar basar ve baskı %fyapmaz. ör x = 1e-100. %.5fbaskılar 0.00000(tam bir devinim kaybı). %.5ebaskılar 1.00000e-100.
chux - Monica'yı eski durumuna getir

1
@bobobobo Ayrıca, "daha doğru nedenler üretmesi" konusunda yanılıyorsunuz. bir nedenleFLT_DIG tanımlandığı değere tanımlanır . 6 ise, bunun nedeni float6 basamaktan fazla hassasiyet tutamamasıdır. Kullanarak yazdırırsanız %.7f, son rakamın bir anlamı olmayacaktır. Eksi oy kullanmadan önce düşünün.

5
@bobobobo Hayır, %.6feşdeğer değil, çünkü FLT_DIGher zaman değil 6. Verimlilik kimin umurunda? G / Ç zaten cehennem kadar pahalıdır, bir basamak daha fazla veya daha az hassasiyet bir darboğaz yaratmaz.

5

İle yazdırmanın DBL_DECIMAL_DIGgerçekten sayının ikili gösterimini tam olarak koruduğunu doğrulamak için küçük bir deney çalıştırıyorum . Denediğim derleyiciler ve C kitaplıkları DBL_DECIMAL_DIGiçin gerçekten de gereken basamak sayısı olduğu ve hatta bir basamak daha az yazdırmanın önemli bir sorun yarattığı ortaya çıktı.

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

Bunu Microsoft'un C derleyicisi 19.00.24215.1 ve gcc sürüm 7.4.0 20170516 (Debian 6.3.0-18 + deb9u1) ile çalıştırıyorum. Bir eksi ondalık basamak kullanmak, tam olarak eşit olan sayıların sayısını yarıya indirir. (Ayrıca rand()kullanıldığı haliyle yaklaşık bir milyon farklı sayı ürettiğini de doğruladım .) İşte ayrıntılı sonuçlar.

Microsoft C

17 basamaklı 999507 değeri test edildi: 999507 sayısal olarak eşit bulundu, 999507 ikili eşit bulundu
16 basamaklı 999507 değeri test edildi: 545389 sayısal olarak eşit bulundu, 545389 ikili eşit bulundu

GCC

999485 değeri 17 basamakla test edildi: 999485 sayısal olarak eşit bulundu, 999485 ikili eşit bulundu
16 basamaklı 999485 değeri test edildi: 545402 sayısal olarak eşit bulundu, 545402 ikili eşit bulundu

1
"bunu Microsoft'un C derleyicisi ile çalıştırın" -> Bu derleyici sahip olabilir RAND_MAX == 32767. u.s[j] = (rand() << 8) ^ rand();Tüm bitlerin 0 veya 1 olma şansına sahip olmasını sağlamayı düşünün veya benzeri bir şey yapın.
chux - Monica'yı

Aslında, RAND_MAX değeri 32767, bu yüzden teklifiniz doğru.
Diomidis Spinellis

1
Gönderiyi @ chux-ReinstateMonica tarafından önerildiği gibi RAND_MAX'ı işleyecek şekilde güncelledim. Sonuçlar daha önce elde edilenlere benzer.
Diomidis Spinellis

3

Bir cevaba yaptığım yorumlardan birinde, uzun zamandır kayan nokta değerindeki tüm önemli basamakları, sorunun sorduğu şekilde ondalık biçimde yazdırmak için bir yol istediğimden yakınıyordum. Sonunda oturdum ve yazdım. Tam olarak mükemmel değil ve bu ek bilgiler yazdıran bir demo kodu, ancak çoğunlukla testlerim için çalışıyor. Lütfen siz (yani herhangi biri) test için onu çalıştıran tüm sarmalayıcı programının bir kopyasını isteyip istemediğinizi bana bildirin.

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

Soruyu cevaplayıp cevaplamaması umurumda değil - bunu yapmak gerçekten etkileyici. Biraz düşündü ve kabul edilmeli ve takdir edilmelidir. Belki bir şekilde (burada veya başka bir şekilde) test için tam kodu dahil etseniz iyi olur, ancak onsuz bile bu gerçekten iyi bir iş. Bunun için +1 alın!
Pryftan

0

Bildiğim kadarıyla, iyi yayılmış izin algoritması vardır anlamlı basamak gerekli sayıya çıktı, öyle ki, orijinal kayan nokta değeri elde edilir dize geri tararken de dtoa.ckullanılabilir Daniel Gay, yazdığı burada (bkz NETLIB üzerinde ayrıca ilgili kağıt ). Bu kod, örneğin Python, MySQL, Scilab ve diğerlerinde kullanılır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.