Forum: PC-Programmierung PC kann -1 + 1 nicht rechnen.


von J. T. (chaoskind)


Angehängte Dateien:

Lesenswert?

Moin Moin,

ich befasse mich z Zt unter anderem mit Vektoren (ich will mich ein 
wenig an der Grafikprogrammierung versuchen)

Dazu hab ich ein struct sVektor erschaffen, in dem die Werte für die 
einzelnen Dimensionen des Vektors drin stehen. Desweiteren eine 
Funktion, die 2 solcher Vektoren addieren kann. Das macht sie 
normalerweise auch recht gut. Aber an einer Stelle des Programms meinte 
sie, -1 + 1 wäre 4.857..... und nicht 0. Ich schmeiße in die Funktion 
Pointer der zu addierenden Vektoren und sie gibt mir das Ergebnis 
zurück.

Nachdem das an dieser Stelle nicht funktioniert hat, hab ich das was die 
Funktion macht, an der Stelle einfach nochmal hingeschrieben. Trotzdem 
noch das falsche Ergebnis.

Dann hab ich mir gedacht, evtl geht irgendwas mit den Pointern schief. 
Also hab ich mir neue sVektor erstellt, die Werte des Pointers 
reingeladen. Die Werte werden in der Watch korrekt angezeigt. Der X-wert 
des einen Vektors ist -1, der des Anderen 1. Ich addiere die beiden 
X-werte der Vektoren, es kommt aber 4.857..... raus.

Im Anhang mal Bilder während des Degbuggens, man sieht einmal die Watch 
vor dem Schritt, und dann hinterher. Desweiteren noch der Quelltext.

P.S. ich hab die Watch grad  mal "breiter" gezogen. Es sind "nur" 
4,857....*10^-17, also nur ne winzige Abweichung von null. Aber halt 
nicht null..... Ich schreib übrigens in CodeBlocks mit GCC

mit etwas verwirrten Grüßen, Chaos

P.P.S
Hier noch die meiner nach entscheidenden Stellen aus dem Quelltext:
in Grafik_Bildschirm.c
1
sVektor SchnittGE(sEbene* pBildebene, sVektor* pPunkt, sVektor* pKamera)
2
{
3
4
    long double t = 0;
5
6
    sVektor Hilfsvektor = {0,0,0};
7
    sVektor HV2 = {0,0,0};
8
    sVektor Punkt = {0,0,0};
9
    sVektor Schnittpunkt = {0,0,0};
10
11
12
13
14
15
    Hilfsvektor = Differenz(pKamera, pPunkt);
16
17
    //T = -(NxPx+NyPy+NzPz)+(NxSx+NySy+NzSz)/(NxKx + NyKy + NzKz)
18
19
    t =(    ( (pBildebene->Normvektor.X*pPunkt->X)
20
            + (pBildebene->Normvektor.Y*pPunkt->Y)
21
            + (pBildebene->Normvektor.Z*pPunkt->Z)
22
            + (pBildebene->Normvektor.X*pBildebene->Stuetzvektor.X)
23
            + (pBildebene->Normvektor.Y*pBildebene->Stuetzvektor.Y)
24
            + (pBildebene->Normvektor.Z*pBildebene->Stuetzvektor.Z) )
25
              /
26
            ( (pBildebene->Normvektor.X*Hilfsvektor.X)
27
            + (pBildebene->Normvektor.Y*Hilfsvektor.Y)
28
            + (pBildebene->Normvektor.Z*Hilfsvektor.Z) )
29
              );
30
31
    Punkt = *pPunkt;
32
    Hilfsvektor  = SkalarMul(&t, &Hilfsvektor);
33
    HV2.X = Hilfsvektor.X + Punkt.X;
34
    HV2.Y = Punkt.Y + Hilfsvektor.Y;
35
    HV2.Z = Punkt.Z + Hilfsvektor.Z;
36
    //Schnittpunkt = Summe(pPunkt, &Hilfsvektor);
37
38
    return Hilfsvektor;
39
    //return Schnittpunkt;
40
}
Das ist grad die Variante ohne Pointer, der Rückgabewert ist erst mal 
egal, da ich die Werte ja beim debuggen in der Watch ansehe.

und noch die Summierfunktion:
in Transformationen.c
1
sVektor Summe(sVektor* P1, sVektor* P2)
2
{
3
    sVektor Sum;
4
5
    Sum.X = P1->X + P2->X;
6
    Sum.Y = P1->Y + P2->Y;
7
    Sum.Z = P1->Z + P2->Z;
8
9
    return Sum;
10
}

von Sebastian V. (sebi_s)


Lesenswert?

J. T. schrieb:
> P.S. ich hab die Watch grad  mal "breiter" gezogen. Es sind "nur"
> 4,857....*10^-17, also nur ne winzige Abweichung von null. Aber halt
> nicht null..... Ich schreib übrigens in CodeBlocks mit GCC

Das ist normal. Beim Rechnen mit float/double muss man mit 
Rundungsfehlern rechnen. Wenn du es exakt möchtest musst du mit ints 
rechnen aber das eignet sich ja auch nicht gerade für Koordinaten.

von J. T. (chaoskind)


Lesenswert?

Danke erstmal für deine Antwort. Aber wie kann es denn sein, das die 
Rundungsfehler unterschiedlich ausfallen? An anderer Stelle sagt er mir 
ja exakt null für die selbe Rechnung?

Sebastian V. schrieb:
> Wenn du es exakt möchtest musst du mit ints
> rechnen aber das eignet sich ja auch nicht gerade für Koordinaten.

Ich hatte mal mit dem Gedanken "Festkomma-Aritmetik" gespielt.

von Rolf M. (rmagnus)


Lesenswert?

J. T. schrieb:
> P.S. ich hab die Watch grad  mal "breiter" gezogen. Es sind "nur"
> 4,857....*10^-17, also nur ne winzige Abweichung von null.

Sind denn deine Ausgangswerte wirklich exakt 1 und -1? Wenn die durch 
vorherige Rundungsfehler irgendwo weit nach dem Komma noch was haben, 
kommt als Summe natürlich nicht mehr 0, sondern ein sehr kleiner Wert 
raus.
Auch long double kann keine 17 Nachkommastellen auflösen.

PS:

Zitat vom Postingfenster:
"Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

von qwertzuiopü+ (Gast)


Lesenswert?

Rolf M. schrieb:
> Auch long double kann keine 17 Nachkommastellen auflösen.

Und wieder ein, der Floatingpoint nicht verstanden hat.

@TE
Deine 4,857....*10^-17 sollten, wenn du tatsächlich mit 1 und -1 
gerechnet hast, kleiner als Epsilon des entsprechenden Compilers ist. 
Eine Übersicht gibt es hier: 
http://www2.informatik.uni-halle.de/lehre/c/c623.html

Das Problem ist die Darstellung von -1 als binäre Zahl; es handelt sich 
um eine Periode. Das führt irgendwann zu Rundungsfehlern.

von Sebastian V. (sebi_s)


Lesenswert?

J. T. schrieb:
> Danke erstmal für deine Antwort. Aber wie kann es denn sein, das die
> Rundungsfehler unterschiedlich ausfallen? An anderer Stelle sagt er mir
> ja exakt null für die selbe Rechnung?

Vielleicht -ffast-math oder -funsafe-math-optimizations aktiviert? Da 
könnte je nach Code drumherum mal unterschiedlich gerundet werden. 
Allerdings sollte man sich fragen was dagegen spricht wenn irgendwas 
10^-17 als Ergebniss rauskommt statt 0. Tests auf Gleichheit mit 
Floating Point Zahlen sollte man sowieso vermeiden (aus eben diesem 
Grund). Und für normale Anwendungsfälle spielt es meist auch keine 
Rolle. Erst recht bei Grafikprogrammierung merkt es kein Mensch wenn 
eine Grafik 10^-17 Einheiten von was auch immer falsch positioniert ist.

: Bearbeitet durch User
von Rolf M. (rmagnus)


Lesenswert?

qwertzuiopü+ schrieb:
> eine 4,857....*10^-17 sollten, wenn du tatsächlich mit 1 und -1
> gerechnet hast, kleiner als Epsilon des entsprechenden Compilers ist.
> Eine Übersicht gibt es hier:

Wow... Visual C++ 1.0, Borland C++ 4.5, GNU C unter DOS. Ja, alles sehr 
relevant.
In Visual C++ ist long double schon seit sie auf 32 Bit umgestiegen sind 
nicht mehr 80 Bit breit. Somit wird dort der selbe Wert wie für double 
gelten.

von J. T. (chaoskind)


Lesenswert?

Rolf M. schrieb:
> Sind denn deine Ausgangswerte wirklich exakt 1 und -1?

Sagt zumindest die Watch.

Rolf M. schrieb:
> Auch long double kann keine 17 Nachkommastellen auflösen.

Mit Floats kannst du "beliebig"(es gibt schon Grenzen) genau auflösen. 
Aber immer nur einen begrenzten Bereich.

qwertzuiopü+ schrieb:
> Deine 4,857....*10^-17 sollten, wenn du tatsächlich mit 1 und -1
> gerechnet hast, kleiner als Epsilon des entsprechenden Compilers ist.

Hab ich tatsächlich mit gerechnet. Du meinst, der Wert sollte kleiner 
SEIN? Sorry normalerweise hack ich nicht auf Rechtschreibung rum, aber 
irgendwie ist das mit dem "ist" so sinnentstellend, das ich nachhaken 
muss =).
Was genau ist Epsilon?


Erstmal bin ich ja schon froh, das der Wert nur um 4*10^-17 abweicht, 
und nicht um 4, das ist ein beruhigend geringfügiger Unterschied :D.

Ich werd mal sehen, ob ich damit leben kann, oder dass doch noch auf 
Festkomma umstrick.

von J. T. (chaoskind)


Lesenswert?

Sebastian V. schrieb:
> Vielleicht -ffast-math oder -funsafe-math-optimizations aktiviert?

Optimierungen sind ganz aus.

von Klaus (Gast)


Lesenswert?

J. T. schrieb:
> P.S. ich hab die Watch grad  mal "breiter" gezogen. Es sind "nur"
> 4,857....*10^-17, also nur ne winzige Abweichung von null. Aber halt
> nicht null..... Ich schreib übrigens in CodeBlocks mit GCC

Dafür hab ich einen Text:

> Floating point arithmethic is like moving a pile of sand. Every time
> you do it, you loose a little sand and pick up a litte dirt.

MfG Klaus

von J. T. (chaoskind)


Lesenswert?

Klaus schrieb:
>> Floating point arithmethic is like moving a pile of sand. Every time
>> you do it, you loose a little sand and pick up a litte dirt.

Den brachte Karl-Heinz schon, als noch mit floats statt long double 
gearbeitet hab, und nach den Rundungsfehlern fragte, da wurde dann (von 
jdm anders) in nem Nebensatz "long double" eingeworfen....

Da trat das Problem auf, das mein Würfel beim drehen kontinuierlich um 
die Drehachse schrumpfte. Dafür  hab ich schon nen Ansatz im 
Hinterkopf...

von Wolfgang S. (ws01)


Lesenswert?

Oha, eine falsche Behauptung gleich im Betreff und ansonsten nirgendwo 
ein Fragezeichen in Sicht. Sehr trollig.

Lesestoff:

https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

von J. T. (chaoskind)


Lesenswert?

Wolfgang S. schrieb:
> Sehr trollig

Den Trollereivorwurf weise ich entschieden zurück!

Das ich unterwegs entdeckt hab, das der Fehler winzig und nicht mehrere 
100% ist, hab ich ja shcon im Eingangspost geschrieben.

Wolfgang S. schrieb:
> ansonsten nirgendwo
> ein Fragezeichen in Sicht

Das Fragezeichen lässt sich eindampfen zu:

Wieso macht er an einer Stelle 0 draus, an dieser aber nicht?

Danke für den Lesestoff

: Bearbeitet durch User
von Klaus (Gast)


Lesenswert?

J. T. schrieb:
> Klaus schrieb:
>>> Floating point arithmethic is like moving a pile of sand. Every time
>>> you do it, you loose a little sand and pick up a litte dirt.
>
> Den brachte Karl-Heinz schon,

Ich wusste nicht mehr, wo ich ihn her hatte. Daher Danke für die 
Quellenangabe

MfG Klaus

von Daniel A. (daniel-a)


Lesenswert?

In einer Renderengine ist es unüblich die Originaldaten eines statischen 
Objekts zu ändern, nur um das Objekt zu Drehen, verschieben oder zu 
Animieren. Normalerweise geht man Folgendermassen for:

Man hat eine Liste von Objekten. Die Objekte haben Koordinaten für die 
Punkte, Indeces um die Punkte zu Dreiecken zu verbinden, eine 
Modelviewmatrix für Transformationen, eventuell Keyframes oder 
Skeletdaten für Animationen.
Die Koordinaten können entweder alle zusammen in einen grossen 
Vertexbuffer kopiert werden, oder einzeln und nacheinander, wobei man 
dabei auch gleich zwischen Keyframes usw. interpolieren kann.
Dann multipliziert man Üblicherweise die Modelviewmatrix mit der World- 
und Projektionsmatrix und diese dann mit dem dem Vektor. Danach nutzt 
man die Index und Vertexdaten um die Geometrie zu zeichnen, dabei 
berechnet man die Farbe des Pixels z.B. mit Texturen und mittels der 
Baryzentrischen coordinaten interpolierten Texturcordinaten, oder auf 
selbige weise interpolierte Farbwerte. Bei der nächsten frame fängt man 
wieder bei den noch unveränderten werten an und wendet Transformationen 
auf die Jeweiligen matrizen an.

Der Springende Punkt ist, dass man dadurch die Rechenungenauigkeit 
reduzieren kann, diese ist aber weiterhin vorhanden. Ausserdem lassen 
sich so viele Trigonometrische Funktionen vermeiden, diese sind meist 
recht langsam.

Ausserdem würde ich die Grafigkarte ausnutzen, welche meist schon eine 
derartige Grafigpipeline eingebaut haben. Ich empfehle OpenGL oder 
Direct3D dafür zu verwenden.

von Rolf M. (rmagnus)


Lesenswert?

Daniel A. schrieb:
> Man hat eine Liste von Objekten. Die Objekte haben Koordinaten für die
> Punkte, Indeces um die Punkte zu Dreiecken zu verbinden, eine
> Modelviewmatrix für Transformationen, eventuell Keyframes oder
> Skeletdaten für Animationen.

Keine Modelview-Matrix. Die wird erst in jedem Frame berechnet.

> Die Koordinaten können entweder alle zusammen in einen grossen
> Vertexbuffer kopiert werden, oder einzeln und nacheinander, wobei man
> dabei auch gleich zwischen Keyframes usw. interpolieren kann.
> Dann multipliziert man Üblicherweise die Modelviewmatrix mit der World-

Das, was bei dieser Multiplikation rauskommst, ist erst die 
Modelview-Matrix. Deshalb heißt sie ja so - weil sie vom 
Modell-Koordinatensystem ins View-Koordinatensytem transformiert.

> und Projektionsmatrix und diese dann mit dem dem Vektor.

> Bei der nächsten frame fängt man wieder bei den noch unveränderten werten
> an und wendet Transformationen auf die Jeweiligen matrizen an.

Das ist der wesentliche Teil.

von Wolfgang S. (ws01)


Lesenswert?

J. T. schrieb:

> Das Fragezeichen lässt sich eindampfen zu:
>
> Wieso macht er an einer Stelle 0 draus, an dieser aber nicht?

Keine Ahnung. Das wirst Du vermutlich herausfinden können, wenn Du für 
beide Fälle das Programm auf eine geschlossene Formel reduzierst und 
beim Vergleich den genannten Lesestoff berücksichtigst. Die Arbeit wirst 
Du Dir schon selber machen müssen.

> Danke für den Lesestoff

Zwischenzeitlich schon gelesen?

Einfachere Variante:

https://docs.python.org/2/tutorial/floatingpoint.html

von Rolf M. (rmagnus)


Lesenswert?

qwertzuiopü+ schrieb:
> Das Problem ist die Darstellung von -1 als binäre Zahl; es handelt sich
> um eine Periode. Das führt irgendwann zu Rundungsfehlern.

Hä? Bei -1 ist überhaupt nix periodisch.  Bis auf das Vorzeichenbit hat 
das exakt den selben Wert wie 1. Für jemanden, der so auf den Putz haut 
wie du, schreibst du einen beeinduckenden Unsinn.
Du hast das vermutlich mit dem Klassiker 10 * 0.1 verwechselt, wo nicht 
1 rauskommt, da 0.1 im Dualsystem eine periodische Zahl ist.

von Mark B. (markbrandis)


Lesenswert?

qwertzuiopü+ schrieb:
> Das Problem ist die Darstellung von -1 als binäre Zahl; es handelt sich
> um eine Periode.

Nö. -1 ist eine glatte Zweierpotenz (eben 2^0) mit negativem Vorzeichen.

von J. T. (chaoskind)


Lesenswert?

Nachdem ich nun einiges geändert habe, kann ich mit den Rundungsfehlern 
leben.

Ich lasse nun nicht mehr jedesmal die Punkte rotieren, sondern lasse die 
Punkte am am selben Fleck, und habe einen Rotationsakkumulator 
eingeführt. Dort werden die Rotationswerte einfach aufsummiert und die 
Rotationsfunktions wird dann jedesmal nur einmal mit dem Rotationsakku 
durchgeführt. So habe ich jedesmal nur den einen kleinen Rundungsfehler 
und er wird nicht mitgeschleppt.

Das klappt nun auch nach mehreren Stunden drehen lassen ohne Verzerrung.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.