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.
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.
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"
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.
@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?
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!
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
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 STM32F0Mathias 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.
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;
@ 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.
@ 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.
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.
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.
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.
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.
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.
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.
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.
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
longspannung_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?
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.
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?
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.
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.
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 AVRM. 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).
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?
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?
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: