2012-11-26 14 views
6

Wenn ich folgende c Zeilen unter Windows und Linux (Ubuntu) kompiliere bekomme ich unterschiedliche Ergebnisse. Ich möchte es vermeiden. Wie kann ich es tun?Wie kann man die gleichen Float-Nummern auf verschiedenen Systemen sicherstellen?

double a = DBL_EPSILON; 
double b = sqrt(a); 
printf("eps = %.20e\tsqrt(eps) = %.20e\n", a, b); 

Linux Ausgang:

eps = 2.22044604925031308085e-16  sqrt(eps) = 1.49011611938476562500e-08 

Fenster Ausgang:

eps = 2.22044604925031310000e-016  sqrt(eps) = 1.49011611938476560000e-008 

auf Linux getestet mit gcc und Klappern auf 32-Bit- und 64-Bit-System gleiches Ergebnis. Auf Windows getestet mit Gcc-Mingw auf 32-Bit und Visual-Studio mit 32-Bit und 64-Bit, auch die gleichen Ergebnisse.

+0

Was ist die zugrunde liegende Architektur jedes Systems? – Mike

+4

Die 'printf'-Implementierungen der verwendeten c-Bibliotheken unterscheiden sich. Die Windows-Datei druckt nur 17 signifikante Ziffern oder so, und füllt mit Nullen, wenn mehr angefordert werden. Glibcs ​​gibt den korrekt gerundeten Wert aus. –

+0

aber für mich scheint es nicht, dass es nur ein Problem des Druckens ist, mein numerischer Algorithmus gibt auch etwas andere Ergebnisse zurück. Und ich habe auf diese Zeilen hingewiesen, da ich annahm, dass sie die Quelle von Unterschieden sind. # – Will

Antwort

9

In dem Beispiel, das Sie geben, scheint es, beide Programme haben die gleichen Gleitkommazahlen. Sie drucken sie einfach anders. Die einfachste Lösung für dieses spezielle Problem ist das Schreiben einer eigenen Fließkomma-Druckfunktion. Wenn Sie keine zu schöne Ausgabe erwarten, könnten Sie die Funktion here als Pseudocode zum Schreiben Ihrer eigenen in C verwenden. Sie ist nicht richtig gerundet, aber sie funktioniert genau so, wie sie vorgesehen ist (dh reproduzierbare und lesbare Ausgaben).


Ein tieferes Problem, das Ihre Frage deutet ist, dass Sie stoßen Gleitkommazahlen Berechnungen unterschiedliche Ergebnisse auf verschiedenen Plattformen zu geben. Dies ist ein Ergebnis der C-Norm (en), die Compiler nicht zwingen, den Gleitkomma-Standard IEEE 754 genau zu implementieren, insbesondere höhere Präzision für Zwischenergebnisse zu ermöglichen. Und diese relative Nachgiebigkeit des C-Standards (der C-Standards) wird zumindest teilweise durch die historischen x86-Gleitkommabefehle verursacht, die es teuer machen, die exakte IEEE-754-Semantik zu implementieren.

Unter Linux, vorausgesetzt, Sie verwenden GCC, versuchen Sie die -msse2 Kompilierungsoption. EDIT: der OP kommentiert, dass -msse2 -mfpmath=sse für ihn arbeitete. Dadurch generiert GCC moderne SSE2-Anweisungen, die die exakte Gleitkomma-Semantik nach IEEE 754 ergeben. Wenn Sie unter Windows auch GCC verwenden, verwenden Sie dort dieselbe Option. Wenn Sie Visual C verwenden: Visual C verwendet einen anderen Trick, um die Gleitkommaanweisungen zu erzwingen IEEE 754-Semantik zu entsprechen: es teilt der alten 80-Bit-Fließkomma-Hardware, nur so viele signifikante Bits wie IEEE zu verwenden 754 Doppelpräzision hat. Dies gibt eine genaue Simulation von Zahlen mit doppelter Genauigkeit, mit Ausnahme einiger Eckfälle, denen Sie nicht begegnen werden. In diesem Fall würde es (*) helfen, wenn Ihr Programm nur Zahlen mit doppelter Genauigkeit verwendet (Typ C double). Der Visual C Compiler könnte theoretisch Code generieren, der exakte Einzelarithmetik berechnet, indem jedes Zwischenergebnis von der doppelten in die einfache Genauigkeit gerundet wird, aber das wäre teuer und ich bezweifle, dass dies der Fall ist.

+1

Ich weiß nicht, ob ich Überlauf und Unterlauf "Eckfall [s] nennen würde, dass Sie nicht sein werden Begegnung "; Sie treten überraschend oft im echten Code auf. –

+0

@StephenCanon Du hast mich erwischt, ich war vielleicht nicht ganz ehrlich: Unterlauf und Überlauf können passieren. Mein Problem ist, dass ich keine Lösung anbieten kann, wenn sie geschieht, kurz bevor man sie manuell in das Programm einfügt, wo immer sie auch vorkommen mögen, 'intermediate_result = intermediate_result> = 0x1.0p + 1024? inf: intermediate_result; '. Und die Geschichte ist noch schlimmer für den Unterlauf, für den ich keinen ** portablen ** Code sehe, um den Signifikanten abzuschneiden. Wenn der Programmierer weiß, dass er x87 verwendet, dann sollten zwei Aufrufe von 'ldexp()' es tun, aber dies wird Denormale auf IEEE 754-Hardware auf Null setzen –

+0

@StephenCanon Auch die "zwei Aufrufe von ldexp()" Lösung eingeführt Doppelrundung. –