Forum: Mikrocontroller und Digitale Elektronik PIC16F887 float in string umwandeln - PROBLEME :-(


von Tobias W. (tobi93)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

"What Every Computer Scientist Should Know About Floating-Point 
Arithmetic"

http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

von Karl H. (kbuchegg)


Lesenswert?

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.)

: Bearbeitet durch User
von Tobias W. (tobi93)


Lesenswert?

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

von Thomas E. (picalic)


Lesenswert?

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

: Bearbeitet durch User
von WehOhWeh (Gast)


Lesenswert?

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.

von Tobias W. (tobi93)


Lesenswert?

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

von WehOhWeh (Gast)


Lesenswert?

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.

von Mampf F. (mampf) Benutzerseite


Lesenswert?

Auf Mikrocontrollern sollte man möglichst die Finger von float und 
double lassen und alles per Festkommaarithmetik machen, wenn man schon 
Kommazahlen benötigt.

von Thomas E. (picalic)


Lesenswert?

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.

von B. S. (bestucki)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

1
Floating point arithmethic is
2
like moving a pile of sand.
3
Every time you do it,
4
you loose a little sand
5
and pick up a litte dirt.

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.