Gleitkommazink zwischen Compilern (Visual Studio 2010 und GCC)

7

Ich versuche ein plattformübergreifendes Problem zu lösen, und ich bin mir nicht ganz sicher, wie ich das anstellen soll. Hier ist ein Demonstrationsprogramm:

%Vor%

Die Ausgabe unter Windows, wenn in VS2010 kompiliert ist:

0.885638 3f62b92a

Die Ausgabe, wenn sie mit GCC 4.8.1 (ideone.com-Beispiel) kompiliert wurde, lautet:

0.885638 3f62b92b

Diese kleinen Fehlanpassungen enden im Verlauf eines Programms, das auf mehreren Plattformen identisch laufen muss, zu einem ernsten Problem. Es geht mir nicht so sehr um "Genauigkeit", da die Ergebnisse zueinander passen . Ich habe versucht, den /fp -Modus in VS zu strict von precise zu wechseln, aber das scheint es nicht zu beheben.

Welche anderen Möglichkeiten sollten ich mir ansehen, damit diese Berechnung auf beiden Plattformen zu dem gleichen Ergebnis führt?

UPDATE : Wenn ich den Code so ändere, passt es interessanterweise über die Plattformen:

%Vor%

Ich bin mir aber nicht sicher, ob es realistisch ist, durch den Code zu gehen und alle Gleitkommaoperationen so zu ändern!

    
aardvarkk 28.08.2013, 17:15
quelle

8 Antworten

5

Zusammenfassung: Dies wird in der Regel nicht von Compilern unterstützt, Sie werden es schwer haben, es in einer höheren Sprache zu tun, und Sie müssen eine Math-Bibliothek für alle Ihre Zielplattformen verwenden.

Die C- und C ++ - Sprachstandards ermöglichen Implementierungen eine beträchtliche Menge (zu viel) an Flexibilität in Fließkommaoperationen. Viele C- und C ++ - Floating-Operationen müssen den IEEE 754-2008-Standard nicht so einhalten, wie es für viele Programmierer intuitiv ist.

Selbst viele C- und C ++ - Implementierungen bieten keine gute Unterstützung für die Einhaltung des Standards IEEE 754-2008.

Mathematische Bibliotheksimplementierungen sind ein besonderes Problem. Es gibt keine normale Bibliothek (kommerziell verfügbare oder weit verbreitete Open Source mit einer bekanntermaßen beschränkten Laufzeit), die korrekt gerundete Ergebnisse für alle mathematischen Standardfunktionen liefert. (Das Rechnen einiger Funktionen ist ein sehr schwieriges Problem.)

sqrt ist jedoch relativ einfach und sollte korrekt gerundete Ergebnisse in einer Bibliothek von angemessener Qualität liefern. (Ich kann nicht für die Microsoft-Implementierung bürgen.) Es ist wahrscheinlicher, dass das spezielle Problem im Code, den Sie zeigen, die Wahl des Compilers ist, unterschiedliche Gleitkommazahlen zu verwenden, während Ausdrücke ausgewertet werden.

Es kann verschiedene Schalter geben, die Sie mit verschiedenen Compilern verwenden können, um sie zu bitten, bestimmten Regeln bezüglich des Gleitkommaverhaltens zu entsprechen. Diese können ausreichen, um elementare Operationen wie erwartet auszuführen. Wenn nicht, ist die Assemblersprache eine Möglichkeit, auf wohldefinierte Fließkommaoperationen zuzugreifen. Das Verhalten von Bibliotheksroutinen unterscheidet sich jedoch zwischen Plattformen, sofern Sie keine gemeinsame Bibliothek bereitstellen. Dies umfasst sowohl mathematische Bibliotheksroutinen (z. B. pow ) als auch Konvertierungen, die in Routinen wie fprintf , fscanf , strtof gefunden werden. Sie müssen daher eine gut durchdachte Implementierung jeder Routine finden, auf die Sie vertrauen, die auf allen Zielplattformen unterstützt wird. (Es muss in dem Sinne gut entworfen sein, dass es auf allen Plattformen das gleiche Verhalten bietet. Mathematisch könnte es etwas ungenau sein, solange es in Grenzen für Ihre Anwendung tolerierbar ist.)

    
Eric Postpischil 28.08.2013 17:34
quelle
4

Der Visual Studio-Compiler tendiert dazu, Anweisungen zu generieren, die die alte x87-FPU (*) verwenden, generiert jedoch am Anfang der ausführbaren Datei Code, um die FPU auf die Genauigkeit des double -Formats zu setzen.

GCC kann auch Anweisungen generieren, die die alte x87-FPU verwenden, aber bei der Generierung von x86-64-Code wird standardmäßig SSE2 verwendet. Unter Mac OS X wird standardmäßig SSE2 sogar in 32-Bit verwendet, da alle Intel-Macs über SSE2 verfügen. Wenn GCC für den Befehl 387 generiert wird, legt nicht die Genauigkeit der FPU auf das Format double fest, sodass Berechnungen im 80-Bit-Format mit doppelter Erweiterung durchgeführt und dann auf gerundet werden double bei Zuweisung.

Als Konsequenz:

  1. Wenn Sie nur double Berechnungen verwenden, sollte Visual Studio ein Programm generieren, das genau mit der Genauigkeit des Typs berechnet, weil es immer double (**) ist. Und wenn Sie auf der GCC-Seite -msse2 -mfpmath=sse verwenden, können Sie erwarten, dass GCC auch Code generiert, der mit der Genauigkeit von double s berechnet, diesmal mit SSE2-Anweisungen. Die Berechnungen sollten übereinstimmen.

  2. Oder wenn Sie sowohl GCC als auch Visual Studio SSE2-Anweisungen ausgeben lassen, sollten die Berechnungen übereinstimmen. Ich bin mit Visual Studio nicht vertraut, aber der Schalter kann /arch:SSE2 sein.

Dies löst nicht das Problem mit Mathematikbibliotheken, was in der Tat ein ungelöstes Problem ist. Wenn Ihre Berechnungen trigonometrische oder andere Funktionen beinhalten, müssen Sie dieselbe Bibliothek als Teil Ihres Projekts auf beiden Seiten verwenden. Ich würde CRlibm empfehlen. Weniger genaue Bibliotheken sind auch in Ordnung, solange es die gleiche Bibliothek ist, und es respektiert die oben genannten Einschränkungen (mit nur double oder kompiliert mit SSE2 auf beiden Seiten).

(*) Möglicherweise gibt es eine Möglichkeit, sie anzuweisen, SSE2-Anweisungen zu generieren. Wenn Sie es finden, verwenden Sie es: es löst Ihr spezielles Problem.

(**) modulo Ausnahmen für Unendlichkeiten und Unternormalen.

    
Pascal Cuoq 28.08.2013 18:30
quelle
3

C ermöglicht die Zwischenberechnung in der Gleitkomma-Genauigkeit oder in einer höheren Gleitkommazahl.

Das Windows-Ergebnis stimmt mit dem GCC überein, wenn bei allen Berechnungen nur float verwendet wird.

Die GCC-Berechnung erhält das andere (und genauere) Ergebnis, wenn alle Berechnungen als float codiert sind, aber für Zwischenergebnisse auf doppelt oder lang verdoppelt werden können.

So selbst wenn alles IEEE 754-konform ist , hat das Steuern der erlaubten Zwischenberechnungen eine Auswirkung.

[Bearbeiten] Ich glaube nicht, dass das Obengenannte wirklich das von der OP genannte Problem löst, sondern dass es sich um allgemeine FP-Probleme handelt. Es ist das Unten, das den Unterschied am besten erklärt.

MS-Entwickler-Netzwerk sqrt

Ich vermute, der Unterschied war, dass es in der Windows -Kompilierung im C ++ - Modus war, also der sqrt(x) namens float sqrt (float) . In gcc war es im C-Modus und sqrt(x1) wurde double sqrt (double) genannt.
Wenn dies der Fall ist, versichern Sie sich, dass C-Code in Windows im C-Modus und nicht in C ++ kompiliert wird.

%Vor%     
chux 28.08.2013 18:14
quelle
2

Mit meinem 4.6.3 Compiler erzeugt er diesen Code:

%Vor%

Beachten Sie, dass in diesem Code NULL-Berechnungen durchgeführt werden, die nur verschiedene Konstanten in Registern speichern.

Ich habe keinen VisualStudio-Compiler, daher weiß ich nicht, was das produziert.

    
Mats Petersson 28.08.2013 17:35
quelle
2

IEEE 754 gibt an, dass Berechnungen mit einer höheren Genauigkeit als die im Speicher gespeicherten verarbeitet und dann bei der Zurückschreibung in den Speicher gerundet werden können. Dies verursacht viele Probleme wie das, das Sie sehen. Kurz gesagt, der Standard verspricht nicht, dass die gleiche Berechnung, die auf der gesamten Hardware ausgeführt wird, die gleiche Antwort liefert.

Wenn der Wert, der berechnet werden soll, auf ein größeres Register gelegt wird, dann wird eine Berechnung durchgeführt und dann wird der Wert aus dem Register zurück in den Speicher bewegt, das Ergebnis wird dort abgeschnitten. Es könnte dann für eine weitere Berechnung wieder in das größere Register verschoben werden.

Wenn andererseits alle Berechnungen im größeren Register ausgeführt werden, bevor der Wert zurück in den Speicher verschoben wird, erhalten Sie ein anderes Ergebnis. Möglicherweise müssen Sie den Code zerlegen, um zu sehen, was in Ihrem Fall passiert.

Bei der Funktion float point ist es wichtig zu verstehen, wie viel Genauigkeit Sie in der endgültigen Antwort benötigen und wie viel Präzision Sie durch die Präzision der Variablen, die Sie wählen, haben werden (beachten Sie die beiden Verwendungen des Wortes) Präzision als Sie sind garantiert.

Am Ende, wenn Sie Ergebnisse vergleichen (dies gilt für Fließkomma-Arbeiten), können Sie nicht nach genauen Übereinstimmungen suchen, Sie bestimmen die erforderliche Genauigkeit und Sie prüfen, ob der Unterschied zwischen zwei Werten kleiner als die Genauigkeit ist du benötigst.

Um zu den praktischen Dingen zurückzukehren, haben die Intel-Prozessoren 80-Bit-Register für Fließkomma-Berechnungen, die verwendet werden können, obwohl Sie float angegeben haben, das normalerweise 32-Bit (aber nicht immer) ist.

Wenn Sie Spaß haben möchten, versuchen Sie, verschiedene Optimierungen und Prozessoroptionen wie SSE in Ihrem Compiler einzuschalten und zu sehen, welche Ergebnisse Sie erhalten (und was aus dem Disassembler kommt).

    
Michael Conlen 20.05.2014 12:42
quelle
1

Der GCC-Compiler implementiert sogenannte Strict-Aliasing-Semantiken , die auf der Tatsache beruhen, dass es in C und C ++ im Allgemeinen illegal ist, Typ-Punning Through-Pointer-Konvertierungen durchzuführen (mit wenigen Ausnahmen). Ihr Code enthält mehrere Verstöße gegen die Anforderungen der Semantik für das strikte Aliasing. Es ist daher vollkommen logisch zu erwarten, dass die Kombination von Strict-Aliasing-Semantik und -Optimierung völlig unerwartete und scheinbar unlogische Ergebnisse in GCC (oder irgendeinem anderen Compiler) erzeugen kann.

Hinzu kommt, dass es in sqrt nichts Ungewöhnliches gibt, was in verschiedenen Implementierungen leicht unterschiedliche Ergebnisse hervorbringt.

    
AnT 28.08.2013 18:05
quelle
1

Wenn Sie die Freiheit haben, Sprachen zu ändern, sollten Sie Java mit "strictfp" verwenden. Die Java-Sprachspezifikation gibt sehr genaue Regeln für die Reihenfolge der Operationen, Rundung usw. im strictfp-Modus.

Genau übereinstimmende Ergebnisse über Implementierungen hinweg sind ein Ziel des Java-Standards für den strictfp-Modus. Es ist kein Ziel für die C ++ - Standards.

    
Patricia Shanahan 28.08.2013 18:58
quelle
0

Sie möchten, dass beide den IEEE 754-Standard verwenden.

    
Paul Evans 28.08.2013 17:17
quelle