Wie zerlegt man eine Fließkommazahl in einzelne Ziffern?

Ich helfe einem Freund bei einem kleinen Elektronikprojekt mit einem PIC-Mikrocontroller 16F877 (A), den ich mit mikroC programmiere.

Ich bin auf ein Problem gestoßen, nämlich dass ich eine Gleitkommazahl in einer Variablen habe, sagen wir zum Beispiel 1234.123456, und sie in Variablen aufteilen muss, die jede einzelne Zahl enthalten, damit ich Char1 = 1, Char2 = 2 usw. usw. für die Anzeige bekomme auf LCD. Die Zahl wird immer auf 3 oder 4 Dezimalstellen gerundet, daher sollte es notwendig sein, die Position des Dezimalpunkts zu verfolgen.

Jeder Rat, wie man diese Aufteilung erhält, wäre sehr willkommen.

Antworten (3)

Es gibt zahlreiche Möglichkeiten, dies zu tun. Möglicherweise hat Ihr Compiler eine Bibliotheksfunktion, die das für Sie erledigt. Es kann möglich sein mit:

  • sprintf() / snprintf()
  • dtostrf()
  • dtoa()

Alternativ ist es nicht allzu schwer, dafür eine eigene Routine zu schreiben. Es geht nur darum, zuerst herauszufinden, wie viele Stellen vor dem Komma vorhanden sind, es so oft durch 10 zu dividieren, dann den ganzzahligen Teil wiederholt zu nehmen und dabei mit 10 zu multiplizieren, wobei darauf zu achten ist, dass das Komma an der richtigen Stelle hinzugefügt wird.

In Pseudo-Code kann es also so aussehen:

If the value < 0.0:
    Insert - into string
    Subtract value from 0.0 to make it positive.
While the value <= 10.0:
    Divide by 10
    Increment decimal counter
For each digit of required precision:
    Take the integer portion of the value and place it in the string
    Subtract the integer portion from the value
    Decrement decimal counter
    If decimal counter is 0:
        Insert decimal point
    Multiply the value by 10.

Verwenden Sie sprintf(). Es funktioniert genau wie printf(), aber "druckt" in eine Zeichenfolge (auch bekannt als ein Array von Zeichen).

do: char stringvar[10]; sprintf(stringvar, "%9.4f", floatvar);

Nicht alle Bibliotheken haben den Gleitkommateil von vsprintf einkompiliert, um Platz zu sparen. Besonders auf 8-Bit-Compilern.
@Majenko: Das mag stimmen, aber wenn er mit Gleitkommazahlen umgeht, besteht eine gute Chance, dass er bereits einige Gleitkommabibliotheken verwendet.
Die Gleitkomma-Mathematikbibliotheken sind vollständig von der printf-Gleitkommabehandlung getrennt. Schauen Sie sich zum Beispiel die avr-libc von Arduino an. Hat volle Gleitkommaunterstützung, kann aber keine Gleitkommazahlen drucken, da dieser Code ausdrücklich ausgeschlossen wurde, da er unglaublich groß ist.
Wenn er die Gleitkommabibliotheken verwendet, hat er wahrscheinlich viel Codeplatz und kann auch die printf-Bibliothek einbinden.
@Austin Es geht nicht darum, die "printf-Bibliothek" "einzuschließen", sondern darum, ob die integrierte C-Bibliothek Gleitkommaunterstützung in der vsprintf-Funktion hat. Es gibt nichts, was Sie "einschließen" können, um dies zu ändern, außer eine andere C-Bibliothek zu verwenden.
Ich meine nicht buchstäblich #einschließen, sondern verlinken dagegen, und mit "printf-Bibliothek" meine ich diejenige, die vsprintf hat, aber printf ist die häufigste Verwendung davon. Ich habe noch nie einen vsprintf gesehen, der nicht auch Gleitkommazahlen verarbeiten kann.

Auf einer kleinen MCU ohne Hardware-Fließkommaunterstützung sollten wir so wenig Fließkommaberechnungen wie möglich durchführen, und wenn Sie die printf-Funktionsfamilie nicht wirklich benötigen, versuchen Sie, sie zu vermeiden, da sie den Code aufbläht und verlangsamt.

Ich schlage vor, den Float in eine Ganzzahl umzuwandeln, indem Sie den Float zuerst mit 1.000,0 multiplizieren (vorausgesetzt, Sie möchten drei Dezimalstellen) und ihn dann in eine lange Ganzzahl konvertieren und gegebenenfalls abrunden. Wenn Sie das Ergebnis auf einem 7-Segment- oder Punktmatrix-LCD anzeigen möchten, ist dieses Format meiner Meinung nach ideal.

Nehmen wir an, dass der Float im Bereich 0 bis 999,999 liegen kann (negieren, wenn negativ, Vorzeichen für spätere Anzeige speichern).

Pseudocode:

dig6 = -1  // Init MSDigit
while number >= 0
   number = number - 100,000
   dig6 = dig6 + 1
number = number + 100,000  // Restore number, Dig 6 is done

dig5 = -1
while number >= 0
   number = number - 10,000
   dig5 = dig5 + 1
number = number + 10,000 // number can be switched to 16-bit here for speed

... dig4 and 3 in similar fashion

dig2 = -1
while number >= 0
   number = number - 10
   dig2 = dig2 + 1
dig1 = number + 10

An dieser Stelle haben Sie alle sechs Ziffern in je einem Byte gespeichert und das Minuszeichen gespeichert. Wenn Sie ein 7-Segment-LCD verwenden, übergeben Sie die Ziffern an eine 7-Segment-Encoderfunktion, bevor Sie sie auf das LCD schreiben. Wenn Sie ein Punktmatrix-Display mit serieller Schnittstelle verwenden, fügen Sie für die ASCII-Codierung 0x30 zu jeder Ziffer hinzu. Wir müssen uns auch den Dezimalpunkt zwischen dig4 und dig3 merken.

Dieser Algorithmus ist ziemlich schnell, da es keine Multiplikation und Division gibt. Ich habe es auf winzigen 4-Bit-MCUs mit guten Ergebnissen verwendet.