Czy istnieje printf
specyfikator 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 float
z dokładnością do 2
miejsc 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.9375
wartość zmiennoprzecinkową (w tym przykładzie prawdopodobnie nie będę).
Chciałbym znaleźć sposób, printf
aby 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.h
aby 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?
źródło
printf( "%f", val );
które jest już przenośne, wydajne i domyślne.double
. Ponieważ twójdouble
staje 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 wszystkiefloat
s /double
y są równe)Odpowiedzi:
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:
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.0
wyniki w wartości poniżej. Pokazane są również poprzedzające i następujące reprezentowalnedouble
liczby zmiennoprzecinkowe.Drukowanie dokładnej reprezentacji dziesiętnej a
double
ma 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.
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.
Wydaje się, że pierwszy zestaw makr spełnia cel OP dotyczący znaczących cyfr. Ale to makro nie zawsze jest dostępne.
„+ 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- 1
jest w porządku. Uwaga:-1
to nie jest na początkuint Digs = DECIMAL_DIG;
W
"%f"
przypadku polem dokładności jest liczba cyfr po przecinku. W przypadku liczby takiej jakOneSeventh/1000000.0
, należałobyOP_DBL_Digs + 6
zobaczyć wszystkie znaczące cyfry.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.źródło
%f
wynosi 6."%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).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):
printf("%.9g", number)
.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, wfloat.h
których definiuje sięFLT_DECIMAL_DIG
iDBL_DECIMAL_DIG
.źródło
%g
specyfikację?double
wartości tuż nad0.1
:1.000_0000_0000_0000_2e-01
,1.000_0000_0000_0000_3e-01
konieczność 17 cyfr do odróżnienia."%.16g"
jest niewystarczająca, a"%.17g"
i"%.16e"
są wystarczające. Szczegóły%g
zostały przeze mnie źle zapamiętane.Jeśli interesuje Cię tylko bit (lub wzorzec szesnastkowy), możesz użyć
%a
formatu. To gwarantuje Ci:Muszę dodać, że jest to dostępne tylko od C99.
źródło
Nie, nie ma takiego specyfikatora szerokości printf, aby drukować zmiennoprzecinkowe z maksymalną precyzją . Pozwól mi wyjaśnić, dlaczego.
Precyzja maksymalnie
float
idouble
jest zmienna i zależy od aktualnej wartości zfloat
lubdouble
.Przypomnij sobie
float
idouble
są 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.Na przykład
float
można łatwo rozróżnić między 0,0 a 0,1.Ale
float
nie ma pojęcia o różnicy między1e27
i1e27 + 0.1
.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.
%.f
Modyfikator 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 .printf
nie może / nie poradzi sobie z tym za Ciebie.źródło
float
, a ty zapewniasz, że nie ma czegoś takiego (tj. Że nie maFLT_DIG
), co jest błędne.FLT_DIG
nic nie znaczy. Ta odpowiedź stwierdza, że liczba dostępnych miejsc dziesiętnych zależy od wartości wewnątrz zmiennej .Po prostu użyj makr z
<float.h>
i specyfikatora konwersji o zmiennej szerokości (".*"
):źródło
printf("%." FLT_DIG "f\n", f);
%e
, a nie tak dobrze%f
: tylko wtedy, gdy wiadomo, że wartość do wydrukowania jest bliska1.0
.%e
drukuje znaczące cyfry dla bardzo małych liczb,%f
ale nie. npx = 1e-100
.%.5f
wydruki0.00000
(całkowita utrata precesji).%.5e
wydruki1.00000e-100
.FLT_DIG
jest zdefiniowany do wartości, na jaką jest zdefiniowany z jakiegoś powodu. Jeśli jest 6, to dlatego, żefloat
nie 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.%.6f
nie jest równoważne, boFLT_DIG
nie 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.Przeprowadzam mały eksperyment, aby sprawdzić, czy drukowanie z
DBL_DECIMAL_DIG
rzeczywiście dokładnie zachowuje binarną reprezentację liczby. Okazało się, że dla kompilatorów i bibliotek C, które wypróbowałem,DBL_DECIMAL_DIG
rzeczywiście jest wymagana liczba cyfr, a drukowanie nawet o jedną cyfrę mniej stwarza poważny problem.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
GCC
źródło
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.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.
źródło
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.c
napisany 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.źródło