2010-09-24 6 views
5

Betrachten Sie den folgenden Code-Snippet:Wie gehen printf und scanf mit Fließkomma-Präzisionsformaten um?

float val1 = 214.20; 
double val2 = 214.20; 

printf("float : %f, %4.6f, %4.2f \n", val1, val1, val1); 
printf("double: %f, %4.6f, %4.2f \n", val2, val2, val2); 

Welche Ausgänge:

float : 214.199997, 214.199997, 214.20 | <- the correct value I wanted 
double: 214.200000, 214.200000, 214.20 | 

Ich verstehe, dass 214.20 eine unendliche binäre Darstellung hat. Die ersten beiden Elemente der ersten Linie eine Annäherung des beabsichtigten Wert haben, aber die die letzte scheint überhaupt keine Annäherung zu haben, und dies führte mich auf die folgende Frage:

Wie das tun scanf, fscanf, printf, fprintf (usw.) Funktionen behandeln die Präzisionsformate?

Ohne Genauigkeit zur Verfügung gestellt, printf ausgedruckt einen angenäherten Wert, aber mit %4.2f gab es das richtige Ergebnis. Können Sie mir den Algorithmus erklären, mit dem diese Funktionen die Genauigkeit handhaben?

Antwort

10

Die Sache ist, 214,20 kann nicht ausgedrückt werden genau mit binärer Darstellung. Wenige Dezimalzahlen können. Also wird eine Approximation gespeichert. Wenn Sie jetzt printf verwenden, wird die binäre Darstellung in eine Dezimaldarstellung umgewandelt, aber sie kann wiederum nicht genau ausgedrückt werden und wird nur approximiert.

Wie Sie bemerkt haben, können Sie printf eine Genauigkeit geben, um zu erfahren, wie die Dezimalnäherung abgerundet wird. Und wenn Sie keine Genauigkeit angeben, wird eine Genauigkeit von 6 angenommen (Details finden Sie auf der Manpage).

Wenn Sie %.40f für den Schwimmer und %.40lf für die Doppel in Ihrem Beispiel oben verwenden, werden Sie diese Ergebnisse erhalten:

214.1999969482421875000000000000000000000000 
214.1999999999999886313162278383970260620117 

Sie sind anders, weil mit Doppel gibt es mehr Bits besser ungefähre 214.20. Aber wie Sie sehen können, sind sie immer noch sehr merkwürdig, wenn sie in Dezimalzahlen dargestellt werden.

Ich empfehle die Wikipedia article on floating point numbers für mehr Erkenntnisse darüber, wie Gleitkommazahlen funktionieren. Ein ausgezeichneter Wert ist auch What Every Computer Scientist Should Know About Floating-Point Arithmetic

+0

Danke für die Beantwortung, obwohl meine Frage immer noch am selben Ort ist. In erster Linie selbst könnte der Float NICHT den exakten Wert von 214,20 enthalten, dann wie könnte er ihn NUR mit% 4.2f Genauigkeit und NICHT mit% 4.6f wiedergewinnen. Was hat es getan, um den Wert wiederzuerlangen? Ich verstehe, dass eine Annäherung gemacht wird, aber WAS WAR DAS BIT-MUSTER, DAS INNERHALB DES GESPEICHERTEN 214.199997 VERURSACHT HATTE, 214.20 ZU WERDEN, UND WIE WURDEN WIR ANGEKOMMEN? –

+0

Nicht sicher, dass ich dich bekomme, aber das ist nur die [Rundung] (http://en.wikipedia.org/wiki/Rounding). Es ist pure Chance, dass '% 4.2f' Ihnen genau den Wert gibt, den Sie wollten. Es ist immer noch "falsch", da die Binärdarstellung * nicht * 214.20, sondern 214.1999969482421875 entspricht. Wenn Sie jetzt "214.1999969482421875" auf 6 Ziffern runden, erhalten Sie "214.199997", weil die 7. Ziffer eine 9 ist und aus der 6 eine 7 wird. Aber wenn Sie nur auf 2 Ziffern runden, erhalten Sie "214.20" wegen der 3. Ziffer ist eine Neun, also müssen Sie die 9 auf der 2. Stelle erhöhen. Das "überläuft" bis 10 und verwandelt die ".19" in ".20". – DarkDust

+0

Oh !! Ich glaube, Sie versuchen zu sagen, dass diese Operation in dezimaler Darstellung erfolgt, und so wurde 214.199997 als ein Rundungseffekt doppelter Genauigkeit gebildet. Weil es in binärer Darstellung gewesen war, haben wir bereits bestätigt, dass 214.20 NICHT binär darstellbar ist, mit allen verwendeten Rundungsfähigkeiten. –

1

scanf runden den Eingabewert auf den nächsten genau darstellbaren Gleitkommawert. Wie die Antwort von DarkDust zeigt, liegt der nächstgelegene exakt darstellbare Wert in einfacher Genauigkeit unter dem genauen Wert, und in doppelter Genauigkeit liegt der nächstgelegene genau darstellbare Wert über dem genauen Wert.

+0

Wenn es möglich war, warum haben wir Approximation an erster Stelle gespeichert? :-). Entschuldigung, wenn ich Schmerzen verursache, aber was ich wissen wollte, was scanf tut, um einen Wert darzustellen, der an sechs Stellen nach dem Komma gegeben wurde, um in zwei Punkte nach dem Komma umgewandelt zu werden. Vorausgesetzt, dass .20 ist nicht darstellbar in binären Fließkomma –

+0

Es sieht aus wie diese Follow-up-Frage im Kommentardialog mit DarkDust beantwortet wird. –

4

Da Sie über scanf gefragt, eine Sache, die Sie beachten sollten, ist, dass POSIX printf und eine nachfolgende scanf (oder strtod) erfordert den ursprünglichen Wert zu rekonstruieren genau solange ausreichend signifikanten Stellen (mindestens DECIMAL_DIG, glaube ich) wurden gedruckt. Plain C macht natürlich keine solche Anforderung; Der C-Standard erlaubt Floating-Point-Operationen so gut wie möglich, das Ergebnis, das das Implementor mag, zu geben, solange sie es dokumentieren. Wenn Sie jedoch beabsichtigen, Gleitkommazahlen in Textdateien zu speichern, um sie später zurückzulesen, sollten Sie besser den C99 %a-Spezifizierer verwenden, um sie in Hex auszudrucken. Auf diese Weise sind sie genau und es besteht keine Verwirrung darüber, ob der Serialisierungs-/Deserialisierungsprozess an Genauigkeit verliert.

Denken Sie daran, als ich sagte "den ursprünglichen Wert rekonstruieren", ich meine den tatsächlichen Wert, der in der Variable/Ausdruck an printf übergeben wurde, nicht die ursprüngliche Dezimalstelle, die Sie in der Quelldatei geschrieben, die auf die gerundet wurde beste binäre Darstellung durch den Compiler.