0.0 und -0.0



  • Hallo,

    ich arbeite gerade an einer 3D-Engine. Dabei rechne ich viele Koordinaten und Vectoren um und wieder zurück. Ich benutzer zum debuggen Visual Studio 2008 Pro.

    Jetzt ist mir aufgefallen, dass ich bei float gelegentlich nicht 0.000000 stehen habe, sondern -0.00000 (oft nach asin). Mir ist durchaus klar, das ein Vorzeichen nur ein "Atrribut" auf einer bestimmten Speicherstelle ist.

    Meine Fragen:
    1. Ist das nur bei Microsoft so, dass ein 0.0f auch mal ein -0.0f ist?
    2. Kann es zu Problemen führen, wenn man 0.0f und -0.0f vergleicht? (Ist es für alle Complier das gleiche?)
    3. Sollte man sich einen Kopf drüber machen, wenn man mit -0.0f arbeitet?
    4. Gibt es mathematisch eigentlich eine negative Null? Oder ist Null eigentlich immer Vorzeichenfrei?

    Danke,
    Stefan



  • stefanjann schrieb:

    1. Ist das nur bei Microsoft so, dass ein 0.0f auch mal ein -0.0f ist?

    Nein, die fließkomma-Darstellung ist standardisiert, ist also quasi überall gleich/ähnlich.

    2. Kann es zu Problemen führen, wenn man 0.0f und -0.0f vergleicht? (Ist es für alle Complier das gleiche?)

    Nein, 0.0f == -0.0f gibt true wie zu erwarten.

    3. Sollte man sich einen Kopf drüber machen, wenn man mit -0.0f arbeitet?

    Nur wenn du dadurch dividierst 😉

    4. Gibt es mathematisch eigentlich eine negative Null? Oder ist Null eigentlich immer Vorzeichenfrei?

    Nein, gibt es nicht. In manchen Fällen wird allerdings +0 und -0 bei Grenzwertbetrahtungen benutzt, um anzudeuten dass es sich um einen Grenzwert von oben bzw. von unten her handelt. Es geht dabei aber eben nicht um 0 selbser sondern positive und negative Werte - 0 ist weder das eine noch das andere.



  • Hallo,

    stefanjann schrieb:

    4. Gibt es mathematisch eigentlich eine negative Null? Oder ist Null eigentlich immer Vorzeichenfrei?

    Danke,
    Stefan

    die Null ist mathematisch eindeutig bestimmt.
    Es ist a*0 = 0 für alle reelle Zahlen a.
    Es ist -0 = (-1)*0 = 0, mit a = -1.

    Gruß,
    B.B.



  • Davon abgesehen das auf Gleichheit prüfen bei Fließkommazahlen selten Sinn macht, da sie zu unpräzise sind.



  • Danke schön.



  • stefanjann schrieb:

    Hallo,
    Jetzt ist mir aufgefallen, dass ich bei float gelegentlich nicht 0.000000 stehen habe, sondern -0.00000 (oft nach asin). Mir ist durchaus klar, das ein Vorzeichen nur ein "Atrribut" auf einer bestimmten Speicherstelle ist.

    Ja, binäre Fließkomma-Darstellungen haben ein Vorzeichenbit. Aber bist Du sicher, dass das nicht nur Rundungskürzungen im Debugger oder printf/cout sind?

    Also evtl. ist das, was Du als -0.0000000 sieht in Wirklichkeit -0.00000000000000004345.

    stefanjann schrieb:

    Meine Fragen:

    1. Ist das nur bei Microsoft so, dass ein 0.0f auch mal ein -0.0f ist?

    Das macht ja nicht MS oder der Compiler. Es ist die FPU des Prozessors. Alle modernen FPUs nutzen die IEEE-754 Standards für float/double/etc.

    Die x87 FPU hat noch 80Bit Register und man kann irgendwo konfigurieren, ob floats oder doubles intern mit 80Bit (höhere numerische Stabilität/Genauigkeit) arbeiten. Aber sobald ein store Register->Mem passiert, wird auf 32bit float oder 64bit double gerundet. Da dies dank Compiler-Optimierungen für den Programmierer nicht kontrollierbar ist, kann es zu unbestimmten Genauigkeitsverhalten kommen. Kann man abschalten. Auch gibt es im IEEE 754 mehrere Rundungsmodi (rauf/runter/nächstes...).

    Sollte aber auf Positiv/Negativ keinen Einfluss haben.

    stefanjann schrieb:

    2. Kann es zu Problemen führen, wenn man 0.0f und -0.0f vergleicht? (Ist es für alle Complier das gleiche?)

    Keine Ahnung. Macht auch die FPU, und ob die so schlau ist?

    stefanjann schrieb:

    3. Sollte man sich einen Kopf drüber machen, wenn man mit -0.0f arbeitet?

    Das Vorzeichen bei weiteren Berechnungen kann evtl. kippen? Aber Multiplikation mit +- Null sollte egal sein, Division ist eh Quatsch, Add/Sub auch egal.

    Aber evtl. wirft z.B. sqrt(-0.0) eine FPU-Exception und sqrt(0.0) wäre ok und einfach 0.0.

    stefanjann schrieb:

    4. Gibt es mathematisch eigentlich eine negative Null? Oder ist Null eigentlich immer Vorzeichenfrei?

    Wäre mir neu. In R ist Null vorzeichenfrei.



  • Fellhuhn schrieb:

    Davon abgesehen das auf Gleichheit prüfen bei Fließkommazahlen selten Sinn macht, da sie zu unpräzise sind.

    Unpräzise bezogen auf was? Nun einen Flameware ....

    @stefanjann Deine Frage ist durch aus berechtigt und bei weitem nicht so trivial wie angenommen. Im IEEE-754 Standard findest du die Definition von Gleitkommazahlen. In diesem sind auch die Sonderfälle wie z.B. 0.0 und -0.0 geregelt.

    Würde man einen Bitweisen-Vergleich der beiden Zahlen, 0.0 und -0.0, durchführen, führt dies zu 0.0 != -0.0. Warum? Das erste Bit bei einer Gleitkommazahl stellt das Vorzeichen dar. Ein Vergleich auf Bitebene bezieht sich nur auf die einzelnen Bits und wenn es da einen Unterschied gibt sind die beiden nicht gleich.
    Gott sei Dank ist im IEEE-Standard nicht der Bitweise-Vergleich definiert. Solange sich dein Zielsystem (Windows, Linux, aber auch Java (Plattform, nicht Sprache), .NET, Mono, ...) an den Standard hält, musst du dir keine Gedanken über 0.0 und -0.0 machen.

    Tipp: Einen Vergleich von Gleitkommazahlen solltest du sowieso mit einer zul. Abweichung durchführen.
    a +- deltaA = B
    Achtung: deltaA != epsilon

    Das deltaA musst du bei jeder Vergleich-Operation berechnen.
    Gleitkommazahl, Bitfolge: Vorzeichen + Mantisse + Exponent

    Der falsche Ansatz und am meisten verwendete Ansatz lautet:
    deltaA = Double.Epsilon
    Anmerkung: Dies ist absolut FALSCH. Wenn du das machst, dann lass es lieber gleich. Das hier verwendete Epsilon bezieht sich auf einen Exponenten von 1 oder 2^-24. Aber hat deine Zahl den Exponenten?

    Der naive Ansatz lautet:
    deltaA = 1 * 2^Exponent
    Anmerkung: Hier wird die kleinste, darstellbare Zahl mit dem aktuellen Exponenten als Abweichung definiert. Das ist erst mal nicht falsch. Aber auch nicht unbedingt richtig. Da bereits beim Umrechnen vom 10er System ins 2er System Rundungsfehler anfallen.
    Wenn man es zu 100% richtig manchen möchte, muss man selbst die Dezimalzahl x in eine Gleitkommazahl umrechnen und danach den Fehler festhalten.

    Oder man handelt das mit einem Faktor ab:
    deltaA = faktor * 2^Exponent

    Ich persönlich würde dir von diesem Faktor abraten und alles manuell umrechnen. Das würde jedoch auch bedeuten, dass du zu jeder Gleitkommazahl einen zusätzlichen Wert mitschlepst und bei jeder Operation (+, -, *, /, %, log, sin, asin, ...) eine Fehlerrechnung machen musst.

    Wenn du gerade nicht weist, wovon ich rede dann rechne doch einfach mal die Dezimalzahl 0,1 in eine Gleitkommazahl um (per Hand versteht sich).

    Gruß,
    Thomas



  • Nunja, zum Glück geht es hier (in meinem Fall) nur um Koordinaten die ich an OpenGL schicke um Formen zu zeichnen. Daher sind Ungenauigkeiten so ab der dritten Stelle hintern Komma wahrscheinlich zu vernachlässigen, da diese eh nicht sooooo genau gerendert werden können.

    Aber die Thematik interessiert mich jetzt. Und ich hab wirklich mal die Koordinaten die bei mir -0.0000000 im debugger stehen komplett in eine Datei schreiben lassen. Und siehe da: -0.000000000000127...
    Also ist die -0.0 bei mir gar keine -0.0 sondern eine grundete negative Zahl.

    Innerhalb meiner Engine benutze ich zur Berechnung extern eigene Typen, z.B. Angle, Length, etc.... Intern arbeite ich allerdings mit float. Also wäre es durchaus denkbar, intern die Typen auch mit einer eigenen Flieskomma-Berechnung zu bestücken.
    Aber wie gesagt: Es handelt sich um Koordinaten für OpenGl und soooo genau wird das nicht gezeichnet. Also ist der Aufwand nicht Wert und ich nehme die Rundungsfehler in Kauf....

    Wobei natürlich bei meinem -0.000000000000127... ein >=0 schon zu einem Problem führen kann. Also werde ich auf alle Fälle noch eine Rundungsfunktion (oder ähnliches) schreiben, die auf (grobe Schätzung) 5 Stellen hinterm Komma rundet. Das sollte für die (meine) Paxis reichen.

    Danke euch schon mal für die Erklärungen. Ich hab auf alle Fälle wieder was gelernt.



  • Ist übrigens eines der Probleme bei naiver Darstellung von Lat/Lon-Werten (Koordinaten in Grad/Minuten-Darstellung).

    Denn der Wert 5.99999995 Grad wird gerechnet in: 5 Grad un 0.99999995 * 60 Minuten. Das ergibt ja 59.99999etc. Minuten, nutzt man nun aber stringstream um dies mit 3 Stellen Genauigkeit auszugeben, bekommt man: 5°60.000'. Tolle Wurst. 😉


Log in to reply