Forum: Mikrocontroller und Digitale Elektronik if-Bedingung Problem


von zigner (Gast)


Lesenswert?

Hallo,
1
float spannung = 0.4125;  
2
3
if (spannung<0.4125){      
4
  value = 0x0;
5
}
6
if (spannung>=0.4125){      
7
  value = 0x1;
8
}

Bei diesem Programm nimmt mein value den Wert 0x00 an.
Aber eigentlich sollte dieser doch 0x1 sein, da spannung = 0.4125 ist?
Wenn ich den Wert spannung auf 0.4126 setze, dann nimmt value erst den 
Wert 0x1 an.

von 99+ (Gast)


Lesenswert?

Floats haben (fast) nie einen genauen dezimalen Wert aufgrund von 
Rundung.

von Dirk B. (dirkb2)


Lesenswert?

Das Literal 0.4125 ist auch ein double.

Darum wird beim Vergleich erst der Inhalt von spannung in double 
gewandelt.

Es sei denn, du programmierst auf einem AVR, da ist double auch nur ein 
float.

Das zweite if sollte ein else sein.

von Mathias (Gast)


Lesenswert?

Willkommen in der Welt der Fließkommazahlen.
Wenn ich mich recht entsinne, ist die Konstante 0.4125 ein double und 
"spannung" eben nicht. Weil es mit floats quasi nie exakte Darstellungen 
der Zahlen gibt, ist der Double Wert zufällig etwas kleiner als der 
Single.
Wenn dir der Unterschied wichtig ist, dann schreib die konstanten mit 
"f" am Ende, also "0.4125f"

von Marco H. (damarco)


Lesenswert?

das Problem ist das float und der Vergleich mit 0.4125. Intern wird dies 
in ein anderen Datentype umgewandelt. Das Ergebnis ist dann ein anderes 
wie erwartet wird.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

1
float spannung = 0.4125;  
2
3
value = 0x01;
4
if (spannung<0.4125){      
5
  value = 0x0;
6
}

 Mit Float und >= hatte ich immer Probleme.

von Falk B. (falk)


Lesenswert?

@zigner (Gast)

>float spannung = 0.4125;

Das kann man zwar so hinschreiben, aber der Compiler rechnet das um. Und 
da Fließkommazahlen zur Basis 2 gespeichert werden, gibt es da fast 
immer Rundungsfehler. Wenn du dir die Zahl im Debugger anschaust, wirst 
du sehen, daß sie nicht 0.4125 ist, sondern einen Tick daneben. Das kann 
man auch mit einem Online-Rechner sehen.

https://www.h-schmidt.net/FloatConverter/IEEE754de.html

>if (spannung<0.4125){
>  value = 0x0;
>}
>if (spannung>=0.4125){
>  value = 0x1;
>}

>Bei diesem Programm nimmt mein value den Wert 0x00 an.

Hmm, das ist komisch, denn auch der Vergleich mit der Konstanten sollte 
den GLEICHEN Rundungsfehler erzeugen und damit sollte der Vergleich 
negativ ausfallen, denn gleich ist nicht kleiner. Möglicherweise gibt es 
hier aber verschiedene "Compilerwege" und damit verschiedene Rundungen. 
Merkwürdig.

Mit welchem Compiler hast du das getestet?

von Marco H. (damarco)


Lesenswert?

Ist ja klar weil gleich nicht gleich sein kann bei einer Fließkommazahl.

 5/2,6=Ergebnis ist nicht gleich wie Ergebnis*2.6

von Mampf F. (mampf) Benutzerseite


Lesenswert?

zigner schrieb:
> Bei diesem Programm nimmt mein value den Wert 0x00 an.
> Aber eigentlich sollte dieser doch 0x1 sein, da spannung = 0.4125 ist?
> Wenn ich den Wert spannung auf 0.4126 setze, dann nimmt value erst den
> Wert 0x1 an.

Floats und Double sind eine ganz fiese Sache ... Wenn möglich darauf 
verzichten und mit Festpunkt-Werten arbeiten!

Da gibts dann echt so tolle Sachen wie:

for (float f=0;f<1.0f;f+=0.1f) {
  if (f==0.4f) {
    ...
  }
}

Da kann es problemlos passieren, dass f niemals 0.4f sein wird!

von Dirk B. (dirkb2)


Lesenswert?

ES gibt heutzutage eigentlich keinen Grund mehr float zu nehmen.
Es sei denn, man hat wirklich große Arrays damit (oder rechnet auf der 
GPU).

von A. S. (Gast)


Lesenswert?

Falk B. schrieb:
> Hmm, das ist komisch, denn auch der Vergleich mit der Konstanten sollte
> den GLEICHEN Rundungsfehler erzeugen

nein, wieso? Stell dir vor, die Mantisse ist (1).01010101010101

Bei der Rundung zu Float wird das auf 10 Stellen begrenzt, dann ist die 
Frage (bei gleichem Exponenten):

(1).010101010000 < (1).010101010101

von zigner (Gast)


Lesenswert?

Dirk B. schrieb:
> Das Literal 0.4125 ist auch ein double.

Woran erkennt man, dass 0.4125 in der if-Bedingung ein double ist und 
kein float wie bei Spannung?

Dirk B. schrieb:
> Das zweite if sollte ein else sein.

Ich habe nur einen Ausschnitt eingefügt. Danach kommen noch weitere 
Bedingungen.

if (spannung>=0.4125*2){
  value = 0x3;
}
...

Falk B. schrieb:
> Mit welchem Compiler hast du das getestet?

Keil µVision und als µC einen STM32F0

Mathias schrieb:
> Wenn dir der Unterschied wichtig ist, dann schreib die konstanten mit
> "f" am Ende, also "0.4125f"

Super, das funktioniert. Verstehe ich das richtig?
Das f am Ende ist ein Typecast und wandelt den double in ein float um. 
Da nun die Variable spannung und der Wert 0.4125 in der if-Bedingung 
jeweils ein float sind, handelt es sich hierbei um exakt die gleichen 
Werte. Folglich stimmt die if-Bedingung.

von zigner (Gast)


Lesenswert?

Dirk B. schrieb:
> ES gibt heutzutage eigentlich keinen Grund mehr float zu nehmen.

Wie kann ich den Wert eines ADCs ohne float speichern?

int eingelesenerWert;
float spannung;

spannung = (eingelesenerWert/4095.0)*3.3;

von Falk B. (falk)


Lesenswert?

@ zigner (Gast)

>> Wenn dir der Unterschied wichtig ist, dann schreib die konstanten mit
>> "f" am Ende, also "0.4125f"

>Super, das funktioniert. Verstehe ich das richtig?
>Das f am Ende ist ein Typecast und wandelt den double in ein float um.
>Da nun die Variable spannung und der Wert 0.4125 in der if-Bedingung
>jeweils ein float sind, handelt es sich hierbei um exakt die gleichen
>Werte. Folglich stimmt die if-Bedingung.

Ja. Auch bei Float gibt es ein "unsichtbares" Standardformat, so wie int 
bei Festkomma. Und ein 0.4125 ist nach Umrechnung in float (32 Bit) 
geringfügig was anderes als ein double (64 Bit), damit geht der 
Vergleich auf Gleichheit schief. Knapp daneben ist auch vorbei ;-)

Darum prüft man bei Fließkomma so gut wie nie auf Gleichheit sondern nur 
auf minimale Abweichung innerhalb einer Fehlergrenze.

1
#define EPSILON 0.0001
2
3
float x, y;
4
5
if ( fabs(x-y) < EPSILON ) {
6
  // x und y sind nahezu gleich
7
}

von Falk B. (falk)


Lesenswert?

@ zigner (Gast)

>> ES gibt heutzutage eigentlich keinen Grund mehr float zu nehmen.

Diese Aussage ist schlicht Unsinn.

>Wie kann ich den Wert eines ADCs ohne float speichern?

Liefert denn der ADC Float-Werte? Eher nicht.

>int eingelesenerWert;
>float spannung;

>spannung = (eingelesenerWert/4095.0)*3.3;

AHA! Du willst die Ganzzahl des ADCs in eine Kommazahl umwandeln. Das 
macht man meist mit Festkommaarithmetik.

von Manfred (Gast)


Lesenswert?

99+ schrieb:
> Floats haben (fast) nie einen genauen dezimalen Wert aufgrund von
> Rundung.
Unbenommen der theoretischen Grundlagen würde ich auf
< 0.4125 oder > 0.4125 abfragen, vermutlich sogar 0.4124 und 0.4126, 
damit mein System nicht bei jedem halben Bit durch die Gegend hampelt.

Klemme mal ein Drehpoti und eine LED an einen Arduino, ohne Hysterese 
gibt es einen Bereich, wo die LED niemals stabil leuchtet.

von Dirk B. (dirkb2)


Lesenswert?

zigner schrieb:
> Dirk B. schrieb:
>> Das Literal 0.4125 ist auch ein double.
>
> Woran erkennt man, dass 0.4125 in der if-Bedingung ein double ist und
> kein float wie bei Spannung?

Das ist per Definition so.
Wie du daraus ein float machst, weißt du ja mittlerweile.

von Mampf F. (mampf) Benutzerseite


Lesenswert?

zigner schrieb:
> Wie kann ich den Wert eines ADCs ohne float speichern?

Der kommt ja nicht als float aus dem ADC ...

> int eingelesenerWert;
> float spannung;
>
> spannung = (eingelesenerWert/4095.0)*3.3;

Dann mach halt sowas:

0.4125 entspricht wohl einem ADC-Wert von 512 bei dir.

Rechne es als in int mit einer Granularität von 1/10-mV um. Du weißt, 
dass der ADC-Wert 512 auf 4125 umgerechnet werden soll. Dann simpel:

int spannung = (int) ((float) eingelesenerWert * 8.056640625f)

Dann wird aus deinem 0.4125 simpel 4125.

Bei Festpunkt würde man das "virtuelle Komma" um 2er-Potenzen 
verschieben, aber da du eh float-Multiplikation hast, kannst du es auch 
gleich so machen.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Dirk B. schrieb:
> ES gibt heutzutage eigentlich keinen Grund mehr float zu nehmen.

Es gibt heutzutage eigentlich keinen Grund mehr, float nicht zu 
nehmen.
Die MC-Entwicklung ist ja nicht in den 80-ern stehen geblieben, d.h. die 
MCs haben deutlich mehr Flash, RAM und Taktfrequenz.
Und auch die Compilerbauer schreiben ziemlich gute float Bibliotheken.

Ich hab kürzlich erst auf nem AVR einen PID-Regler von int_32 auf float 
umgestellt, weil durch Rundungsfehler die Regelung instabil wurde.

von Mathias (Gast)


Lesenswert?

Manfred schrieb:
> Unbenommen der theoretischen Grundlagen würde ich auf
> < 0.4125 oder > 0.4125 abfragen, vermutlich sogar 0.4124 und 0.4126,
> damit mein System nicht bei jedem halben Bit durch die Gegend hampelt.

Das ist natürlich großer Quatsch. Statt einer Stelle wo es hakt, hat man 
dann zwei. Bei <0.4124 springt es zwischen undefined und 0, bei >0.4126 
zwischen undefined und 1.

Klar, man kann das alles in Int lassen und die rohen ADC Werte nutzen, 
dann wirds aber natürlich schlechter lesbar und wartbar. Dafür kann man 
sich die Floats evtl. komplett sparen und hat keine Rundungsfehler. Wenn 
genug Platz und Speed im uC ist, würde ich dennoch mit Floats arbeiten.

Auch wenn es schon 3x geschrieben wurde, weise ich auch nochmal drauf 
hin: Erwarte nicht, das jemals eine echte Gleichheit von zwei Float 
werten auftritt. In deinem Beispiel kannst du das erzwingen, indem du 
echt zweimal den gleichen Wert hinschreibst, aber wenn ein Wert 
errechnet und einer im Code steht, dann sind die ziemlich sicher nicht 
gleich. Mach dann das, was Falk Brunner geschrieben hat.

von Rolf M. (rmagnus)


Lesenswert?

Falk B. schrieb:
>>Bei diesem Programm nimmt mein value den Wert 0x00 an.
>
> Hmm, das ist komisch, denn auch der Vergleich mit der Konstanten sollte
> den GLEICHEN Rundungsfehler erzeugen und damit sollte der Vergleich
> negativ ausfallen, denn gleich ist nicht kleiner.

Nein, denn auf der rechten Seite steht direkt ein double. Links steht 
dagegen ein Wert, der erst von double nach float und dann zurück 
konvertiert wurde.

zigner schrieb:
> Woran erkennt man, dass 0.4125 in der if-Bedingung ein double ist und
> kein float wie bei Spannung?

0.4125 ist so hingeschrieben immer ein double. Floating-Point-Konstanten 
sind grunsätzlich double, wenn man nicht einen Suffix dahinter schreibt, 
der was anderes sagt. Und spannung ist ein float, weil du es so 
definiert hast.

zigner schrieb:
> float spannung = 0.4125;

Hier ist 0.4125 ein double. Der wird konvertiert in einen float, und das 
Ergebnis dieser Konvertiertung wird dann nach spannung geschrieben.

> Mathias schrieb:
>> Wenn dir der Unterschied wichtig ist, dann schreib die konstanten mit
>> "f" am Ende, also "0.4125f"
>
> Super, das funktioniert. Verstehe ich das richtig?
> Das f am Ende ist ein Typecast und wandelt den double in ein float um.

Nicht ganz. Das f sagt, dass es gar nicht erst ein double sein soll, 
sondern ein float. Gewandelt wird da nix mehr. Typecast wäre 
(float)0.4125

> Da nun die Variable spannung und der Wert 0.4125 in der if-Bedingung
> jeweils ein float sind, handelt es sich hierbei um exakt die gleichen
> Werte. Folglich stimmt die if-Bedingung.

Ja.

von Peter D. (peda)


Lesenswert?

Compiler arbeiten nach dem Prinzip der maximalen Faulheit. Wenn die 
einen konstanten Ausdruck sehen, rechnen sie ihn einfach aus, ehe sie 
erst umständlich Laufzeitkode dafür generieren.

Man kann den ADC-Wert in float umrechnen und dann vergleichen.
Man kann aber auch die Vergleiswerte umrechnen lassen und dann als int 
vergleichen. Bequemer Weise schreibt man die Umrechnungsformel als 
Macro, dann ist es gleich gut lesbar, wie float:
1
#define VOLT2ADC(x) (uint16_t)(x * 123.45) // irgendein Gain
2
3
if( ADCW > VOLT2ADC( 4.0 ))
4
  foo();
5
if( ADCW < VOLT2ADC( 0.1 ))
6
  faa();

Solange man nur vergleichen und nicht rechnen muß, kann man das float 
einfach in die Compilezeit auslagern.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

zigner schrieb:
> Wie kann ich den Wert eines ADCs ohne float speichern?
> int eingelesenerWert;
> float spannung;
> spannung = (eingelesenerWert/4095.0)*3.3;
Rechne in mV oder in µv und nimm einen long Wert:
1
long spannung_mV = (eingelesenerWert*3300)/4096;
Der Compiler freut sich, weil das unglaublich einfach zu berechnen ist. 
Du freust dich, weil das Programm schnell läuft und weil es wenig Platz 
braucht.

> spannung = (eingelesenerWert/4095.0)*3.3;
Diese Formel ist zwar hübsch aber grundlegend falsch. Die Verwendung 
von 4095 statt der korrekten 4096 wird auch durch allseitig häufigste 
Wiederholung nicht richtig.
Eine Frage: du machst das nur, damit du auch mal 3.3 auf der Anzeige 
siehst, richtig?

Peter D. schrieb:
> Ich hab kürzlich erst auf nem AVR einen PID-Regler von int_32 auf float
> umgestellt, weil durch Rundungsfehler die Regelung instabil wurde.
Wie hast du das mit den lediglich 6 signifikanten Dezimalstellen eines 
Float besser hinbekommen?

von Peter D. (peda)


Lesenswert?

Lothar M. schrieb:
> Wie hast du das mit den lediglich 6 signifikanten Dezimalstellen eines
> Float besser hinbekommen?

Weil float eine viel höhere Dynamik (1.5E-45 .. 3.4E38) hat und darauf 
kommt es bei der Regelung an.
Genauer als die 16Bit des ADC und DAC geht eh nicht.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Peter D. schrieb:
> Weil float eine viel höhere Dynamik (1.5E-45 .. 3.4E38) hat und darauf
> kommt es bei der Regelung an.
Es war also eher ein Unter-Überlauf-Problem als eine Rundungsgeschichte?

von Dirk B. (dirkb2)


Lesenswert?

Peter D. schrieb:
> Es gibt heutzutage eigentlich keinen Grund mehr, float nicht zu
> nehmen.

Warum noch float. double ist doch besser.

Mit den Coprozessoren ist es auch nicht langsamer.
double braucht mehr Speicher, darum die Einschränkung mit den großen 
Arrays.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Dirk B. schrieb:
> Warum noch float. double ist doch besser.
> Mit den Coprozessoren ist es auch nicht langsamer.
Ähem, Peter D. schrieb:
>>> auf nem AVR

von M. K. (sylaina)


Lesenswert?

Dirk B. schrieb:
> Warum noch float. double ist doch besser.

Weil für den AVR auch ein Double ein Float ist ;)

von Peter D. (peda)


Lesenswert?

Lothar M. schrieb:
> Es war also eher ein Unter-Überlauf-Problem als eine Rundungsgeschichte?

Es war ein Rundungsfehler, die I-Summe muß kleiner 1 DAC-Schritt 
addieren können. Natürlich könnte man das auch durch Hin- und 
Herschieberei erreichen, wird dann aber schnell fehlerträchtig.

von Dirk B. (dirkb2)


Lesenswert?

Lothar M. schrieb:
> Dirk B. schrieb:
>> Warum noch float. double ist doch besser.
>> Mit den Coprozessoren ist es auch nicht langsamer.
> Ähem, Peter D. schrieb:
>>>> auf nem AVR

M. K. schrieb:
> Dirk B. schrieb:
>> Warum noch float. double ist doch besser.
>
> Weil für den AVR auch ein Double ein Float ist ;)

Im ursprünglen Post ging es um einen STM32F0 (kam erst später raus)

Es ging mir nie gegen Fließkomma-Typen, nur gegen float. Denn double ist 
besser.

Auch auf einem AVR spricht nichts dagegen double zu nehmen (auch wenn es 
nur ein float ist).

von M. K. (sylaina)


Lesenswert?

Dirk B. schrieb:
> Auch auf einem AVR spricht nichts dagegen double zu nehmen (auch wenn es
> nur ein float ist).

Doch, man könnte beim Lesen des Quellcodes später mal tatsächlich 
vermuten, dass es sich um einen Double handelt obwohl es in Wirklichkeit 
ein Float ist. Du unterscheidest doch auch zwischen einer Kuchengabel 
und einer Heugabel...oder?

von S. R. (svenska)


Lesenswert?

Dirk B. schrieb:
> Auch auf einem AVR spricht nichts dagegen double zu nehmen (auch wenn es
> nur ein float ist).

Versprichst du deinen Kindern auch immer eine ganze Tafel Schokolade und 
gibst ihnen dann nur ein Stück?

von Witkatz :. (wit)


Lesenswert?

Marco H. schrieb:
> Das Ergebnis ist dann ein anderes
> wie erwartet wird.

Nein. Für den TO kommt es eben nicht wie erwartet.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Rolf M. schrieb:
>> Mathias schrieb:
>>> Wenn dir der Unterschied wichtig ist, dann schreib die konstanten mit
>>> "f" am Ende, also "0.4125f"
>>
>> Super, das funktioniert. Verstehe ich das richtig?
>> Das f am Ende ist ein Typecast und wandelt den double in ein float um.
>
> Nicht ganz. Das f sagt, dass es gar nicht erst ein double sein soll,
> sondern ein float. Gewandelt wird da nix mehr. Typecast wäre
> (float)0.4125

Hier ist ein Beispiel, das den Unterschied verdeutlicht:

1
#include <stdio.h>
2
3
#define TEST(e) printf("%-20s -> %s\n", #e, (e) ? "true" : "false")
4
5
int main(void) {
6
  float  f = 0.4125f;
7
  double d = 0.4125;
8
9
  TEST(f == 0.4125f);
10
  TEST(d == 0.4125);
11
  TEST(f == (float)0.4125f);
12
  TEST(d == (double)0.4125);
13
14
  return 0;
15
}

Kompiliert und ausgeführt auf meiner ollen Pentium-4-Gurke unter Linux
mit

1
gcc -std=c11 -o fdtest fdtest.c
2
fdtest

wird folgendes ausgegeben:

1
f == 0.4125f         -> false
2
d == 0.4125          -> false
3
f == (float)0.4125f  -> true
4
d == (double)0.4125  -> true

Das Ergebnis der ersten beiden Vergleiche ist plattformabhängig.

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.