Forum: Compiler & IDEs usigned long long sprtinf


von blonkel (Gast)


Lesenswert?

Hiho,

Leider hab ich nach längerem suchen herausgefunden, das es nicht möglich 
ist ein "unsigned long long" mit sprintf/printf in ein Char Array zu 
schreiben um dieses auf der UART auszugeben. Nun hab ich dies durch 
folgenden C Code realisiert:

void uart_ulltoa(unsigned long long val)
{
    char Buffer[50];
    char tmp;
    int i=0;

    while(val > 0)
    {
        Buffer[i++]='0'+val%10;
        val/=10;
    }

    for(int j = 0; j < i / 2; ++j ) {
        tmp = Buffer[j];
        Buffer[j] = Buffer[i-j-1];
        Buffer[i-j-1] = tmp;
    }

    Buffer[i] = 0;

    uart_puts(Buffer);
}

Funktioniert auch sehr gut soweit, das Problem dabei ist nur das die 
Funktion 8kbyte(!) Flash sowie ~250byte Ram benötigt. Das problem liegt 
in folgender Zeile: val/=10;

Gibt es eine Möglichkeit dies nun noch auf "speicher" zu optimieren? CPU 
last wäre mir realitiv egal.

Vielen Dank
Blonkel

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

blonkel schrieb:
> sowie ~250byte Ram benötigt.

Genauer gesagt: 256 Bytes.  Die kommen aus einer Bibliotheksfunktion,
sie werden für das Array __clz_tab[] belegt.  Wenn man danach gugelt,
findet man, dass die Gleitkommabibliothek das zwar für die
Gleitkommadivision ausgeräumt hat, aber offenbar schleppt's der GCC
beim (wenig optimierten) long-long-Code dann wieder mit ein.  Du
müsstest wohl die Funktion __udivdi3() reimplementieren, um das
loszuwerden.

p.s.: "Bitte den Controllertyp im Titel mit angeben."

von Andreas F. (aferber)


Lesenswert?

Wenn du sonst keinerlei Division mit "unsigned long long" in deinem Code 
brauchst, gibt es einen Weg, die Division (und damit die Einbindung der 
Routinen/Tabellen) komplett zu vermeiden. Dazu ist ein kleiner Umweg 
über die Packed-BCD-Darstellung notwendig. Gebraucht werden dann (auf 
AVR) nur die Register plus ein wenig Stack zum Sichern der benutzten 
Register.

Zunächst wird dazu die Zahl komplett in Packed BCD umgewandelt. Dazu 
musst du den Algorithmus zu bin2BCD16 aus folgender Appnote verwenden:

http://www.atmel.com/dyn/resources/prod_documents/doc0938.pdf

Die Erweiterung von 16 Bit Input, 3 Byte Output auf 64 Bit Input, 10 
Byte Output (2^64-1 hat 20 Dezimalstellen, also 10 BCD-Bytes) ist 
ziemlich "straight forward".

Sinnvoll implementieren lässt sich der Algorithmus IMO eigentlich nur in 
Assembler, da dabei fast nur auf Bitebene mit Einbeziehung von Carry 
gearbeitet wird. Wenn ich mich beim Überschlagen gerade nicht grob 
vertan habe, dürfte dieser Part dann so um die 10000 Takte plus/minus 
brauchen.

Wenn es nicht auf das letzte Code-Byte ankommt, würde ich die in der 
Appnote im Flussdiagramm dargestellte innere Schleife (das untere 
Drittel) übrigens nicht als Schleife schreiben, sondern komplett 
ausrollen (die Zahl der Durchläufe ist immer konstant). Nur so können 
auch die kompletten Outputbytes in Registern gehalten werden.

Anschliessend muss dann noch von BCD nach ASCII umgewandelt werden, das 
braucht nur noch Byte-Division bzw. -Modulo mit 16, also per 
Bitoperationen zu erledigen. Hierbei könnte es zweckmässig sein, die 
generierten Zeichen einfach direkt in den UART zu schieben, anstatt erst 
einen String zu schreiben und den dann an uart_puts() zu übergeben.

Andreas

von blonkel (Gast)


Lesenswert?

Jörg Wunsch schrieb:
> ...aber offenbar schleppt's der GCC
> beim (wenig optimierten) long-long-Code dann wieder mit ein...
Wenig optimiert bezieht sich hier wohl schätz ich nicht auf meine 
Optimierungstufe sondern auf das verabeiten von ull's von gcc?

> p.s.: "Bitte den Controllertyp im Titel mit angeben."
Tut mir leid, es handelt sich um hierbei um einen AVR atmega162.

Andreas Ferber schrieb:
> Sinnvoll implementieren lässt sich der Algorithmus IMO eigentlich nur in
> Assembler, da dabei fast nur auf Bitebene mit Einbeziehung von Carry
> gearbeitet wird. Wenn ich mich beim Überschlagen gerade nicht grob
> vertan habe, dürfte dieser Part dann so um die 10000 Takte plus/minus
> brauchen.
Vielen Dank für die vorgeschlagene Lösung, einziger Nachteil dabei ist 
das meine Assembler Kenntnisse ein wenig zurück liegen. Sollte es 
dennoch keine Alternative geben, werde ich wohl den oben genannten 
Algorithmus implementieren.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

blonkel schrieb:
> Jörg Wunsch schrieb:
>> ...aber offenbar schleppt's der GCC
>> beim (wenig optimierten) long-long-Code dann wieder mit ein...
> Wenig optimiert bezieht sich hier wohl schätz ich nicht auf meine
> Optimierungstufe sondern auf das verabeiten von ull's von gcc?

Ja, auf die interne Bibliothek (libgcc.a).  Deren 64-bit-Code ist
einfach stur maschinengeneriert, während viele andere Dinge (32-bit
integer und floating-point) handoptimierten oder manuell
nachgebesserten Code benutzen.

von Karl H. (kbuchegg)


Lesenswert?

blonkel schrieb:

> dennoch keine Alternative geben, werde ich wohl den oben genannten
> Algorithmus implementieren.

Die unsigned long long überhaupt loszuwerden, geht nicht?


Was ist mit Subtraktionen, brauchen die weniger?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Karl heinz Buchegger schrieb:

> Was ist mit Subtraktionen, brauchen die weniger?

Ich denke schon, Addition und Subtraktion lassen sich ja relativ
geradlinig implementieren.

Dieses __clz_bits[] ist irgendwie ein Array, das das Zählen der
führenden Nullen ("count leading zeros") vereinfachen soll, wobei
halt die Implementierer wohl davon ausgegangen waren, dass das Opfern
von 256 Bytes RAM für den erzielten Geschwindigkeitsgewinn (auf
einem PC) "Peanuts" sind.  Dummerweise gibt's offenbar halt keine
Möglichkeit, das pro Target umzuschalten, und erst recht fehlt die
Infrastruktur im GCC, um für verschiedenen -Os-Stufen verschiedene
libgcc's zu linken, die ihrerseits wiederum selbst der jeweiligen
Optimierungsstufe gerecht werden.

von Karl H. (kbuchegg)


Lesenswert?

Jörg Wunsch schrieb:
> Karl heinz Buchegger schrieb:
>
>> Was ist mit Subtraktionen, brauchen die weniger?
>
> Ich denke schon, Addition und Subtraktion lassen sich ja relativ
> geradlinig implementieren.

Schön ist es nicht unbedingt aber zyklenmässig würde man wahrscheinlich 
mit fortgesetzter Subtraktion runterbringen können. Problem ist 
natürlich, dass ein ull schon sehr hoch reicht und man viele einzelne 
Subtraktionsstufen braucht.

von Andreas F. (aferber)


Lesenswert?

Jörg Wunsch schrieb:
> und erst recht fehlt die
> Infrastruktur im GCC, um für verschiedenen -Os-Stufen verschiedene
> libgcc's zu linken

Doch, das geht eigentlich schon, über Änderungen/Ergänzungen der specs. 
Der "*libgcc"-Spec-String ist speziell dazu vorgesehen, beim Linken die 
passende libgcc-Variante auszuwählen. Beim avr-gcc wird damit z.B. dafür 
gesorgt, dass bei den ganz kleinen ATtiny (11, 12, 15, 28) keine 
libgcc gelinkt wird. "Verzweigung" anhand der gewählten "-O"-Einstellung 
lässt die Spec-Syntax dabei auch zu, z.B. so:
1
*libgcc:
2
%{!mmcu=at90s1*:%{!mmcu=attiny11:%{!mmcu=attiny12:%{!mmcu=attiny15:%{!mmcu=attiny28: %{Os|O0:-lgcc_small; O*:-lgcc_fast; :-lgcc_small }}}}}}

Voraussetzung ist natürlich, dass beim Linken dann auch "-O..." mit 
übergeben wird, was normalerweise eigentlich nicht unbedingt nötig ist, 
aber auch nicht schadet und oft sowieso im Makefile gemacht wird.

Hilft aber alles nichts, solange sich keiner findet, der entsprechend 
auf Größe optimierte Funktionen schreibt ;-)

Andreas

von blonkel (Gast)


Lesenswert?

Für alle die das Problem auch irgendwann haben hier meine Lösung (Ansatz 
dafür war irgendwo im Netz, leider hab ich die Quelle nicht mehr):

void bin2bcd64(unsigned long long inValue)
{
   unsigned char acc[21]={0};
   unsigned char carry[22]={0};

   for (int loop=0;loop<64;loop++)
   {
      if (( inValue & 0x8000000000000000) != 0)
         carry[0] = 1;
      else
         carry[0] =0;
      inValue <<= 1;

/*
If carry generated to next digit, convert and add carry.
If no carry generated to next digit, shift left 1 step and add carry.
*/
    for(int i=1; i < 21;i++)
    {
        if (carry[i] == 1)
           acc[i-1] = ((acc[i-1]-5)*2) + carry[i-1];
        else
           acc[i-1] = (acc[i-1] << 1) + carry[i-1];
    }

    // Check each accumulator if >= 5. Generate carry if so.
    for(int i=0; i < 21;i++)
    {
        if (acc[i] >= 5)
           carry[i+1] = 1;
        else
            carry[i+1] = 0;
    }

   }

   //Output without leeding zeros
   char zero=1;
   for(int i=19; i >= 0;i--)
   {
       if((zero == 1 && acc[i] != 0) || i == 0)
    {
      zero=0;
    }
       if(zero == 0)
    {
      uart1_putchar('0' + acc[i]);
    }
   }

}

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.