Frage

Ich war mein Projekt Debuggen und konnte keinen Fehler finden. Schließlich befindet ich es. Schauen Sie sich den Code. Sie denken, dass alles in Ordnung ist, und das Ergebnis wird „OK! OK! OK!“ Sein, nicht wahr? Nun kompilieren Sie es mit VC (Ich habe versucht, VS2005 und VS2008).

#include <math.h>
#include <stdio.h>


int main () {
    for ( double x = 90100.0; x<90120.0; x+=1 )
    {
        if ( cos(x) == cos(x) )
            printf ("x==%f  OK!\n", x);
        else
            printf ("x==%f  FAIL!\n", x);
    }

    getchar();
    return 0; 
}

Die Magie Doppel konstant ist 90.112,0. Wenn x <90.112,0 alles ist in Ordnung, wenn x> 90.112,0 - Nein! Sie können cos sin ändern.

Irgendwelche Ideen? Vergessen Sie nicht, dass die Sünde und cos sind periodisch.

War es hilfreich?

Lösung

Könnte dies: http: //www.parashift .com / c ++ - FAQ-lite / newbie.html # faq-29.18

  

Ich weiß, es ist schwer zu akzeptieren, aber Gleitkomma-Arithmetik funktioniert einfach nicht wie die meisten Menschen erwarten. Schlimmer noch, sind einige der Unterschiede abhängig von den Details Ihrer Floating-Point der besonderen Computer-Hardware und / oder den Optimierungseinstellungen, die Sie auf Ihrem speziellen Compiler verwenden. Sie werden vielleicht nicht so, aber es ist so, wie es ist. Der einzige Weg, um "get it" ist beiseite über Ihre Annahmen zu setzen, wie es sollte verhalten und akzeptieren die Dinge wie sie eigentlich Sie behave ...

     

(mit Betonung auf dem Wort „oft“, das Verhalten hängt von Ihrer Hardware, Compiler usw.): Floating-Point-Berechnungen und Vergleiche werden oft durch spezielle Hardware durchgeführt, dass oft spezielles Register enthalten, und diese Register oft mehr Bits haben als ein double. Das bedeutet, dass Zwischenpunkt schwebenden Berechnungen oft mehr Bits als sizeof(double) haben, und wenn ein Fließkommawert in den Arbeitsspeicher geschrieben wird, wird es oft abgeschnitten, oft einige Bits an Präzision zu verlieren ...

     

nur daran erinnern: Floating-Point-Vergleiche sind schwierig und subtil und voller Gefahren. Achtung. Die Art und Weise Gleitkomma wirklich funktioniert, ist, unterscheidet sich von der Art und Weise die meisten Programmierer daran denken neigen sollte an die Arbeit. Wenn Sie beabsichtigen, Floating-Point zu verwenden, müssen Sie lernen, wie es tatsächlich funktioniert ...

Andere Tipps

Wie andere haben darauf hingewiesen, die VS-Mathematik-Bibliothek ist seine Berechnung auf der x87-FPU tun, und zum Erzeugen von 80-Bit-Ergebnisse, obwohl der Typ double ist.

So:

  1. cos () aufgerufen wird, und kehrt mit cos (x) auf der Oberseite des x87-Stack als 80bit float
  2. cos (x) wird aus dem x87 Stapel genommen und in dem Speicher als Doppel gespeichert ist; Dies bewirkt, dass es zu 64-Bit-Float gerundet werden, die ändert seinen Wert
  3. cos () aufgerufen wird, und kehrt mit cos (x) auf der Oberseite des x87-Stack als 80bit float
  4. Der gerundete Wert wird auf den x87-Stapel aus dem Speicher geladen
  5. die abgerundeten und ungerundeten Werte von cos (x) vergleichen ungleich.

Viele mathematische Bibliotheken und Compiler schützen Sie diese entweder durch die Berechnung in 64-Bit-Float in den SSE-Register zu tun, wenn verfügbar, oder indem Sie die Werte zwingen vor dem Vergleich gespeichert und abgerundet werden, oder durch das Speichern und Neuladen des Endergebnisses in die tatsächliche Berechnung von cos (). Der Compiler / Bibliothek Kombination mit zu arbeiten, passieren nicht so nachsichtig.

Die cos (x) == cos (x) Verfahren im Release-Modus erzeugt:

00DB101A  call        _CIcos (0DB1870h) 
00DB101F  fld         st(0) 
00DB1021  fucompp 

Der Wert wird berechnet, einmal, und dann kloniert, dann im Vergleich mit sich selbst - Ergebnis ok sein wird,

Das gleiche im Debug-Modus:

00A51405  sub         esp,8 
00A51408  fld         qword ptr [x] 
00A5140B  fstp        qword ptr [esp] 
00A5140E  call        @ILT+270(_cos) (0A51113h) 
00A51413  fld         qword ptr [x] 
00A51416  fstp        qword ptr [esp] 
00A51419  fstp        qword ptr [ebp-0D8h] 
00A5141F  call        @ILT+270(_cos) (0A51113h) 
00A51424  add         esp,8 
00A51427  fld         qword ptr [ebp-0D8h] 
00A5142D  fucompp          

Nun, passieren seltsame Dinge.
1. X geladen auf fstack (X, 0)
2. X auf normalen Stapel (Trunkierung)
gespeichert 3. Cosinus berechnet, das Ergebnis auf float Stapel
4. X wird wieder geladen
5. X auf normalen Stack gespeichert (Abschneiden, wie jetzt, wir sind „symmetrisch“)
6. Das Ergebnis des ersten Kosinus, die auf dem Stapel war im Speicher gespeichert ist, jetzt tritt ein weiterer Abschneiden für den ersten Wert
7. Kosinus, 2. Ergebnis, wenn auf dem Schwimmer-Stapel berechnet, aber dieser Wert wurde nur abgeschnitten einmal
8. 1. Wert wird auf die fstack geladen, aber dieser Wert wurde zweimal abgeschnitten (einmal vor dem Cosinus-Berechnung, einmal nach)
9. Diese zwei Werte werden miteinander verglichen -. Wir bekommen Rundungsfehler

Sie sollten nie nicht vergleichen Doppel für die Gleichstellung in den meisten Fällen. Sie können nicht bekommen, was Sie erwarten.

Point-Register Schwimmdock eine andere Größe als Speicherwert haben kann (in der aktuellen Intel-Maschinen, FPU-Register sind 80-Bit-vs 64-Bit-Doppelzimmer). Wenn der Compiler Code erzeugt, der den ersten Kosinus berechnet, speichert dann den Wert in den Speicher, der zweite Cosinus berechnet und vergleicht den Wert in dem Speicher von dem in dem Register dann könnten die Werte unterscheiden (aufgrund der Ausgaben 80 bis 64 Bits Rundung) .

Gleitkomma-Werte sind ein bisschen schwierig. Google für Punkt comparissons schweben.

Der Compiler kann Code generiert haben, dass endet eine 64-Bit-Doppel Wert mit einem Vergleich 80-Bit interne Gleitkommaregister. Testen Gleitkommazahlen für die Gleichstellung ist anfällig für diese Art von Fehler - Sie haben eine "fuzzy" Vergleich wie (Fabs (val1 - val2)

Inkrementieren und Testen eines Float-Wert als eine Schleifensteuerungsvariable im Allgemeinen eine wirklich schlechte Idee ist. Erstellen Sie eine separate int LCV nur für Looping auf, wenn Sie zu haben.

In diesem Fall ist es einfacher:

for ( int i = 90100; i<90120; i+=1 )    {
    if ( cos(i) == cos(i) )
        printf ("i==%d  OK!\n", i);
    else
        printf ("i==%d  FAIL!\n", i);
}

Wie um Problem? Ändern , wenn Block:

if ( (float)cos(x) == (float)cos(x) )
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top