Druckschwimmer, Erhaltung der Präzision

Lesezeit: 8 Minuten

Benutzer-Avatar
fredoverflow

Ich schreibe ein Programm, das Fließkommaliterale druckt, die in einem anderen Programm verwendet werden sollen.

Wie viele Ziffern muss ich drucken, um die Genauigkeit des ursprünglichen Floats zu erhalten?

Da hat ein Schwimmer 24 * (log(2) / log(10)) = 7.2247199 Dezimalstellen der Genauigkeit, mein anfänglicher Gedanke war, dass das Drucken von 8 Ziffern ausreichen sollte. Aber wenn ich Pech habe, die 0.2247199 links und rechts von den 7 signifikanten Stellen verteilt werden, also sollte ich wahrscheinlich 9 Dezimalstellen drucken.

Ist meine Analyse richtig? Reichen 9 Dezimalstellen für alle Fälle? Wie printf("%.9g", x);?

Gibt es eine Standardfunktion, die eine Gleitkommazahl in eine Zeichenfolge mit der für diesen Wert erforderlichen Mindestanzahl von Dezimalstellen umwandelt, wenn 7 oder 8 ausreichen, damit ich keine unnötigen Ziffern drucke?

Hinweis: Ich kann keine hexadezimalen Gleitkommaliterale verwenden, da Standard-C++ sie nicht unterstützt.

  • Verwenden Sie 1000 Stellen und schneiden Sie die abschließenden Nullen ab! 😉

    – R.Martinho Fernandes

    5. Juni 2012 um 10:02 Uhr


  • Da Sie einen binären Float nicht fehlerfrei in einen Dezimalbruch umwandeln können, würde ich vorschlagen, nur die binäre Darstellung (oder eine Mantisse + Exponent separat) auszugeben.

    – Vlad

    5. Juni 2012 um 10:02 Uhr


  • @Vlad kannst du nicht? Sind nicht alle binären Brüche als endliche Dezimalzahl darstellbar?

    – R.Martinho Fernandes

    5. Juni 2012 um 10:04 Uhr

  • @Fred: Das wird es nicht, aber zumindest ist diese Darstellung genau, sodass Sie sie im anderen Programm abholen und genau den gleichen Float-Wert daraus machen können.

    – Vlad

    5. Juni 2012 um 10:40 Uhr

  • @FredOverflow: Können Sie hier das Ziel verdeutlichen? Soll eine exakte Dezimaldarstellung des Schwimmers erhalten werden? (Wenn ja, ist R.Martinho auf dem richtigen Weg.) Oder soll es mit ausreichender Genauigkeit gedruckt werden, damit es eindeutig auf den ursprünglichen Float-Wert zurückgeführt werden kann?

    – Oliver Charlesworth

    5. Juni 2012 um 10:49 Uhr


Benutzer-Avatar
janeb

Um sicherzustellen, dass ein Binär->Dezimal->Binär-Roundtrip den ursprünglichen Binärwert wieder herstellt, IEEE754 erfordert


The original binary value will be preserved by converting to decimal and back again using:[10]

    5 decimal digits for binary16
    9 decimal digits for binary32
    17 decimal digits for binary64
    36 decimal digits for binary128

For other binary formats the required number of decimal digits is

    1 + ceiling(p*log10(2)) 

where p is the number of significant bits in the binary format, e.g. 24 bits for binary32.

In C sind die Funktionen, die Sie für diese Konvertierungen verwenden können, snprintf() und strtof/strtod/strtold().

Natürlich können in manchen Fällen auch noch mehr Ziffern sinnvoll sein (nein, das sind nicht immer „Rauschen“, je nach Implementierung der Dezimalkonvertierungsroutinen wie snprintf() ). Betrachten Sie z Drucken von dyadischen Brüchen.

  • +1 Für die standardgeprüfte Antwort (unter der Annahme einer IEEE-konformen Implementierung, aber wer verwendet sowieso keine IEEE-Gleitkommazahlen).

    – Christian Rau

    5. Juni 2012 um 11:34 Uhr

  • @ChristianRau: Jeder verwendet heutzutage mehr oder weniger IEEE Floats, ja. Der Vorbehalt ist jedoch, ob Ihre Dezimalumwandlungsfunktionen für alle Eingaben korrekt gerundet sind, was vielleicht weniger sicher ist. Aber wenn Sie eine Dezimaldarstellung benötigen, können Sie nicht viel dagegen tun (außer der Implementierung Ihrer eigenen fehlerfreien Dezimalkonvertierungen, viel Glück!).

    – janeb

    5. Juni 2012 um 11:37 Uhr

  • @ChristianRau, Janneb: Es ist nicht universell. Ich habe immer noch gelegentlich mit seismischen Daten zu tun, die im IBM-Gleitkommaformat generiert wurden!

    – Oliver Charlesworth

    5. Juni 2012 um 11:55 Uhr

  • Sie sind Rauschen in dem Sinne, dass sie keine zusätzliche Präzision bieten; das ist, 3.1415927f und 3.1415927410125732421875f bezeichnen genau dasselbe float Wert. Die Ziffern nach dem 927 werden vollständig durch die Ziffern davor bestimmt; sie liefern keine neuen Informationen. Warum also sie drucken, wenn die Absicht darin besteht, die kürzesten zu produzieren? float wörtlich möglich?

    – fredoverflow

    5. Juni 2012 um 17:17 Uhr


  • @FredOverflow: Der Punkt zum Rauschen war nicht als direkte Antwort auf Ihre Frage gedacht, sondern eher als allgemeiner Punkt, dass manchmal mehr Ziffern als für einen Binär-> Dezimal-> Binär-Roundtrip erforderlich sein können.

    – janeb

    5. Juni 2012 um 18:52 Uhr

24 * (log(2) / log(10)) = 7,2247199

Das ist ziemlich repräsentativ für das Problem. Es macht überhaupt keinen Sinn, die Anzahl signifikanter Stellen mit einer Genauigkeit von 0,0000001 Stellen auszudrücken. Sie konvertieren Zahlen in Text zum Nutzen von a Mensch, keine Maschine. Einem Menschen ist es völlig egal und er würde es vorziehen, wenn Sie schreiben würden

24 * (log(2) / log(10)) = 7

Der Versuch, 8 signifikante Ziffern anzuzeigen, erzeugt nur zufällige Rauschziffern. Bei Quoten ungleich Null ist diese 7 bereits zu viel, da sich bei Berechnungen Gleitkommafehler ansammeln. Drucken Sie vor allem Zahlen mit einer vernünftigen Maßeinheit. Die Leute interessieren sich für Millimeter, Gramm, Pfund, Zoll und so weiter. Kein Architekt wird sich um die Größe eines Fensters kümmern, die genauer als 1 mm ausgedrückt wird. Keine Fensterfabrik wird das tun versprechen ein Fenster, das so genau bemessen ist.

Zu guter Letzt können Sie die Genauigkeit der Zahlen, die Sie in Ihr Programm eingeben, nicht ignorieren. Es ist nicht möglich, die Geschwindigkeit einer unbeladenen europäischen Schwalbe auf 7 Stellen herunter zu messen. Es sind ungefähr 11 Meter pro Sekunde, bestenfalls 2-stellig. Also Berechnungen mit dieser Geschwindigkeit durchführen und ein Ergebnis drucken, das hat mehr signifikante Ziffern erzeugen unsinnige Ergebnisse, die eine Genauigkeit versprechen, die nicht vorhanden ist.

  • innerhalb eines anderen Programms verwendet werden => Es scheint, dass Ihre Annahme, dass ein Mensch die Ausgabe liest, falsch ist.

    – Matthias M.

    5. Juni 2012 um 11:02 Uhr

  • Hmm, das hat sich ja nicht registriert. Seltsame Sache. Nun, einfache Lösung, solange ein Mensch es nie sieht, dann druckt es jede Menge Ziffern.

    – Hans Passant

    5. Juni 2012 um 11:08 Uhr

  • Ich hasse es, wenn es so aussieht, als ob mein Gehirn das eine Importwort im Text herausgeschnitten hat 😡 Es sieht auch so aus, als ob Fred sich Sorgen um den Speicherplatzverbrauch (und ich denke, die Leistung) gemacht hat.

    – Matthias M.

    5. Juni 2012 um 11:17 Uhr

Wenn Sie eine C-Bibliothek haben, die C99 entspricht (und wenn Ihre Float-Typen eine Basis haben, die eine Potenz von 2 ist 🙂 the printf Zeichen formatieren %a kann Gleitkommawerte ohne Mangel an Genauigkeit in hexadezimaler Form und Dienstprogramme als drucken scanf und strod werden sie lesen können.

Wenn das Programm von einem Computer gelesen werden soll, würde ich den einfachen Trick der Verwendung anwenden char* Aliasing.

  • alias float* zu char*
  • in eine kopieren unsigned (oder welcher unsignierte Typ ausreichend groß ist) via char* Aliasing
  • Drucken Sie die unsigned Wert

Die Dekodierung kehrt den Prozess nur um (und auf den meisten Plattformen eine direkte reinterpret_cast kann verwendet werden).

Die in Java verwendete Gleitkomma-zu-Dezimal-Konvertierung erzeugt garantiert die geringste Anzahl von Dezimalstellen jenseits des Dezimalpunkts, die erforderlich ist, um die Zahl von ihren Nachbarn (mehr oder weniger) zu unterscheiden.

Sie können den Algorithmus von hier kopieren: http://www.docjar.com/html/api/sun/misc/FloatingDecimal.java.html
Achten Sie auf die FloatingDecimal(float) Konstrukteur und die toJavaFormatString() Methode.

  • Wo genau in diesen über 2800 Codezeilen beginnt der Algorithmus?

    – Gabe

    5. Juni 2012 um 10:41 Uhr

  • @Gabe Es beginnt im Konstruktor und endet in der von mir angegebenen Methode. Der Ausdruck gibt die vollständige Zeichenfolge zurück new FloatingDecimal(number).toJavaFormatString().

    – Joni

    5. Juni 2012 um 11:37 Uhr

  • @sehe Ich sage nicht, dass Sie das kopieren sollten Codenur der Algorithmus.

    – Joni

    5. Juni 2012 um 11:38 Uhr

  • Keine dieser Funktionen enthält offensichtliche Mittel, um die Anzahl der benötigten Dezimalstellen zu bestimmen.

    – Gabe

    5. Juni 2012 um 12:29 Uhr

Benutzer-Avatar
auch bekannt als nett

Wenn Sie diese Artikel lesen (siehe unten), werden Sie feststellen, dass es einige Algorithmen gibt, die die minimale Anzahl von Dezimalstellen ausgeben, sodass die Zahl unverändert neu interpretiert werden kann (dh durch scanf).

Da es mehrere solcher Zahlen geben kann, wählt der Algorithmus auch den nächsten Dezimalbruch zum ursprünglichen Binärbruch (ich habe Float-Wert genannt).

Schade, dass es in C keine solche Standardbibliothek gibt.

  • Wo genau in diesen über 2800 Codezeilen beginnt der Algorithmus?

    – Gabe

    5. Juni 2012 um 10:41 Uhr

  • @Gabe Es beginnt im Konstruktor und endet in der von mir angegebenen Methode. Der Ausdruck gibt die vollständige Zeichenfolge zurück new FloatingDecimal(number).toJavaFormatString().

    – Joni

    5. Juni 2012 um 11:37 Uhr

  • @sehe Ich sage nicht, dass Sie das kopieren sollten Codenur der Algorithmus.

    – Joni

    5. Juni 2012 um 11:38 Uhr

  • Keine dieser Funktionen enthält offensichtliche Mittel, um die Anzahl der benötigten Dezimalstellen zu bestimmen.

    – Gabe

    5. Juni 2012 um 12:29 Uhr

Benutzer-Avatar
Pavan Manjunath

Sie können verwenden sprintf. Ich bin mir nicht sicher, ob dies Ihre Frage genau beantwortet, aber wie auch immer, hier ist der Beispielcode

#include <stdio.h>
int main( void )
{
float d_n = 123.45;
char s_cp[13] = { '\0' };
char s_cnp[4] = { '\0' };
/*
* with sprintf you need to make sure there's enough space
* declared in the array
*/
sprintf( s_cp, "%.2f", d_n );
printf( "%s\n", s_cp );
/*
* snprinft allows to control how much is read into array.
* it might have portable issues if you are not using C99
*/
snprintf( s_cnp, sizeof s_cnp - 1 , "%f", d_n );
printf( "%s\n", s_cnp );
getchar();
return 0;
}
/* output :
* 123.45
* 123
*/

  • Dies ist ein vernünftiger Ansatz, aber eine Sache, die wir bei der Verwendung von sprintf festgestellt haben, ist, dass die Rundung für verschiedene Plattformen unterschiedlich sein kann.

    – Richard Corden

    5. Juni 2012 um 10:43 Uhr

1202250cookie-checkDruckschwimmer, Erhaltung der Präzision

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy