Forum: Compiler & IDEs float auf Gleichheit testen erlaubt?


von Peter D. (peda)


Lesenswert?

Ich will ein float auf Gleichheit mit dem Initialisierungswert testen.
In AVR-GCC funktioniert es, aber ist das immer so oder ist das Undefined 
behavior?

Hier mal ein Beispiel:
1
float x = 1.0;
2
// ... some code
3
  if( x == 1.0 ){
4
    // ... some code
5
  }

von B. S. (bestucki)


Lesenswert?

Das Problem ist, dass 1.0 eine Konstante vom Typ double ist. Diese wird 
bei der Zuweisung nach x zu float konvertiert. Beim Vergleich auf 
Gleichheit wird x wieder zu double konvertiert, dabei kann aber die bei 
der Konvertierung zu float verlorene Genauigkeit nicht zurückgewonnen 
werden. Nutze 1.0f und der Vergleich wird klappen.

Siehe auch hier:
http://stackoverflow.com/a/9850710

von Adib (Gast)


Lesenswert?

Hallo Peter,
Was passiert, wenn der Wert nicht als float darstellbar ist?
Evtl. Runden oder mit einem epsilon testen.
Ist zwar keine Antwort auf deine Frage, ...

Adib.

von Wilhelm M. (wimalopaan)


Lesenswert?

Peter D. schrieb:
> Ich will ein float auf Gleichheit mit dem Initialisierungswert testen.
> In AVR-GCC funktioniert es, aber ist das immer so oder ist das Undefined
> behavior?
>
> Hier mal ein Beispiel:
>
1
> float x = 1.0;
2
> // ... some code
3
>   if( x == 1.0 ){
4
>     // ... some code
5
>   }
6
>

Das ist eine lange Geschichte:

https://randomascii.wordpress.com/2012/06/26/doubles-are-not-floats-so-dont-compare-them/

In diesem Beispiel ist in x (float) der gewandelte Wert von 1.0 
(double). In (x == 1.0) wird x wieder in ein double impilizit gewandelt. 
Solange bei der ersten Wandlung kein Fehlrepräsentation stattgefunden 
hat, ergibt der ==-Operator "true".

Wird aber im ersten "some code" etwa berechnet, dann sollte man einen 
epsilon-Vergleich machen.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter D. schrieb:
> ist das Undefined behavior?

Das auf keinen Fall.

von Peter D. (peda)


Lesenswert?

D.h. beim AVR-GCC funktioniert es, weil dort float und double identisch 
sind.
Mit 1.0f wäre es dann Compiler unabhängig.

von Rolf M. (rmagnus)


Lesenswert?

Be S. schrieb:
> Das Problem ist, dass 1.0 eine Konstante vom Typ double ist. Diese wird
> bei der Zuweisung nach x zu float konvertiert. Beim Vergleich auf
> Gleichheit wird x wieder zu double konvertiert, dabei kann aber die bei
> der Konvertierung zu float verlorene Genauigkeit nicht zurückgewonnen
> werden.

Naja, was soll bei 1.0 an Genauigkeit verloren gehen? Im verlinkten 
Beitrag wird 0.7 verwendet, wo das von dir gesagte gilt. Aber 1.0 ist in 
float und double exakt darstellbar, so dass bei der Konvertierung nichts 
verloren geht. Man muss sich aber bewusst sein, dass das nur für 
bestimmte Werte gilt und nicht allgemeingültig ist.

von PittyJ (Gast)


Lesenswert?

Ich würde == Tests mit Floats immer vermeiden.

nimm einfach mal
x = 0.4 + 0.6;
Das sollte ja x == 1.0 ergeben.

Aber ist das wirklich 1.0? Es könnte auch 0.99999999 sein. Oder 
1.00000001

Ich verzeichte auf solche Vergleiche und bin damit immer gut gefahren.

von Markus F. (mfro)


Lesenswert?

Bei Fließkommazahlen (egal ob float oder double) kommt es in jeder 
nicht-trivialen Anwendung zu Rundungs- oder Darstellungsfehlern. Ein 
simpler Vergleich auf "absolute" Gleichheit wird also 
höchstwahrscheinlich öfter fehlschlagen, als dir lieb ist.

Besser so:
1
#define EPSILON 0.0001
2
3
float val1, val2;
4
5
...
6
7
8
if (abs(val1 - val2) < EPSILON)
9
{
10
    /*
11
     * "so gut wie"-gleich
12
     */
13
    ...
14
}
15
else
16
{
17
    /*
18
     * unterschiedlich
19
     */
20
}

Der Wert von EPSILON ist abhängig davon, was Du bei deiner Anwendung 
noch als "gleich genug" betrachten möchtest.

: Bearbeitet durch User
von Wilhelm M. (wimalopaan)


Lesenswert?

Keine absoluten Epsilonvergleiche (s.a. das Paper was ich oben schon 
verlinkt habe).

Hier nochmal die kurze Version:

http://floating-point-gui.de/errors/comparison/

Oder hier:

http://en.cppreference.com/w/cpp/types/numeric_limits/epsilon

von hp-freund (Gast)


Lesenswert?

Früher, als ich noch bei der Vermessung war, ohne Internet und ohne 
fremde Hilfe, habe ich einfach die Zahlen in Zeichenketten verwandelt 
und diese verglichen. Damit lässt sich leicht die Genauigkeit festlegen.
Hat immer funktioniert.
Programmiert wurde in Basic und Turbopascal.

von Peter D. (peda)


Lesenswert?

Markus F. schrieb:
> Der Wert von EPSILON ist abhängig davon, was Du bei deiner Anwendung
> noch als "gleich genug" betrachten möchtest.

Nö, sondern vom Vergleichswert.
Es ist ein Unterschied, ob ich kV (1e3) oder nA (1e-9) vergleichen will.
Man müßte also auf eine prozentuale Abweichung vergleichen und das artet 
in Schreibarbeit aus, d.h. wird unleserlich:
1
#define INIT_VAL 1.0
2
#define TOLERANCE 0.1 // +/-0.1%
3
  if((x < (INIT_VAL * (1.0 + TOLERANCE / 100.0)))
4
    && (x > (INIT_VAL * (1.0 - TOLERANCE / 100.0)))){

Es handelt sich konkret um einen Skalierungsfaktor, d.h. die Variable 
wird nicht berechnet, sondern nur verschieden gesetzt.

von Markus F. (mfro)


Lesenswert?

Peter D. schrieb:
> Nö, sondern vom Vergleichswert.

ich ging davon aus, daß der TE eine einfache Vergleichsmöglichkeit in 
seiner Anwendung haben wollte und keine allgemeingültige, mathematisch 
korrekte (für viele praktische Anwendungsfälle aber "overengineerte") 
Library schreiben möchte.

Vielleicht hab' ich mich ja auch getäuscht.

von Eric B. (beric)


Lesenswert?

Peter D. schrieb:
> if((x < (INIT_VAL * (1.0 + TOLERANCE / 100.0)))
>     && (x > (INIT_VAL * (1.0 - TOLERANCE / 100.0)))){

(x < BLAH) && (x > BLAH) ? Das wird auf jedem Fall immer FALSE liefern 
:-)

von Amateur (Gast)


Lesenswert?

Natürlich kann man float-Zahlen auf Gleichheit testen!

...aber

1. Die interne Darstellung beinhaltet ein paar Pferdefüße.
2. Wird nicht gerade - unsinnigerweise - mit Ganzzahlen gearbeitet, so 
ist
   es fast unmöglich, dass irgendwo z.B. 1 herauskommt.

Der gute, alte Bleistift:

1 / 3 = Oops;

gefolgt von:

if ( ( Oops * 3 ) == 1 )

schlägt fast immer fehl:-)

Deshalb sollte man möglichst immer einen Bereich abfragen. Auch die 
Größe des Bereiches ist oft einen zweiten Blick wert.

Noch was zu 1.
Viele, für das "Auge" gerade aussehende Zahlen, sind in der float- bzw. 
double-Darstellung, krumm und schief.

von Sheeva P. (sheevaplug)


Lesenswert?

Eric B. schrieb:
> Peter D. schrieb:
>> if((x < (INIT_VAL * (1.0 + TOLERANCE / 100.0)))
>>     && (x > (INIT_VAL * (1.0 - TOLERANCE / 100.0)))){
>
> (x < BLAH) && (x > BLAH) ? Das wird auf jedem Fall immer FALSE liefern
> :-)

1.0 + TOLERANCE wird für jeden Fall, in dem TOLERANCE != 0 ist, auf 
jeden Fall einen anderen Wert liefern als 1.0 - TOLERANCE. ;-)

von guest (Gast)


Lesenswert?

Eric B. schrieb:
>> if((x < (INIT_VAL * (1.0 + TOLERANCE / 100.0)))
>>     && (x > (INIT_VAL * (1.0 - TOLERANCE / 100.0)))){
>
> (x < BLAH) && (x > BLAH) ? Das wird auf jedem Fall immer FALSE liefern
> :-)

schon richtig nur steht hier:

(x < BLAH) && (x > BLUB)

von Falk B. (falk)


Lesenswert?

@ Amateur (Gast)

>Viele, für das "Auge" gerade aussehende Zahlen, sind in der float- bzw.
>double-Darstellung, krumm und schief.

Was eben daran liegt, daß "das Auge" meist mit Dezimalzahlen zu tun hat, 
floats aber binär zur Basis 2 kodiert werden.

von ;-) (Gast)


Lesenswert?

PittyJ schrieb:
> Aber ist das wirklich 1.0? Es könnte auch 0.99999999 sein. Oder
> 1.00000001

Beim GCC bestimmt nicht. Float liefert günstigstenfalls 7 Stellen.

von Lutz H. (luhe)


Lesenswert?

float x = 1.0;
float y = 1.0;
// ... some code
  if( x == y ){
    // ... some code
  }

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

PittyJ schrieb:
> Ich würde == Tests mit Floats immer vermeiden.

Mit einer Ausnahme: der Wert 0.0, wenn er durch Zuweisung oder
Initialisierung (auch impliziter) entstand (also nicht durch
eine Berechnung) ist garantiert immer exakt darstellbar und kann
daher auch auf Gleichheit getestet werden.

Die 1.0 ist in den heutzutage üblichen Zahlendarstellungen auch
exakt darstellbar, aber das ist m. E. nicht garantiert.

von Joachim B. (jar)


Lesenswert?

wieso überhaupt auf Gleichheit testen?

Gerade bei float müsste sich doch ein Wertebereich besser machen

>99,999% && < 100,001%

wir wissen doch das es nie einschliesslich bis auf die letzte 
Dezimalstelle gleich wird, oder man muss gleich Ganzzahlrechnung machen.

von Mikro 7. (mikro77)


Lesenswert?

Peter D. schrieb:
> Ich will ein float auf Gleichheit mit dem Initialisierungswert testen.

Edit:

Falls man Angst vor dem Literal hat und unterschiedliche Compiler 
verwendet, dann kann man...
1
extern float init_x ;
2
// ...
3
float init_x = 1.0 ;
4
// ...
5
float x = init_x ;
6
// ... some code
7
  if( x == init_x ){
8
    // ... some code
9
  }
...machen. Wobei "1.0" wie im Eingangsposting schon steht nur ein 
Beispiel ist.

: Bearbeitet durch User
von Klaus (Gast)


Lesenswert?

Man könnte die Werte auch so wählen, dass keine Nachkommastellen 
vorkommen. Die 1.0 mit 10 (oder 100, je nach gewünschter Genauigkeit) 
multiplizieren und dann mit int-Werten arbeiten. Vor der Ausgabe kann 
man dann ja einen cast zu double machen und durch 10.0 teilen.

von Lutz H. (luhe)


Lesenswert?

Zu was gibt es Wahrheitswerte true , false?

Wenn einer eine Null ist, ist er es in integer, long oder float.

Es ist möglich, allesmögliche auf Gleichheit zu prüfen. Es ist eben 
gleich, wenn es gleich ist. Aber es ist nicht einerlei.

von Eric B. (beric)


Lesenswert?

guest schrieb:
> Eric B. schrieb:
>>> if((x < (INIT_VAL * (1.0 + TOLERANCE / 100.0)))
>>>     && (x > (INIT_VAL * (1.0 - TOLERANCE / 100.0)))){
>>
>> (x < BLAH) && (x > BLAH) ? Das wird auf jedem Fall immer FALSE liefern
>> :-)
>
> schon richtig nur steht hier:
>
> (x < BLAH) && (x > BLUB)

Ah, richtig. Ich hatte da wohl 'ne halbe Tomate auf den Augen :-S

von W.S. (Gast)


Lesenswert?

Peter D. schrieb:
> Ich will ein float auf Gleichheit mit dem Initialisierungswert testen.

In diesem speziellen Fall kannst du das brutal binär machen, also alle 4 
Bytes auf Gleichheit testen. Braucht keinerlei echte GK-Arithmetik, gilt 
aber nur für den Fall, daß du tatsächlich einen float im RAM mit 
dessen eigenem Ursprung im Flash oder EEPROM vergleichst, also exakt mit 
den 4 Bytes, die du zuvor in den RAM geladen hast.

W.S.

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.