PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : C++: Vergleiche mit Gleitkomma-Zahlen!?


eXistence
2006-03-06, 08:06:49
Hi,
ich habe mal gelernt, dass Vergleiche mit Gleitkomma-Zahlen problematisch sind, da viele Werte nicht exakt aufgelöst werden können (siehe auch http://www.3dcenter.de/artikel/fp_format/).

Wie sollte man also in so einem Fall vorgehen?
Kann die Null korrekt aufgelöst werden? Kann man also schreiben

if(n == 0.0) doSomething();
?

wie sieht es mit anderen Zahlen aus?

if(n == 2.5) doSomething();

?

Was ist hier guter Stil, wie macht man sowas?

Gast
2006-03-06, 08:18:00
if (fabs(ist - soll) < eps)

wobei eps eine kleine Float-Zahl (gewünschte Genauigkeit) ist, z.B. const float eps = 0.00001f.

Coda
2006-03-06, 09:06:54
Am besten die Algorithmen so schreiben dass man keine Vergleiche braucht, auch wenn das natürlich nicht immer möglich sein wird.

Trap
2006-03-06, 13:21:43
In http://docs.sun.com/source/806-3568/ncg_goldberg.html steht einiges zum Thema.

Ein wirkliches Problem bei Gleitkommarechnung ist, dass man bei C++ nicht genau sagen kann welche Operationen in welcher Reihenfolge und in welcher Genauigkeit durchgeführt werden.

Es gibt Rechnungen bei denen man genau vorhersagen kann was rauskommen kann, da ist == ok. Es gibt andere Rechnungen bei denen man das nicht kann und fabs funktioniert. Es gibt auch Rechnungen bei denen der Vorschlag mit fabs oben nicht funktioniert.

Pinoccio
2006-03-06, 13:44:44
if(n == 0.0) doSomething();
?

wie sieht es mit anderen Zahlen aus?

if(n == 2.5) doSomething();
Gehen wird beides, für n1=0.0 bzw n2=2.5 ist n1==0.0 true bzw n2==2.5 true. Das ist auch weniger das Problem.
Gefährlich wird es, wenn man n als Ergebnis einer Rechnung erhält, sieh zB hier (http://forum-3dcenter.de/vbulletin/showthread.php?p=3986773#post3986773), da FP-Zahlen nciht wie "mathematische Zahlen" rechnen, da ihre Genauigkeit Endlich sit. 10^100 - 1- 10^100 +1 ist nämlich meistens dann nicht 0 sondern 1.

mfg Sebastian

Trap
2006-03-06, 13:52:04
Das ist ein lustiges Beispiel:
double n = 0.1;
if(n==0.1) bla(); else blub();

Was wird aufgerufen? ;)

Pinoccio
2006-03-06, 14:17:05
Das ist ein lustiges Beispiel:
double n = 0.1;
if(n==0.1) bla(); else blub();

Was wird aufgerufen? ;)Hm, blub().


n==0.1 vergleicht nen double mit nem float. 0,1 ist nicht exakt darstellbar in beiden Formaten udn wird aber auch unterschiedlich kodiert, da double eine höhere genauigkeit zulässt. Ergo sind (double)0.1 und (float)0.1 verscheiden.

mfg Sebastian

eXistence
2006-03-06, 14:24:05
Thx für die Antworten, ich werde nochmal meinen Quelltext überarbeiten und gucken, ob ich einige Vergleiche nicht doch irgendwie umgehen kann, bei den anderen Sachen werden ich die fabs()-Methode nehmen :)

In http://docs.sun.com/source/806-3568/ncg_goldberg.html steht einiges zum Thema.

Wow, so ausführlich brauch es momentan garnicht, aber es klingt interessant, sobald ich Zeit hab, werde ich mir das mal durchlesen :)


Das ist ein lustiges Beispiel:
double n = 0.1;
if(n==0.1) bla(); else blub();

Was wird aufgerufen?

bei mir bla();
ich muss n schon als float deklarieren, damit blub() aufgerufen wird, aber das wird wohl compiler-abhängig sein, welcher Typ für das 0.1 genommen wird... (bei mir: VS .NET 2003)

Xmas
2006-03-06, 14:32:36
bla(), zumindest sollte es das bei einem vernünftigen Compiler.


n==0.1 vergleicht nen double mit nem float. 0,1 ist nicht exakt darstellbar in beiden Formaten udn wird aber auch unterschiedlich kodiert, da double eine höhere genauigkeit zulässt. Ergo sind (double)0.1 und (float)0.1 verscheiden.

0.1 ist kein float, sondern standardmäßig double. 0.1f ist float.

Trap
2006-03-06, 14:37:39
n==0.1 vergleicht nen double mit nem float. 0,1 ist nicht exakt darstellbar in beiden Formaten udn wird aber auch unterschiedlich kodiert, da double eine höhere genauigkeit zulässt. Ergo sind (double)0.1 und (float)0.1 verscheiden.
Du hast das 3. Format vergessen: 80-bit FPU des x86, die entscheidet schließlich ob es gleich ist.

Ich bin mir sicher, dass der C++-Standard nicht spezifiziert welche Funktion aufgerufen wird. Das wird in dem Goldberg-Artikel auch angesprochen.

Pinoccio
2006-03-06, 14:44:47
bla(), zumindest sollte es das bei einem vernünftigen Compiler.

0.1 ist kein float, sondern standardmäßig double. 0.1f ist float.Es wurde keine Programiersprache gennant, in der das Programm geschrieben sein soll. Imho könnte man es auch als Pseudo-Code durchgehen lassen. Und da ist meine Annahme, daß 0.1 automatisch zu einem float wird halt falsch, aber ebenso unwiderlegbar richtig wie die gegenteilige Annahme. In JAVA ist es nicht so wie ich schrieb (http://java.sun.com/docs/books/jls/third_edition/html/lexical.html#3.10.2), nach euren Bemerkungen ist das in den von euch bevorzugten Sprachen auch der Fall.
Letzendlich ist es auch egal, wichtig ist, daß jeder weiß, wie es in der Sprache ist, die er gerade nimmt.

Du hast das 3. Format vergessen: 80-bit FPU des x86, die entscheidet schließlich ob es gleich ist.IMHO nein (also vergessen ja, aber: auch die 80 Bit-FPU bekommt ja nur die bit-verschiedenen float- und double-Werte rein. So wie ich mir das vorstelle, werden überzählige Stellen mit nullen gefüllt. Woher soll sie auch wissen, daß mit 0.1f und 0.1d (welche ja verschieden sind) die gleiche Zahl gemeint sein soll? Zumal siehe oben)

mfg Sebastian

Coda
2006-03-06, 14:45:58
In C/C++ ist 0.1 double und 0.1f wäre float. Das ist sicher.

Trap
2006-03-06, 15:13:42
Es ist aber nirgends gesagt was für ein Bitmuster 0.1 oder 0.1f auf der 80-bit FPU ergeben soll und das kann je nach Compiler und Kontext durchaus unterschiedlich sein.

Neomi
2006-03-06, 21:07:55
Es ist aber nirgends gesagt was für ein Bitmuster 0.1 oder 0.1f auf der 80-bit FPU ergeben soll und das kann je nach Compiler und Kontext durchaus unterschiedlich sein.

Der Compiler macht aus 0.1 einen double mit 64 Bit, aus 0.1f einen float mit 32 Bit. Sofern der Compiler kein schlechter Scherz ist, sondern ein auch nur viertelwegs brauchbarer Compiler, ergeben die beiden 0.1 bei der Zuweisung und beim Vergleich ein identischen Bitmuster.

Wie die FPU die restlichen Bits beim Laden eines Wertes auffüllt, hat mit dem Compiler nichts mehr zu tun, allerdings gibt es hier nur eine einzige sinnvolle Variante. Und sollte die FPU doch tatsächlich aus zwei identischen doubles beim Laden zwei unterschiedliche Werte machen, ist die CPU bestimmt nicht primestable und damit das Ergebnis eh irrelevant.

Coda
2006-03-06, 21:11:04
Zumal man die FPU ja bei performancekritischen Sachen eh auf 32 oder 64 bit Präzision einstellt.

Pinoccio
2006-03-07, 15:31:09
Der Compiler macht aus 0.1 einen double mit 64 Bit, aus 0.1f einen float mit 32 Bit. Sofern der Compiler kein schlechter Scherz ist, sondern ein auch nur viertelwegs brauchbarer Compiler, ergeben die beiden 0.1 bei der Zuweisung und beim Vergleich ein identischen Bitmuster.Ich hoffe nicht, daß ein Compiler sowas macht! Und in der Tat macht zumindest der SUN-Compiler in Verison 1.5.0_04 von JAVA es nicht:Bitmuster:
s=Sign (Vorzeichen), e=Exponent, m=Mantisse
0.1d 11111110111001100110011001100110011001100110011001100110011010
se---------em-->
0.1f 111101110011001100110011001101
se--------em-->
(double)0.1f 11111110111001100110011001100110100000000000000000000000000000
se---------em-->
Vergleiche:
0.1f ==0.1d false
((double)0.1f)==0.1d false
((double)0.1f)==0.1f true
((float)0.1d)==0.1f truepublic class Main {

public static void main(String[] args) {
System.out.println("Bitmuster:");
System.out.println("s=Sign (Vorzeichen), e=Exponent, m=Mantisse ");
System.out.println(" 0.1d "+Long.toBinaryString(Double.doubleToRawLongBits(0.1d)));
System.out.println(" se---------em-->");
System.out.println(" 0.1f "+Integer.toBinaryString(Float.floatToRawIntBits(0.1f)));
System.out.println(" s e------em-->");
System.out.println("(double)0.1f "+Long.toBinaryString(Double.doubleToRawLongBits((double)0.1f)));
System.out.println(" se---------em-->");
System.out.println("Vergleiche:");
System.out.println(" 0.1f ==0.1d "+(0.1f==0.1d));
System.out.println("((double)0.1f)==0.1d "+(((double)0.1f)==0.1d));
System.out.println("((double)0.1f)==0.1f "+(((double)0.1f)==0.1f));
System.out.println(" ((float)0.1d)==0.1f "+(((float)0.1f)==0.1f));
}
}

/edit: Vielleicht habe ich dich auch etwas missverstanden. Falls du den Fall des dritten Vergleiches meintest, so sollte man genau das Verhalten erwarten dürfen welches auftritt, da in JAVA definiert ist (http://java.sun.com/docs/books/jls/third_edition/html/conversions.html#170983), daß bei Vergleichen ungleichlanger Typen immer zum "größeren" Typ gecasetet wird. Ergo wird aus 0.1d==0.1f durch den Kompiler 0.1d==(double)0.1f. Und das muss schiefgehen.

mfg Sebastian

Neomi
2006-03-07, 15:56:02
Jep, das war wirklich bloß ein Mißverständnis. Wenn ich das nochmal so lese, dann klingt das wirklich so, als würde ich 1.0d und 1.0f als identisch betrachten, dabei war das aber unabhängig von dem Satz davor. Ich meinte, daß die beiden 1.0 (also implizit 1.0d) bei Zuweisung und Vergleich ein identisches Bitmuster ergeben würden. Quasi zwei 1.0d an unterschiedlichen Stellen, aber nicht 1.0d und 1.0f gemischt.