Betrachten Sie den folgenden Code:
%Vor% Wenn die Ausgabe mit Visual C ++ 2012 ( cl fputest.cpp
) kompiliert wurde und das Programm ausgeführt wird, lautet die Ausgabe wie folgt:
Fragen:
Dieses Problem wird nicht durch die Konvertierung von long double in double verursacht. Dies kann auf Ungenauigkeiten in der sin
-Routine in der Math-Bibliothek zurückzuführen sein.
Die Anweisung fsin
wird angegeben, um ein Ergebnis innerhalb von 1 ULP (im langen Doppelformat) für Operanden innerhalb ihres Bereichs zu erzeugen (gemäß Intel 64 und IA-32 Architectures Software Developer's Manual, Oktober 2011, Band 1, 8.3. 10), im Rund-auf-den-Nächsten-Modus. Auf einem Intel Core i7, fsin
des Wertes des Fragestellers, -5.0712136427272633190495298549649305641651153564453125 oder -0x1.448ec3aaa278dp + 2, wird 0xe.fb206c69b0ba402p-4 erzeugt. Aus diesem Hexadezimalwert können wir leicht erkennen, dass die letzten 11 Bits 100 0000 0010 sind. Dies sind die Bits, die bei der Umwandlung von Long Double abgerundet werden. Wenn sie größer als 100 0000 0000 sind, wird die Zahl aufgerundet. Sie sind größer. Daher ist das Ergebnis der Umwandlung dieses langen Doppelwerts in das Doppelte 0xe.fb206c69b0ba8p-4, was 0x1.df640d8d36175p-1 und 0,93631021832247418590355891865328885614871978759765625 entspricht. Beachten Sie auch, dass, selbst wenn das Ergebnis um einen ULP niedriger wäre, die letzten 11 Bits immer noch größer als 100 0000 0000 wären und immer noch aufrunden würden. Daher sollte dieses Ergebnis bei Intel-CPUs, die der obigen Dokumentation entsprechen, nicht variieren.
Vergleichen Sie dies, indem Sie direkt einen Sinus mit doppelter Genauigkeit berechnen, indem Sie eine ideale sin
-Routine verwenden, die korrekt gerundete Ergebnisse liefert. Der Sinus des Wertes ist ungefähr 0,93631021832247413051857150785044253634581268961333520518023697738674775240815140702992025520721336793516756640679315765619707343171517531053811196321335899848286682535203710849065933755262347468763562 (berechnet mit Maple 10). Das am nächsten liegende Doppel ist 0x1.df640d8d36175p-1. Das ist derselbe Wert, den wir erhalten haben, indem wir das fsin
-Ergebnis in double umgewandelt haben.
Daher wird die Diskrepanz nicht durch die Umwandlung von langem Doppel in Doppel verursacht; Das Konvertieren des langen Double fsin
-Ergebnisses in double erzeugt genau das gleiche Ergebnis wie eine ideale double-precision sin
-Routine.
Wir haben keine Spezifikation für die Genauigkeit der sin
-Routine, die vom Visual Studio-Paket des Fragestellers verwendet wird. In kommerziellen Bibliotheken ist es üblich, Fehler von 1 ULP oder mehreren ULP zuzulassen. Beobachten Sie, wie nahe der Sinus an einem Punkt ist, an dem der Wert für die doppelte Genauigkeit gerundet wird: Er ist .498864 ULP (double-precision ULP) von einem Double entfernt, also ist .001136 ULP von dem Punkt entfernt, an dem sich die Rundung ändert. Daher wird selbst eine sehr geringe Ungenauigkeit in der sin
-Routine dazu führen, dass sie 0x1.df640d8d36174p-1 anstelle des näheren 0x1.df640d8d36175p-1 zurückgibt.
Daher vermute ich die Quelle der Diskrepanz ist eine sehr kleine Ungenauigkeit in der sin
Routine.
(Hinweis: Wie in den Kommentaren erwähnt, funktioniert dies nicht auf VC2012. Ich habe es hier für allgemeine Informationen gelassen. Ich würde nicht empfehlen, sich auf irgendetwas zu verlassen, das sowieso vom Optimierungslevel abhängt!)
Ich habe VS2012 nicht, aber auf dem VS2010-Compiler können Sie /fp:fast
in der Befehlszeile angeben und dann bekomme ich die gleichen Ergebnisse. Dies bewirkt, dass der Compiler "schnellen" Code generiert, der nicht unbedingt den erforderlichen Rundungsregeln und Regeln in C ++ entspricht, aber mit Ihrer Assemblersprachberechnung übereinstimmt.
Ich kann das nicht in VS2012 versuchen, aber ich stelle mir vor, es hat die gleiche Option.
Dies scheint nur in einem optimierten Build zu funktionieren, mit /Ox
als Option.
Siehe Warum ist cos (x)! = cos (y) obwohl x == y? ?
Als David in dem Kommentar erwähnt , die Diskrepanz kommt aus dem Verschieben der Daten in einem FP-Register zu einem Speicherplatz (Register / RAM) einer anderen Größe. Und es ist auch nicht immer Aufgabe; sogar eine andere Gleitkommaoperation kann ausreichen, um ein FP-Register zu löschen, was jeden Versuch vereitelt, einen bestimmten Wert vergeblich zu machen. Wenn Sie einen Vergleich durchführen müssen, können Sie möglicherweise einige davon mindern, indem Sie alle Ergebnisse wie folgt an einen Speicherort zwingen:
%Vor%Aber auch das könnte nicht funktionieren. Der beste Ansatz besteht darin, zu akzeptieren, dass eine Gleitkommaoperation nur "meistens genau" ist und dass aus der Perspektive des Programmierers dieser Fehler tatsächlich unvorhersagbar und zufällig ist, selbst wenn die gleiche Operation wiederholt durchgeführt wird. Anstelle von
%Vor%Sie sollten etwas für die Wirkung von
verwenden %Vor%oder
%Vor% wobei n
eine winzige Zahl ist.
Tags und Links c c++ assembly visual-c++ x87