Hallo,
ich habe ein Problem (vermutlich ein Verständnisproblem) zu folgendem
Thema,
ich möchte auf einem pic16f887 mit dem xc8 Compiler eine Float Zahl in
einen string umwandeln um ihn dann an einem LCD anzeigen zu können.
im wesentlichen sieht der Code wie folgt aus:
////////////// CODE ////////////////
float wert = 125.735;
char buffer[20];
sprintf(buffer,"%.4f",wert);
lcd_printWord(buffer);
//////////// ENDE //////////////////
lcd_printWord ist eine eigene funktion die auch funktioniert, sie gibt
einfach nur bis zum Erscheinen des NULL Zeichens den String ans Display
aus.
Das was ich nun nicht verstehe ist: In dem string "buffer" der die durch
sprintf gewandelte float zahl aufnehmen soll wird die folgende Zahl
gespeichert: 125.7343
Das gleiche "Problem" habe ich ebenfalls wenn ich die ftoa funktion
nutze.
Was ich mir ebenfalls nicht erklären kann ist: Wenn ich in der sprintf
funktion die Nachkommastellen auf 3 ändere, also "%.3f" schreibe, wird
folgende Zahl in den string gespeichert: 125.734
Ich verstehe hier absolut nicht woher dieser Fehler in der
Nachkommastelle kommt, kann mir bitte jemand weiterhelfen?
Mein Gedanke war das eventuell der pic16f887 durch seine interne
architektur diesen Zahlenwert nicht verarbeiten kann, aber vermutlich
ist das noch keine Zahlengrößenordnung die ihn vor probleme stellt -
oder doch??
Ich wäre euch sehr dankbar für hilfe zum finden der Ursache und einer
Lösung.
Vielen Dank, tobi
Tobias W. schrieb:>> Das was ich nun nicht verstehe ist: In dem string "buffer" der die durch> sprintf gewandelte float zahl aufnehmen soll wird die folgende Zahl> gespeichert: 125.7343
Ein float hat so um die 5 bis 6 signifikante Stellen. Alles dahinter
könnte man als gelogen bezeichnen (tatsächlich reicht einfach di
Auflösung deines float nicht mehr aus um die 3. Nachkommastelle noch
exakt speichern zu können.
Achtung: signifikante Stellen! Nicht Nachkommastellen!
Deine Zahl 125.irgendwas verbraucht bereits 3 signifikante Stellen vor
dem Komma!
> Das gleiche "Problem" habe ich ebenfalls wenn ich die ftoa funktion> nutze.
logisch. Das Problem besteht darin, dass ein float nun mal nicht genauer
ist.
> Was ich mir ebenfalls nicht erklären kann ist: Wenn ich in der sprintf> funktion die Nachkommastellen auf 3 ändere, also "%.3f" schreibe, wird> folgende Zahl in den string gespeichert: 125.734
Nö. WIrd sie nicht. Der float ist immer noch derselbe. Aber bei der
Ausgabe ist auf 3 Stellen nach dem Komma gerundet worden. Genau das hast
du ja angeordnet.
> Mein Gedanke war das eventuell der pic16f887 durch seine interne> architektur diesen Zahlenwert nicht verarbeiten kann, aber vermutlich> ist das noch keine Zahlengrößenordnung die ihn vor probleme stellt -> oder doch??
Dein Grundsatzproblem ist die Denkweise, dass Gleitkommaarithmetik genau
so funktioniert wie Zahlen mit Komma in der Mathematik. Dem ist nicht
so.
Man könnte das Problem in etwa so veranschaulichen.
Du darfst dir Zahlen notieren. Du kannst dir eine Zahl notieren, indem
du dir eine Mantisse und einen Exponent (jeweils) im Dezimalsystem
notierst. Der Einfachheit nehmen wir an, dass ein gedachtes Komma immer
hinter der ersten Ziffer eingefügt wird (dann brauchen wir das nicht
speichern). Du darfst dir aber in der Mantisse nicht mehr als 5 Ziffern
notieren. Zu mehr reicht der Speicherplatz, den ich dir zugestehe nicht
aus. d.h genau genommen musst du dir sogar immer genau 5 Ziffern in der
Mantisse notieren. Das macht aber nichts, denn wir wissen ja, das das
Komma immer an derselben Position sitzt und zwischen 2 und 2.0 und 2.00
usw. besteht ja numerisch kein Unterschied.
D.h. die Zahl 5.0 wirst du zb so aufschreiben
1
Mantisse Exponent
2
50000 0
das ist zu lesen als 5.0000 mal 10 hoch 0.
10 hoch 0 ist 1 also ist die Zahl 5.0000 mal 1, also 5 (die
Nachkommastellen, die nur noch 0 sind, schreiben wir normalerweise
nicht).
Wie notierst du dir 50?
Die Mantisse wird natürlich wieder 50000 sein. Und weil ja das Komma
hinter der ersten Ziffer festgenagelt ist, musst du die
Kommaverschiebung mit dem Exponenten machen. Um aus 5 die 50 zu machen,
musst du mit 10 multiplizieren. 10, das ist aber nichts anderes als 10
hoch 1. Also notierst du dir
1
Mantisse Exponent
2
50000 1
Wie notierst du dir 500?
Einfach. Um aus den 5 die 500 zu machen, musst du mit 100
multiplizieren. 100, das ist 10 hoch 2. Also
1
Mantisse Exponent
2
50000 2
soweit so gut.
Was ist mit 5.5?
Einfach. Das gewollte Komma ist ja bereits hinter der ersten Ziffer. Der
Exponent ist daher 0 und du schreibst
1
Mantisse Exponent
2
55000 0
Welche Zahl wäre
1
Mantisse Exponent
2
55000 2
na, ja. nimm die 5.5000 und multipliziere mit 10 hoch 2. 10 hoch 2, das
ist 100, also 5.5 mal 100 und du erhältst 550
Gut. Das kann man jetzt weiter treiben. Wie speicherst du 5.5678?
Einfach.
1
Mantisse Exponent
2
55678 0
Und wie würde sich 55.678 darstellen?
1
Mantisse Exponent
2
55678 1
Und 556.78?
1
Mantisse Exponent
2
55678 2
So weit, so (hoffentlich) klar. Mit dem Exponenten verschiebst du im
Prinzip einfach nur das Komma. Gespeichert ist es immer nach der ersten
Stelle, wenn deine Zahl größer als 10 ist, dann wird es eben durch einen
entsprechenden Exponenten wieder an diese Position verschoben.
Aber:
Wie speicherst du 55.6789?
Jetzt hast du ein Problem. Ich hab dir in der Mantisse nur 5 Ziffern
erlaubt. Um 55.6789 speichern zu können, müsstest du aber 6 Ziffern
anschreiben. Das erlaube ich dir aber nicht - wir haben den
Speicherplatz dafür nicht.
Also wirst du irgendeinen Tod sterben müssen. 55.6789 kannst du nicht
speichern. Du kannst 55.678 speichern
1
Mantisse Exponent
2
55678 1
oder du könntest auch 55.679 speichern (die gerundete Zahl zu 55.6789)
1
Mantisse Exponent
2
55679 1
aber 55.6789 geht nun mal nicht.
Und es spielt dabei jetzt auch keine Rolle, ob du 5.56789 speichern
solltest oder 55.56789 oder 556.789 oder 5567.89 oder 55678.9 oder .....
Du stehst jedesmal vor dem gleichen Dilemma: du hast nicht genug
Speicherstellen um die 6-ziffrige Mantisse speichern zu können. Das
Komma könntest du mit dem Exponenten problemlos so verschieben, dass du
letzten Endes wieder 5.56789 vor dir hast (mit dem entsprechenden
Exponenten). Aber diese 5.56789 kannst du nicht bis in die 5.
Nachkommastelle speichern. Dafür hast du den Speicher nicht.
Dein PIC steht vor genau dem gleichen Dilemma. Nur dass sich bei ihm
alles im Binärsystem abspielt und nicht im Dezimalsystem. Daher sind die
Effekte für dich etwas subtiler. Aber vom Prinzip her hat er exakt genau
das gleiche Problem: Wenn die Bits in der Mantisse aufgebraucht sind,
dann ist Schluss mit lustig.
(und ja. Für den Exponenten gibt es natürlich ebenfalls eine
Beschränkung, die ich der Einfachheit halber unter den Tisch hab fallen
lassen.)
vielen Dank für die schnellen und aussagekräftigen Infos.
@karl heinz: Vielen Dank für die Ausführliche Information
Karl H. schrieb:> logisch. Das Problem besteht darin, dass ein float nun mal nicht genauer> ist.
Hierzu ein Frage, ich habe das ganze auch mit dem Datentyp double
versucht und erhalte die gleiche "fehlerhafte" Zahl... gibt es eine
Datentyp der sich für diese Zahlengröße die ich abspeichern möchte
überhaupt einen geeigneten Datentypen oder komm ich da mit diesem Ansatz
nicht weiter?
Viele Grüße, tobias
Hallo Tobias,
genaueres findest Du im XC8 C Compiler User Guide (s. /docs im
Installationsverzeichnis) ab Seite 154.
Dort steht auch, daß "float" und "double" normalerweise beide im
gleichen Format gespeichert werden (Länge:24 Bit = 15 Bit Mantisse, 8
Bit Exponent, 1 Bit Vorzeichen). Mit einer Option kannst Du aber dem
Compiler z.B. sagen, daß er 32 Bits speichern soll, wahlweise auch
selektiv z.B. nur für double. Damit ist die Mantisse 25 Bits lang.
Grüße,
Thomas
Was ist das eigentlich für eine Zahl, dass du eine Auflösung von so um
die 20bit brauchst?
Weil die muss ja irgendwo herkommen, und damit hat sie eine endliche
Genauigkeit. Ist die Zahl wirlich so genau?
Generell würde ich immer nur soviele Stellen anzeigen, wie die echte
Genauigkeit ist. Digitale Anzeigen suggerieren sonst eine Genauigkeit,
die gar nicht existiert.
Was bringt z.B. Anzeige 20.776385°C (+- 2°C)?
Wenn die wirklich so genau sein muss, dann nimm richtig skalierte
integer-Werte. Damit gewinnst du bei 32 Bit bis zu 9Bit Auflösung - der
Exponent und das Vorzeichen entfallen. Das ist noch tragbar, vom Aufwand
her.
Oder du brauchst eine Lib, die mit 64 bit rechnen kann. (der arme
8-bit-PIC!)
Halb Offtopic:
Diese Sache hier:
1
sprintf(buffer,"%.4f",&wert);
Ist extrem lahm. Hier auf einem PIC24 dauert das etwa 170000 CPU-Zyklen,
wohingegen ein
1
sprintf(buffer,"%4d",&wert);
nur so ungefähr 900 benötigt.
Auch allgemein ist alles mit float nicht gerade schnell, vor allem auf
den 8-bittern.
Wenns du viel Float rechnen möchtest, nimm einen PIC32MZ, der hat eine
FPU.
hallo,
vielen Dank für die Infos.
WehOhWeh schrieb:> Was ist das eigentlich für eine Zahl, dass du eine Auflösung von so um> die 20bit brauchst?
Es geht mir hier nicht darum ein Ergebnis von irgendeiner Quelle (AD
Wandler,ect.) möglichst genau darzustellen.
Diese Zahl die ich da umwandeln möchte ist einfach nur eine float
Variable. Das ganze hat den Hintergrund das ich neu in der
Mikrocontroller Materie bin und von der Programmierung in Java und Co.
an PCs verwöhnt bin was das Arbeiten mit Flißkommazahlen angeht.
Denn das was hier neu für mich bei der Angelegenheit ist, ist das der
Pic wenn ich ihm die Aufgabe stelle z.B. zwei float Variablen zu
addieren, das "falsch" macht, weil ich hierbei nicht berücksichtigt habe
das der Speicherbereich die Zahl (z.B. 125.23456) gar nicht reicht um
die gesamte Zahl zu speichern?
Vielen Dank für die vielen Tipps und Hinweise. Eins noch ...
WehOhWeh schrieb:> Wenns du viel Float rechnen möchtest, nimm einen PIC32MZ, der hat eine> FPU.
Stellt dieser Pic dann auch noch weitere "Hürden" an mich in sachen
Programmierung, oder ist das ähnlich wie bei dem pic16f887 ? Also mit
MPLab und pickit3 möglich ?
Vielen Dank,Tobi
Tobias W. schrieb:> Denn das was hier neu für mich bei der Angelegenheit ist, ist das der> Pic wenn ich ihm die Aufgabe stelle z.B. zwei float Variablen zu> addieren, das "falsch" macht, weil ich hierbei nicht berücksichtigt habe> das der Speicherbereich die Zahl (z.B. 125.23456) gar nicht reicht um> die gesamte Zahl zu speichern?
Nein. Der PIC macht nichts falsch! Und mit dem Speicher hat das schon
gar nichts zu tun.
Das würde dir am PC unter Java auch passieren, außer du verwendest
doppelte Genauigkeit (64Bit). Das ist einfach nur eine Eigenschaft der
Gleitkommazahlen, sonst nichts.
>Stellt dieser Pic dann auch noch weitere "Hürden" an mich in sachen>Programmierung, oder ist das ähnlich wie bei dem pic16f887 ? Also mit>MPLab und pickit3 möglich ?
Nur insofern, als dass die deutlich komplexer sind als PIC16. Dafür um
Größenordnungen Leistungsstärker. Du brauchtst nur den XC32 compiler zu
installieren, das wars. Mit PICkit3 kannst du ganz normal programmieren
und debuggen.
Aber: der PIC32MZ wird dir in diesem Fall das gleiche Ergebnis bringen.
Wie auch ein Intel Cor2Duo oder ATMEGA oder STM32. 32Bit Float bleibt
32Bit Float.
Auf Mikrocontrollern sollte man möglichst die Finger von float und
double lassen und alles per Festkommaarithmetik machen, wenn man schon
Kommazahlen benötigt.
Mampf F. schrieb:> Auf Mikrocontrollern sollte man möglichst die Finger von float und> double lassen und alles per Festkommaarithmetik machen, wenn man schon> Kommazahlen benötigt....
Das stimmt! Aber zumindest der Lerneffekt ist sehr nützlich, um die
grundsätzliche Problematik zu erkennen. Auf dem kleinen µC mit "kurzen"
floats sieht man die Problematik viel schneller, was bei großen
Maschinen mit langen floats einfach nicht so schnell auffällt. Das
Problem wird mit längeren floats einfach nur ein paar Stellen nach
hinten verschoben, bleibt aber bestehen!
Ein Programm in dieser Art:
float a = 0.1;
float b = 3.5;
if ((a+b) == 3.6) {
...
}
ist auf jeder Maschine, die mit binären Gleitkommazahlen rechnet, höchst
problematisch, selbst, wenn die floats 128 Bits benutzen.
Ich empfehle auch einen Umstieg, egal auf was, Hauptsache kein PIC16.
Dein PIC ist nur sehr umständlich für grössere Projekte geeignet, da
dieser nur einen 8-stufigen Hardwarestack besitzt, wobei die
Interruptroutine auch eine Stufe benötigt. Für Leute wie mich, die gerne
für jeden Scheiss eine eigene Funktion verwenden, ist dieses Limit sehr
schnell erreicht.
Die PIC18-Familie ist bereist um einiges besser, besitzt jedoch keine
höhere Rechenleistung. Für mehr Leistung empfehle ich, wie bereits
vorgeschlagen, die PIC32-Familie oder etwas ähnliches eines anderen
Herstellers.
Thomas E. schrieb:> Mampf F. schrieb:>> Auf Mikrocontrollern sollte man möglichst die Finger von float und>> double lassen und alles per Festkommaarithmetik machen, wenn man schon>> Kommazahlen benötigt....>> Das stimmt! Aber zumindest der Lerneffekt ist sehr nützlich, um die> grundsätzliche Problematik zu erkennen.
Genau darum gehts.
Um das Erkennen, dass das naive Rumwerfen mit float bzw. double
keineswegs die Lösung aller Probleme darstellt, sondern ganz im
Gegenteil einen neuen Sack an Würmern aufmacht.
Und dabei wurde noch gar nicht besprochen, was einem dann in der
Arithmetik noch so alles passieren kann. Aber ich denke dem TO ist
mittlerweile schon klar geworden, was da noch so alles lauert.