www.mikrocontroller.net

Forum: Projekte & Code C-Umwandlungsfunktionen für double/int/long nach String


Autor: Marko B. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wenn man eine Gleitkommazahl ausgeben will, z.B. auf ein LCD, dann
benutzt man im Allgemeinen sprintf. Allerdings benötigt diese Funktion
viel Speicher, sowohl RAM als auch ROM. Konkretes Beispiel: in der libc
für den Renesas R8C sind die Gleitkomma-Umwandlungsfunktionen für
printf, sprintf, etc. deaktiviert, weil der R8C zu wenig RAM hat. Man
benötigt somit eine eigene Umwandlungsfunktion wenn man
Gleitkommazahlen ausgeben will. Aber auch unter anderen Umständen kann
es sinnvoll sein, die stdlib-Funktionen zu meiden und eigene zu
benutzen, um Speicher zu sparen. Es folgen ein paar Funktionen, die ich
zu diesem zweck geschrieben habe.

Zuerst die einfache Variante: nur für positive Zahlen, Anzahl der
Dezimalstellen und Art des Füllzeichens sind hartkodiert.
void ftoa(char * buf, double f, char pad)
{
    char * p;

    f = (f*100.0 + 0.5)/100.0;        // round
    p = itoa(buf,f,pad);
    *p++ = '.';

    f -= (unsigned int) f;
    f = f * 100.0;
    itoa(p,f,2);
}

char * itoa(char * buf, unsigned int val, char pad) {
    char i;
    char tmp_buf[6];

    for(i=0; val>0; val/=10)
        tmp_buf[i++] = (val % 10) + '0';
    if(i==0)
        tmp_buf[i++] = '0';

    while(i<pad)
        tmp_buf[i++] = ' ';

    while(i>0)
        *buf++ = tmp_buf[--i];

    *buf = 0;

    return buf;
}

Autor: Marko B. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hier die vollständige Version: negative Zahlen sind möglich, Anzahl der
Dezimalstellen und Art des Füllzeichens (0 oder Leerzeichen) können als
Parameter übergeben werden.
void ftoa(char * buf, double f, char d, char pad, char padchar)
{
    char * p;
    const double powers[] = {1.0,10.0,100.0,1000.0,10000.0,100000.0};

    /* round */
    f = f>=0.0 ? (f*powers[d]+0.5)/powers[d] :
(f*powers[d]-0.5)/powers[d];

    /* special case */
    if (f<0.0 && f > -1.0) {
/*        if(pad<2)
            pad = 2;
        if(padchar) {
            while(--pad-1)
                *buf++ = ' ';
            *buf++ = '-';
            *buf++ = '0';
        }
        else {
            *buf++ = '-';
            while(--pad)
                *buf++ = '0';
        }
        p = buf; */
        p = ltoa(buf,-1,pad,padchar);
        *(p-1) = '0';
    }
    else
        p = ltoa(buf,f,pad,padchar);

    *p++ = '.';

    if (f<0.0)
        f = -f;
    
    f -= (long) f;
    f *= powers[d];
    ltoa(p,(long)f,d,0);
}

char * ltoa(char * buf, long val, char pad, char padchar)
{
    char i;
    char neg = 0;
    char tmp_buf[21];

    if(val<0) {
        val = -val;
        neg = 1;
    }

    for(i=0; val>0; val/=10)
        tmp_buf[i++] = (val % 10) + '0';

    if(i==0)
        tmp_buf[i++] = '0';

    if(neg) {
        if(padchar) {
            tmp_buf[i++] = '-';
            while(i<pad)
                tmp_buf[i++] = ' ';
        }
        else {
            while(i<pad-1)
                tmp_buf[i++] = '0';
            tmp_buf[i++] = '-';
        }
    }
    else
        while(i<pad)
            tmp_buf[i++] = padchar ? ' ' : '0';

    while(i>0)
        *buf++ = tmp_buf[--i];

    *buf = 0;

    return buf;
}

Autor: pittbull (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
ohne jetzt frech zu erscheinen, aber die ltoa/itoa funktionen scheinen
mir recht umständlich zu sein. hier ein beispiel das eine positive zahl
als zeichen ausgibt.

void iout (long v)
{
   long z;
   if (z = v/10)
     iout (z);
   putchar ('0' + (char)(v-10*z));
}

das lässt sich sicher leicht umbauen, so dass die zahl in einen buffer
geschrieben wird und dass auch negative zahlen dargestellt werden

Autor: peter dannegger (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Divisionen sind auf CPUs ohne DIV-Unit sehr rechenintensiv.

Besser fährt man daher mit der Subtraktionsmethode:

http://www.mikrocontroller.net/forum/read-4-46127.html#new


Peter

Autor: Marko B. (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Anscheinend hat der Syntax Highlighter hier im Forum einen Bug, da
einige float-Zahlen doppelt erschienen sind. So kann der Code natürlich
nicht funktionieren. Fällt hier natürlich keinem auf. ;-)

Ich habe das ganze noch um Funktionen zur Umwandlung in Binär- und
Hex-Strings ergänzt, Zipdatei ist im Anhang.

pittbull: Rekursive Funktionen will man auf Mikrocontollern vermeiden.
Auf einem Controller mit 1kB RAM bleibt wenig Spielraum für großartige
Verschachtelungen.

Aber auch ohne Rekursion kann man natürlich eine Umwandlungsfunktion
von int nach String in 5 Zeilen schreiben. Wenn man allerdings den
Funktionsumfang von sprintf nachbilden will, dann wird es schon etwas
komplexer. Meine Funktionen benötigen zusammen 1269 Bytes ROM und haben
praktisch den selben Funktionsumfang wie sprintf. Zum Vergleich: sprintf
aus der libc benötigt fast 4kB! Und diese Version unterstützt nicht
einmal float oder double-Zahlen. sprintf mit float/double-Unterstützung
benötigt 9kB ROM, ist aber auf dem R8C gar nicht lauffähig weil es mehr
als 1kB RAM benötigt.

peter: Gute Idee, vielleicht ändere ich das noch. Ich benutze den Code
aber auf dem R8C, der hat einen HW-Mult/Div, da ist das nicht so
tragisch. Schneller als sprintf ist es auf jeden Fall.

Autor: GEO (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi, da ich für eine Displayausgabe mit MC eine int/float Umwandlung in
string benötige, ich aber auf keine Libaries zurückgreifen kann, wollte
ich obige Funktionen probieren.

Folgende Funktionen (aus Codebeispiel zum downloaden) laufen ohne
Probleme.
void ctob(char * buf, unsigned char val);
void itob(char * buf, unsigned int val);
void ctoh(char * buf, unsigned char val);
void itoh(char * buf, unsigned int val);
void ltoh(char * buf, unsigned long val);

Ich bekomme aber irgendwie ftoa bzw. itoa nicht ans laufen.
Hat dazu vielleicht jemand Tips.

void ftoa(char * buf, double f, char d, char pad, char padchar);
char * ltoa(char * buf, long val, char pad, char padchar);

d ist doch Anzahl der Dezimalstellen,
padchar das Füllzeichen?
Für was steht Übergabeparameter pad? Bzw. wie müssen diese char
Parameter übergeben werden?

Habe schon einiges rumprobiert aber nicht hinbekommen.
Wäre froh wenn mir einer kurz helfen könnte.
Mit freundlichen Grüßen

Autor: Marko B. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi GEO,

pad gibt an, wieviel Zeichen mindestens links vom Dezimalpunkt stehen
sollen bzw. bei ltoa wie breit die Darstellung mindestens sein soll.
pad entspricht also der "field width" bei printf. Dadurch kann man
Werte rechtsbündig darstellen, z.B. wenn man mehrere Werte
untereinander anzeigen will. Braucht man das ganze nicht, gibt man für
pad 0 an. padchar gibt an, mit welchem Zeichen aufgefüllt wird. Für
Nullen gibt man 0 an, für Leerzeichen eine Zahl ungleich 0. Um folgende
Darstellung zu erreichen:

   3.142
  32.545
 884.982
  -0.443

schreibt man:

ftoa(lcd_buf,d,3,4,1);

(entspricht sprintf(lcd_buf, "%8.3f", d);)

Und für diese Darstellung:

0003.142
0032.545
0884.982
-000.443

ftoa(lcd_buf,d,3,4,0);

(entspricht sprintf(lcd_buf, "%08.3f", d);)

Ich hätte wohl noch ein paar erklärende Worte dazuschreiben sollen,
aber ich habe mir gedacht, der Code ist ja eigentlich selbsterklärend.
;-)

Autor: derbrain (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,
mir ist da was aufgefallen, schlagt mich nicht wenn ich Blödsinn rede
;-)
Du verwendest
*p++ = '.';
Ist das nicht gefährlich, einfach so mal auf die nächste Speicherstelle
zu schreiben, ohne den Speicher zu reservieren?

Autor: Marko B. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Nein, das stimmt schon so. Der eigentliche Buffer wird ja vorher im
Programm deklariert und ein Pointer (buf) darauf von der aufrufenden
Funktion übergeben. Der Pointer p zeigt auf bestimmte Stellen innerhalb
des eigentlichen Buffers.



Z.B. benutzt ftoa() ltoa() um die Vorkommastellen zu schreiben. Dazu
gibt ltoa() einen Pointer auf das letzte geschriebene Zeichen zurück.



Der Vorteil dieser Pointerarithmetik ist, daß man dadurch viele
Zählvariablen spart und die Adressen innerhalb der Buffer direkt
zwischen den Funktionen übergeben kann. Wenn man weiß wie es
funktioniert ist es auch übersichtlicher und einfacher zu lesen. ;-)

Autor: Jankey (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
vl. etwas altmodisch:

void SendZahl( unsigned char input )
{
  uchar disp[3];
  uchar i;

  disp[0] = input/100;
  input  -= disp[0] * 100;
  disp[1] = input / 10;
  input  -= disp[1] *10;
  disp[2] = input;

  for (i=0;i<2;i++)
    if(disp[i]!=0)        // nicht senden!
      Send_Serial(disp[i]+48);

  Send_Serial(disp[2]+48);
}

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Jankey

"vl. etwas altmodisch"

Nö nicht altmodisch, bloß viel aufwendiger auf CPUs ohne
Divisionsbefehl als die Subtraktionsmethode.


Peter

Autor: Ralf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo zusammen,

ich habe versucht die Funktion "itoa" auf einem MSP430F169 zum laufen zu 
bringen.
Leider erfolglos. Ich glaube der MSP hat mit 2kB nicht genügend RAM.
Liege ich da richtig?
Jedenfalls bleibt er an der Stelle immer stehen :(

Wär nett, wenn mir da jemand helfen könnte.

LG
Ralf

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wie sieht deine Funktion aus?
Wie benutzt du sie?

> Ich glaube der MSP hat mit 2kB nicht genügend RAM.
> Liege ich da richtig?

Ziemlich unwahrscheinlich.

Autor: Denny S. (nightstorm99)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo!


Ich bekomme bei dem Code folgende Compiler Warnings:
sprintf.c: In function 'ftoa':
sprintf.c:123: warning: array subscript has type 'char'
sprintf.c:123: warning: array subscript has type 'char'
sprintf.c:123: warning: array subscript has type 'char'
sprintf.c:123: warning: array subscript has type 'char'
sprintf.c:139: warning: array subscript has type 'char'

Er zeigt hier als erstes auf die letzt Zeile:
void ftoa(char * buf, double f, char d, char pad, char padchar)
{
  char * p;
  const double powers[] = {1.0,10.0,100.0,1000.0,10000.0,100000.0};

  /* round */
  f = f>=0.0 ? (f*powers[d]+0.5)/powers[d] : (f*powers[d]-0.5)/powers[d];

Was kann ich dagegen machen?

Gruß Denny

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
mach aus d und pad einen unsigned char

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.