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


von Marko B. (Gast)


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.
1
void ftoa(char * buf, double f, char pad)
2
{
3
    char * p;
4
5
    f = (f*100.0 + 0.5)/100.0;        // round
6
    p = itoa(buf,f,pad);
7
    *p++ = '.';
8
9
    f -= (unsigned int) f;
10
    f = f * 100.0;
11
    itoa(p,f,2);
12
}
13
14
char * itoa(char * buf, unsigned int val, char pad) {
15
    char i;
16
    char tmp_buf[6];
17
18
    for(i=0; val>0; val/=10)
19
        tmp_buf[i++] = (val % 10) + '0';
20
    if(i==0)
21
        tmp_buf[i++] = '0';
22
23
    while(i<pad)
24
        tmp_buf[i++] = ' ';
25
26
    while(i>0)
27
        *buf++ = tmp_buf[--i];
28
29
    *buf = 0;
30
31
    return buf;
32
}

von Marko B. (Gast)


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.
1
void ftoa(char * buf, double f, char d, char pad, char padchar)
2
{
3
    char * p;
4
    const double powers[] = {1.0,10.0,100.0,1000.0,10000.0,100000.0};
5
6
    /* round */
7
    f = f>=0.0 ? (f*powers[d]+0.5)/powers[d] :
8
(f*powers[d]-0.5)/powers[d];
9
10
    /* special case */
11
    if (f<0.0 && f > -1.0) {
12
/*        if(pad<2)
13
            pad = 2;
14
        if(padchar) {
15
            while(--pad-1)
16
                *buf++ = ' ';
17
            *buf++ = '-';
18
            *buf++ = '0';
19
        }
20
        else {
21
            *buf++ = '-';
22
            while(--pad)
23
                *buf++ = '0';
24
        }
25
        p = buf; */
26
        p = ltoa(buf,-1,pad,padchar);
27
        *(p-1) = '0';
28
    }
29
    else
30
        p = ltoa(buf,f,pad,padchar);
31
32
    *p++ = '.';
33
34
    if (f<0.0)
35
        f = -f;
36
    
37
    f -= (long) f;
38
    f *= powers[d];
39
    ltoa(p,(long)f,d,0);
40
}
41
42
char * ltoa(char * buf, long val, char pad, char padchar)
43
{
44
    char i;
45
    char neg = 0;
46
    char tmp_buf[21];
47
48
    if(val<0) {
49
        val = -val;
50
        neg = 1;
51
    }
52
53
    for(i=0; val>0; val/=10)
54
        tmp_buf[i++] = (val % 10) + '0';
55
56
    if(i==0)
57
        tmp_buf[i++] = '0';
58
59
    if(neg) {
60
        if(padchar) {
61
            tmp_buf[i++] = '-';
62
            while(i<pad)
63
                tmp_buf[i++] = ' ';
64
        }
65
        else {
66
            while(i<pad-1)
67
                tmp_buf[i++] = '0';
68
            tmp_buf[i++] = '-';
69
        }
70
    }
71
    else
72
        while(i<pad)
73
            tmp_buf[i++] = padchar ? ' ' : '0';
74
75
    while(i>0)
76
        *buf++ = tmp_buf[--i];
77
78
    *buf = 0;
79
80
    return buf;
81
}

von pittbull (Gast)


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

von peter dannegger (Gast)


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

von Marko B. (Gast)


Angehängte Dateien:

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.

von GEO (Gast)


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

von Marko B. (Gast)


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

von derbrain (Gast)


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?

von Marko B. (Gast)


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

von Jankey (Gast)


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);
}

von Peter D. (peda)


Lesenswert?

@Jankey

"vl. etwas altmodisch"

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


Peter

von Ralf (Gast)


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

von Karl H. (kbuchegg)


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.

von Denny S. (nightstorm99)


Lesenswert?

Hallo!


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

Er zeigt hier als erstes auf die letzt Zeile:
1
void ftoa(char * buf, double f, char d, char pad, char padchar)
2
{
3
  char * p;
4
  const double powers[] = {1.0,10.0,100.0,1000.0,10000.0,100000.0};
5
6
  /* round */
7
  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

von Karl H. (kbuchegg)


Lesenswert?

mach aus d und pad einen unsigned char

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.