2010-03-23 7 views
8

Ich habe das folgende Bit Code, aber wenn ich es mit GCC 4.4 mit verschiedenen Optimierungsflags kompiliere, erhalte ich einige unerwartete Ergebnisse, wenn es ausgeführt wird.GCC-Problem mit rohen Doppeltyp-Vergleichen

#include <iostream> 

int main() 
{ 
    const unsigned int cnt = 10; 
    double lst[cnt] = { 0.0 }; 
    const double v[4] = { 131.313, 737.373, 979.797, 731.137 }; 

    for(unsigned int i = 0; i < cnt; ++i) { 
     lst[i] = v[i % 4] * i; 
    } 

    for(unsigned int i = 0; i < cnt; ++i) { 
     double d = v[i % 4] * i; 
     if(lst[i] != d) { 
     std::cout << "error @ : " << i << std::endl; 
     return 1; 
     } 
    } 
    return 0; 
} 
  • , wenn sie mit kompiliert: "g ++ -pedantic -Wall Werror -O1 -o Test test.cpp" ich folgende Ausgabe: "Fehler @ 3"

  • , wenn sie mit kompiliert: "g ++ -pedantic -Wall Werror -O2 -o Test test.cpp" ich folgende Ausgabe: "Fehler @ 3"

  • , wenn sie mit kompiliert: "g ++ -pedantic -Wall Werror O3 -o Test test.cpp" Ich bekomme keine Fehler

  • , wenn sie mit kompiliert: „g ++ -pedantic -Wall - Werror -o Test test.cpp“ ich keine Fehler

ich glaube nicht, dass dies ein Problem zu Rundung oder epsilon Unterschied im Vergleich mit Bezug zu sein. Ich habe dies mit Intel v10 und MSVC 9.0 versucht und sie scheinen alle wie erwartet zu funktionieren. Ich glaube, das sollte nicht mehr als ein bitweiser Vergleich sein.

Wenn ich die if-Anweisung mit der folgenden ersetzen: if (static_cast<long long int>(lst[i]) != static_cast<long long int>(d)), und fügen Sie „-Wno-lang-lang“ bekomme ich keine Fehler in einem der Optimierungsmodi bei der Ausführung.

Wenn ich std::cout << d << std::endl; vor dem „Return 1“ hinzufügen, bekomme ich keine Fehler in einem der Optimierungsmodi bei der Ausführung.

Ist das ein Fehler in meinem Code, oder ist etwas nicht in Ordnung mit GCC und der Art, wie es den doppelten Typ behandelt?

Hinweis: Ich habe gerade dies mit GCC-Versionen 4.3 und 3.3 versucht, der Fehler wird nicht angezeigt.

Auflösung: Mike Dinsdale nahm den folgenden Fehlerbericht: http://gcc.gnu.org/bugzilla/show_bug.cgi?id=323 Es scheint, das GCC-Team ist nicht ganz sicher über die Natur des Problems.

Wie im Fehlerbericht vorgeschlagen, besteht eine mögliche Lösung darin, die Option ffloat-store zu verwenden. Ich habe das versucht und es funktioniert, aber die Ergebnisse aus Sicht der Leistung sind nicht so toll, aber ymmv.

+1

Vergleichen Sie 'doubles' am besten mit einem kleinen Delta-Wert, um Gleitkomma-Ungenauigkeiten zu berücksichtigen. Versuchen Sie, die Werte von "lst [i]" und "d" direkt vor dem Vergleich auszudrucken und lassen Sie uns wissen, wie unterschiedlich sie sind. – dirkgently

+3

Dies ist kein Szenario, wo ich Werte aus verschiedenen Quellen erzeugt habe und ich möchte sehen, ob sie innerhalb einer Toleranz gleich sind, diese sind identische Werte (bitweise), sie sollten wahr vergleichen. Die Tatsache, dass der Compiler keine Binärdatei erzeugt, die eine konsistente Menge von Ausgaben liefert, ist beunruhigend. –

+0

@Momoner: Wenn Sie das Programm so ändern, dass anstelle der FPU ein bitweiser Vergleich verwendet wird, ändern Sie die Baugruppe. Scheint grundlegend, aber das bedeutet, es wird nicht die Werte auf der FPU verwenden, die ausgeschaltet sein können, und tatsächlich laden und möglicherweise kürzen sie. Einfach gesagt, Ihr Test ist nicht schlüssig. – GManNickG

Antwort

4

Das Problem ist wahrscheinlich das Ergebnis einer Präzision zu verlieren, wenn das Ergebnis eines Ausdrucks Speicherung vs. der Compiler nicht so in einer lokalen als Optimierungs tun:

double d = v[i % 4] * i; // the result, `d`, might be kept in a register 
          // instead of storing it in a memory location, 
          // keeping full precision 

if(lst[i] != d) {   // the value stored in lst[i] may have lost some 
          // precision since it had to be stored in memory, 
          // which might not be able to hold the full 
          // precision that the expression generated 

Der Standard C99 sagt in 6.3.1 .8/2 „übliche arithmetische Umwandlungen“:

Die Werte von schwimmenden Operanden und der Ergebnisse der schwimmenden Ausdrücke in größerer Genauigkeit und Reichweite dargestellt werden kann als die vom Typ erforderlich ist; Die Typen werden dabei nicht geändert.

+1

Dieser Fehler wird nicht angezeigt, wenn keine Optimierung wie in der vierten Option aktiviert ist, auch auf demselben System (ubuntu) und bei Windows mit Intel v10 wird der Fehler nicht angezeigt. –

+3

@Momoner: Die Auswirkungen dieses Verhaltens werden sehr wahrscheinlich stark von Implementierungs-, Plattform- und Optimierungseinstellungen beeinflusst. Diese Art der Sache ist einer der Gründe, warum ich ziemlich glücklich bin, dass ich mich selten mit Fließkomma beschäftigen muss. –

+1

Ich stimme zu, Floating-Point-Probleme waren der Fluch meiner Existenz in den letzten paar Jahren, du denkst, du hast alles gesehen, dann taucht plötzlich etwas anderes auf. –

3

Die Breite der Gleitkommaregister in x86 unterscheidet sich von der Breite des double im RAM. Daher können Vergleiche Erfolg haben oder scheitern, ganz abhängig davon, wie der Compiler entscheidet, die Lasten von Gleitkommawerten zu optimieren.

+0

das ist richtig, aber sollte nicht der Compiler den richtigen Code für den gegebenen Ausdruck erzeugen? –

+0

Es erzeugt korrekten Code. Wie ist es nicht korrekt? In einem Dezimalbeispiel ist 3.333 nicht gleich 3.333333. So sieht es die FPU nur binär statt dezimal. –

+2

der Punkt, den ich versuche zu machen ist, dass die Werte auf beiden Seiten des "==" in der gleichen Weise generiert werden. Ich verstehe, dass für Doppel a == b und b == a nicht das gleiche Ergebnis liefern kann. Aber diese Werte sind bitweise identisch, wenn sie nicht der lange lange Vergleichsansatz wären, würde das nicht funktionieren. Wie erklärt man das Ganze auch, wenn ich eine extra Druckanweisung habe? riecht das nicht ein bisschen fischig? –

6

Die Tatsache, dass das Ergebnis von den Optimierungseinstellungen abhängt, deutet darauf hin, dass es die x87 erweiterte Präzision sein könnte, die mit den Dingen vermischt (wie Michael Burr sagt).

Hier einige Code, den ich (mit gcc auf x86-Prozessoren) verwenden, um die erweiterten Präzision auszuschalten:

static const unsigned int PRECISION_BIT_MASK = 0x300; 
///< bitmask to mask out all non-precision bits in the fpu control word \cite{INTEL}. 
static const unsigned int EXTENDED_PRECISION_BITS = 0x300; 
///< add to the fpu control word (after zeroing precision bits) to turn on extended precision \cite{INTEL}. 
static const unsigned int STANDARD_PRECISION_BITS = 0x200; 
///< add to the fpu control word (after zeroing precision bits) to turn off extended precision \cite{INTEL}. 

void set_fpu_control_word(unsigned int mode) 
{ 
    asm ("fldcw %0" : : "m" (*&mode)); 
} 

unsigned int get_fpu_control_word() 
{ 
    volatile unsigned int mode = 0; 
    asm ("fstcw %0" : "=m" (*&mode)); 
    return mode; 
} 

bool fpu_set_extended_precision_is_on(bool state) 
{ 
    unsigned int old_cw = get_fpu_control_word(); 
    unsigned int masked = old_cw & ~PRECISION_BIT_MASK; 
    unsigned int new_cw; 
    if(state) 
    new_cw = masked + EXTENDED_PRECISION_BITS; 
    else 
    new_cw = masked + STANDARD_PRECISION_BITS; 
    set_fpu_control_word(new_cw); 
    return true; 
} 

bool fpu_get_extended_precision_is_on() 
{ 
    unsigned int old_cw = get_fpu_control_word(); 
    return ((old_cw & PRECISION_BIT_MASK) == 0x300); 
} 

Oder Sie können Ihren Code mit valgrind nur ausführen, die nicht die 80-Bit-Register nicht simulieren und ist wahrscheinlich einfacher für ein kurzes Programm wie dieses!

+0

@Mike: Das Steuerwort ist eingeschaltet, ich denke, dass dies ein Problem von 64-Bit-Fließkommawerten ist, die zu 32-Bit-Fließkommawerten herabgestuft werden. –

+3

Zu 32-Bit? Hast du den Unterschied überprüft und es ist wirklich so groß? Wenn ja, sieht das wie ein Fehler in gcc aus. Wenn nicht, wette ich, dass es diese 80-Bit-> 64-Bit-Trunkierung ist, die durch Speichern der FPU-Register im Speicher verursacht wird. Hast du Valgrind installiert? Wenn ja, würde ich überprüfen, ob das Problem verschwindet, wenn ich deinen Code durch Valgrind laufen lasse. –

+0

@Mike: ein Fehler im Zusammenhang mit dem Speichern des FPU-Status macht, da, wenn ich den Wert "d" noch einmal zugreifen (aka eine Druckanweisung für d) das Problem verschwindet. –

Verwandte Themen