if-Bedingung Problem

Gast #4904655
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.
Gast #4904673
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"
#4904679
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?
Persönliche Seite #4904698
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!
Gast #4904706
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
Gast #4904711
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.
#4904720
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
}
#4904723
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.
Gast #4904749
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.
Persönliche Seite #4904963
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.
#4904972
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.
Gast #4904980
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.
#4904983
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.
#4904995
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.
Moderator (Firma: Titel) Persönliche Seite #4905008
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?
#4905215
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).
Moderator #4905473
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.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren