Für eine Umwandlung von DEC Zahlen in das ASCII Format muss ich nach für
die Anzahl der Stellen immer durch 10 teilen. Ich arbeite mit einem
Atmega32u4, dieser hat ja nun auch keine Hardwaredivision. Gibt es eine
bessere Alternative als ein x/10? Vielleicht ein bitshift mit
aufaddieren?
Draco schrieb:> Für eine Umwandlung von DEC Zahlen in das ASCII Format muss ich nach für> die Anzahl der Stellen immer durch 10 teilen
wieso das denn?
itoa ltoa ultoa dtostr wäre eine Möglichkeit, sprintf eine andere
Draco schrieb:> Für eine Umwandlung von DEC Zahlen in das ASCII Format muss ich nach für> die Anzahl der Stellen immer durch 10 teilen. Ich arbeite mit einem> Atmega32u4, dieser hat ja nun auch keine Hardwaredivision. Gibt es eine> bessere Alternative als ein x/10?
Ich würde vermuten, der Compiler kann die Division durch die Konstante
so gut optimieren, dass jede andere Lösung umständlicher wäre.
> Vielleicht ein bitshift mit aufaddieren?
Das geht nur dann efizient, wenn du beliebigen Zugriff auf die einzelnen
Bits hast. Also: nimm ein FPGA.
http://www.lothar-miller.de/s9y/categories/44-BCD-UmwandlungFrank M. schrieb:> findet man eine Shift-Add-Variante. Aber ob die tatsächlich schneller ist?
Sicher nicht auf einem Rechenknecht, der nur 8 Bit kann und dann schon
wieder mit einem Übertrag rumhampeln muss und keinen Nibble-Zugriff
beherrscht...
Stefan U. schrieb:> Für a=b/2 und a=b<<1 erzeugt der Compiler exakt den selben Bytecode.
Bestimmt nicht. Ersteres ist eine Division, letzteres eine
Plutimikation ;-)
>>> Allenfalls zu beginn weg in BCD rechnen ?>> AVR's haben doch gar keine BCD Operationen.> So schwierig ist das nun auch wieder nicht.
Aber es macht doch keinen Sinn, Daten im BCD Format zu speichern, wenn
es keine dazu passenden Operationen gibt. Dann müsste man doch vor und
nach jeder Operation konvertieren. Damit gewinnt der TO nichts.
Sooo.. ganz dämlich und einfach - einfach ne Multiplikation mit 0.1:
1
uint16_tmul01(uint16_tdivisior)
2
{
3
returndivisior*0.1;
4
}
5
6
voidown_utoa(uint16_tuIntValue,char*Buffer)
7
{
8
uint8_ti=0;
9
chartemp;
10
11
while(uIntValue>0)
12
{
13
Buffer[i++]='0'+(uIntValue%10);
14
uIntValue=mul01(uIntValue);// Hierfür!!!
15
}
16
17
18
for(uint8_tj=0;j<i/2;++j)
19
{
20
temp=Buffer[j];
21
Buffer[j]=Buffer[i-j-1];
22
Buffer[i-j-1]=temp;
23
}
24
Buffer[i]='\0';
25
}
Das hat den Sourcecode um satte 500 Bytes minimiert bei nur einer 16Bit
Ausgabe (Dec: 786).
Mit mul01(uIntValue) :
1
Program Memory Usage : 4508 bytes 13,8 % Full
2
Data Memory Usage : 70 bytes 2,7 % Full
Mit uIntValue /= 10 :
1
Program Memory Usage : 5072 bytes 15,5 % Full
2
Data Memory Usage : 70 bytes 2,7 % Full
Eine Multiplikation mit Ganzzahlen und Bitshift hat nicht so den
erhofften Erfolg gebracht ((divisior * 205) >> 11;) - der dort mit 32bit
gerechnet werden muss um kein Overflow herbeizuführen, bringt nicht
sonderliche Vorteile und ist auf 1024 begrenzt:
Stefan U. schrieb:>>> bessere Alternative als ein x/10?>> x * 0,1>> Ach komm schon, jetzt wird's aber schäbig.
OK ganzzahlig *10/100 (scnr)
Rene K. schrieb:> Es geht nicht um Geschwindigkeitsvorteile, es geht um Platzersparnis.
nächst größeren nehmen, wozu um Cent zu sparen zu lange am Code sitzen?
Joachim B. schrieb:> isn Argument, bis jetzt habe ich lieber nach m328p den m1284p genommen,> den m32u4 muss ich erst noch kennenlernen, liegt schon hier rum.
Ist nen feines Teil mit dem "On-Board" USB - wenn man da was größeres
will muss man in der AT90USB Reihe rumstöbern. Das ist dann aber leider
nimmer Pinkompatibel.
Rene K. schrieb:> Es geht nicht um Geschwindigkeitsvorteile, es geht um Platzersparnis.
Ich habs grad mal compiliert, itoa() benötigt wahnsinnig enorme, extrem
riesige, unvorstellbar gigantische 134 Byte Flash, das sind volle 0,4%
Deines ATmega32.
Findest Du es nicht selber lachhaft, darum so ein Getöse zu machen?
Peter D. schrieb:> Ich habs grad mal compiliert, itoa() benötigt wahnsinnig enorme, extrem> riesige, unvorstellbar gigantische 134 Byte Flash, das sind volle 0,4%> Deines ATmega32.> Findest Du es nicht selber lachhaft, darum so ein Getöse zu machen?
Das Argument lasse ich gelten :-D Leider muss ich viele Werte umwandeln
um sie via UART rauszuschicken. In der Summierung macht sich das schon
bezahlbar ja. Und "Getöse" war es ja nichtmal, ging ja alles flott von
der Hand und hat mich ja auch zeitlich nicht sonderlich aufgehalten.
Das andere wäre ja auch noch eine Spur einfacher gewesen, ich könnte ja
auch die Daten direkt rausschicken und auf dem Host die Umrechnung
machen lassen. Aber so isses schöner, passt, is kleiner und läuft.
Rene K. schrieb:> Leider muss ich viele Werte umwandeln um sie via UART rauszuschicken.> In der Summierung macht sich das schon bezahlbar ja.
Dir ist aber schon klar, dass itoa() eine Funktion ist, die nur einmal
im Flash gespeichert werden muss, oder?
Rene K. schrieb:> In der Summierung macht sich das schon> bezahlbar ja.
Nein, eben nicht. Die Routine wird nur einmal angelegt, egal wie oft Du
sie aufrufst.
Um die Aufrufkosten zu senken, hilft z.B. eine Schleife, statt
haufenweise Copy&Paste.
Stefan U. schrieb:> Überlass das mal dem Compiler, der wird schon guten Code erzeugen.>> Für a=b/2 und a=b<<1 erzeugt der Compiler exakt den selben Bytecode.
Erstens hast Du da was verdreht (du meintest Rechtsschift) und zweitens
hab ich schon Compiler gesehen (vorzugsweise solche die mit K anfangen
und mit l aufhören und viel Geld verlangen) die sind nicht imstande eine
Division durch eine 2^n Konstante als solche zu erkennen und ziehen dann
die komplette Integer-Division mit rein.
Allerdings muss man sich beim Rechtsschift vorher schlau machen was auf
dieser Platform und bei diesem Compiler mit negativen Zahlen passiert.
> Allerdings muss man sich ... schlau machen was auf ...mit negativen> Zahlen passiert.
Es geht um unsigned Integer, die können nicht negativ werden.
Aber der Hinweis ist sinnvoll, bei signed Integer muss man aufpassen.
> hab ich schon Compiler gesehen ... die sind nicht imstande (sind) eine> Division durch eine 2^n Konstante als solche zu erkennen und ziehen> dann die komplette Integer-Division mit rein.
Na das sind ja erstaunlich dumme Compiler. Nicht mehr sonderlich
Zeitgemäß, würde ich mal sagen.
W.A. schrieb:> Das mach mal vor. Den Unterschied zwischen 1/10 und 1.0/10.0 kennst du?
Für x = 123.456
x / 10 = 12.3456
x * 0.1 = 12.3456
Bitteschön ;-)
>>>> bessere Alternative als ein x/10?>>> x * 0,1>> Ach komm schon, jetzt wird's aber schäbig.> Das "schäbig" mußt Du mir schon begründen.
x*0,1 ist genau das Gleiche wie x/10. Nicht nur Mathematisch, sondern
auch für den Mikrocontroller. Der Compiler erzeugt in beiden Fällen des
selben Code.
Der Vorschlag ist daher vollkommen sinnlos.
Stefan U. schrieb:> Der Compiler erzeugt in beiden Fällen des> selben Code.
Der Compiler verwendet dafür aber aufgrund fehlenden Wissens eine
Softdivisionsfunktion mit entsprechend langer Laufzeit. Genau das will
der TO ja nicht.
Hier mal ein Beispiel für einen vorzeichenbehafteten 16-bit-Wert in
ASCII, führende Nullen werden weggelassen, ein Dezimalpunkt wird
eingefügt. Es ist also ein Festkommawert mit einer Nachkommastelle,
deswegen nicht bei den Stellen verwirren lassen.
1
/**** Ausgabe als -####.# oder ####.# an RS232 ****/
2
3
voidwrite_snnnndn(int16_tvalue){
4
uint8_ttchr,lz;
5
6
lz=1;
7
if(value<0){// wenn negativ, invertieren und Minus
8
value=-value;
9
tchr='-';
10
serial_write(tchr);
11
}
12
tchr='0';
13
while(value>=10000){// Tausender
14
value-=10000;
15
tchr++;
16
}
17
if(!(tchr=='0')){
18
serial_write(tchr);
19
lz=0;
20
}
21
tchr='0';
22
while(value>=1000){// Hunderter
23
value-=1000;
24
tchr++;
25
}
26
if(!(tchr=='0')||!lz){
27
serial_write(tchr);
28
lz=0;
29
}
30
tchr='0';
31
while(value>=100){// Zehner
32
value-=100;
33
tchr++;
34
}
35
if(!(tchr=='0')||!lz){
36
serial_write(tchr);
37
}
38
tchr='0';
39
while(value>=10){// Einer
40
value-=10;
41
tchr++;
42
}
43
serial_write(tchr);
44
serial_write('.');
45
tchr='0';
46
while(value>=1){// Nachkomma
47
value-=1;
48
tchr++;
49
}
50
serial_write(tchr);
51
}
In ASM geht das noch etwas knapper, aber das würde die Koniferen hier im
Forum verwirren.
Stefan U. schrieb:> x*0,1 ist genau das Gleiche wie x/10. Nicht nur Mathematisch, sondern> auch für den Mikrocontroller. Der Compiler erzeugt in beiden Fällen des> selben Code.
Eben nicht! Selbst bei einem AVR8 besteht ein Unterschied zwischen
Multiplikation, die mit dem MUL-Befehl verkürzt und beschleunigt werden
kann, und der Division, die deutlich aufwendiger mit Subtraktionen und
Bitschieberei erledigt werden muß.
Bei der Fragestellung x/10 gehe ich bei x zunächst einmal von einer
reellen Zahl aus, weshalb ein Vorschlag von itoa() für x ein Integer
impliziert. Das geht aber aus der Eingangsfrage nicht eindeutig hervor.
m.n. schrieb:> Bei der Fragestellung x/10 gehe ich bei x zunächst einmal von einer> reellen Zahl aus
Ich gehe in Verbindung mit uC immer zuallererst von dessen generischem
Format, also einer Ganzzahl in binärer Darstellung (aka Integer) aus.
Demnach berechnet der die Division mit Erebnis und Rest, und eben nicht
mit einer rellen Zahl als Resultat.
Lothar M. schrieb:> m.n. schrieb:>> Bei der Fragestellung x/10 gehe ich bei x zunächst einmal von einer>> reellen Zahl aus> Ich gehe in Verbindung mit uC immer zuallererst von dessen generischem> Format, also einer Ganzzahl in binärer Darstellung (aka Integer) aus.
Ja gut, dann bezeichne ich diesen Typen aber mit i, j, k, ... und nicht
mit x, y, z.
m.n. schrieb:> Ja gut, dann bezeichne ich diesen Typen aber mit i, j, k, ...> und nicht mit x, y, z.
Was ist da der Unterschied? Man kann auch a, b und c nehmen...
>> x*0,1 ist genau das Gleiche wie x/10> Eben nicht! Selbst bei einem AVR8 besteht ein Unterschied> zwischen Multiplikation, die mit dem MUL-Befehl verkürzt und> beschleunigt werden kann, und der Division, die deutlich> aufwendiger mit Subtraktionen und Bitschieberei erledigt werden muß.
Das wollte ich überprüfen und kam zu einem überraschenden Ergebnis:
Offensichtlich ist hier in beiden Fällen entscheidend, dass bei der
Multiplikation eine Fließkommazahl verwendet wurde, bei der Division
jedoch nur Integer vorkommen.
Da habe ich mal wieder etwas dazu gelernt.
Rene K. schrieb:> Peter D. schrieb:>> Ich habs grad mal compiliert, itoa() benötigt wahnsinnig enorme, extrem>> riesige, unvorstellbar gigantische 134 Byte Flash, das sind volle 0,4%>> Deines ATmega32.>> Findest Du es nicht selber lachhaft, darum so ein Getöse zu machen?>> Das Argument lasse ich gelten :-D Leider muss ich viele Werte umwandeln> um sie via UART rauszuschicken. In der Summierung macht sich das schon> bezahlbar ja. Und "Getöse" war es ja nichtmal, ging ja alles flott von> der Hand und hat mich ja auch zeitlich nicht sonderlich aufgehalten.>> Das andere wäre ja auch noch eine Spur einfacher gewesen, ich könnte ja> auch die Daten direkt rausschicken und auf dem Host die Umrechnung> machen lassen. Aber so isses schöner, passt, is kleiner und läuft.
Naja.
Ich täte zunächst mal kucken, wo denn die ganze Rechenzeit hinfließt.
Dazu gibts zum Beispiel die Möglichkeit, mittels Timer zu zählen,
wieviele Taktzyklen denn eine bestimmte Funktion wirklich benötigt. Oder
man setzt sich einen Pin, das Scope kann dir DIREKT sagen, wie langer
der Controller da drin rumgammelt. Gemittelt Minuten wenn es sein muss.
Nur Funktionen, in denen er lange ist, sollte man überhaupt anschauen.
Dabei stellt sich oft heraus, dass eine als wichtig betrachtete
Optimierung nur im unteren Promillebereich effektiv ist, aber eine
andere Funtkion wegen eines simplen Fehlers massig Zyklen frisst.
Ein strategisch ungeschickt verwendetes % mir schon mal 20% CPU-Last
gekostet.
Vor allem stellt sich IMMER heraus, dass das anders ist, als man
geglaubt hat :-)
Premature optimization is the root of all evil. (Donald Knuth)
Lothar M. schrieb:> Was ist da der Unterschied? Man kann auch a, b und c nehmen...
Der Unterschied besteht im Programmierstil, der sich in Anlehnung an K&R
ergeben hat. Eine Character-Variable bekommt 'c', ein String 's',
Pointer vorzugsweise 'p' und 'q' und eine temporäre Variable 'temp' als
Bezeichner.
Angelehnt an mathematische Funktionen stellen 'x' und 'y' reelle Zahlen
dar. Einfaches Beispiel: y = ax + b
Das sollte doch nachvollziehbar sein?
Stefan U. schrieb:> Offensichtlich ist hier in beiden Fällen entscheidend, dass bei der> Multiplikation eine Fließkommazahl verwendet wurde, bei der Division> jedoch nur Integer vorkommen.>> Da habe ich mal wieder etwas dazu gelernt.
Vergleich doch auch noch die Bereechnungen für a *= 10; und a /= 0.1;
;-)
Stefan U. schrieb:> Compiliert mit -O1 erzeugt die Multiplikation immer noch deutlich> umfangreicheren Code:> 00000080 <durch_zehn>:> 80: 9c 01 movw r18, r24> 82: ad ec ldi r26, 0xCD ; 205> 84: bc ec ldi r27, 0xCC ; 204> 86: 0e 94 70 00 call 0xe0 ; 0xe0 <__umulhisi3>
...
Siehe da, bei der Division mit konstanter 10 wird auch multipliziert.
Die Verwendung von float bei Multiplikation mit 0.1 macht es natürlich
aufwendiger.
Würde mich wundern, wenn das bei itoa nicht auch so gemacht würde.
m.n. schrieb:> Angelehnt an mathematische Funktionen stellen 'x' und 'y' reelle Zahlen> dar. Einfaches Beispiel: y = ax + b
Das steht ja jedem frei, es so zu halten, besonders, wenn er z.B. als
Prof Mathe- oder Informatik-Vorlesungen hält. Wenn ich aber in einer
Mikrocontroller-Anwendung z.B. in einer Integer-Variablen den Verfahrweg
in x-Richtung speichern will, benenne ich die Variable deshalb nicht in
"i" um, da ist mir Mathe und K&R Schnurz...
Und vom Namen auf den Typ zu schließen ist sicher auch nicht im Sinne
von K&R konsequent.
Daß x/10 anderen Code erzeugt, als x * 0.1 (mit x als int), ist doch
klar definiert: "0.1" ist eine reelle Zahl, wird also als float
behandelt, daher wird der 2. Operand immer intern vor der Operation auch
in float umgewandelt. "10" ist dagegen eine ganze Zahl, folglich wird
"x/10" als int/int = Interger-Division verarbeitet.
Achim schrieb:> Keine Rundungsfehler bis x==1024
auch darunter schon: 900 * 102 / 1024 = 89
m.n. schrieb:> Der Unterschied besteht im Programmierstil, der sich in Anlehnung an K&R> ergeben hat. Eine Character-Variable bekommt 'c', ein String 's',> Pointer vorzugsweise 'p' und 'q' und eine temporäre Variable 'temp' als> Bezeichner.> Angelehnt an mathematische Funktionen stellen 'x' und 'y' reelle Zahlen> dar. Einfaches Beispiel: y = ax + b
Den Typ einer Variablen im Namen zu spiegeln hatte zu Zeiten von K&R
noch Sinn. Heutzutage liefert so ziemlich jede IDE den Typ einer
Variablen, sobald der Mauszeiger über ihr steht.
Ein sinnvoller Variablenname soll repräsentieren, WAS in einer Variable
steht. Ggf. kann dazu die Angabe der Einheit des gespeicherten Werts
dazukommen.
Viele Grüße, Stefan
Thomas E. schrieb:> Wenn ich aber in einer> Mikrocontroller-Anwendung z.B. in einer Integer-Variablen den Verfahrweg> in x-Richtung speichern will, benenne ich die Variable deshalb nicht in> "a" um, da ist mir Mathe und K&R Schnurz...
Mir ist es Schnurz, ob Du Deinen Anfängerfehler bezüglich eines
Verfahrweges beibehalten willst oder nicht.
Bei mir werden diese Wege als 'float' bzw. 'double' in 'mm' verarbeitet.
Da ist es dann egal, ob ein Geber 10 µm, 2,5 µm, 0,2 µm oder 0,1µm
Auflösung hat. Man kann die Werte direkt miteinander verrechnen und bei
Bedarf ohne Verlust von Genauigkeit/Auflösung auch nach 'inch' wandeln.
Stefan K. schrieb:> Den Typ einer Variablen im Namen zu spiegeln hatte zu Zeiten von K&R> noch Sinn. Heutzutage liefert so ziemlich jede IDE den Typ einer> Variablen, sobald der Mauszeiger über ihr steht.
Dann gehe ich mal davon aus, daß Du die K&R Zeiten nicht miterlebt hast,
und immer eine IDE mit Mauszeiger in der Tasche durch die Gegend
schleppst.
Thomas E. schrieb:> m.n. schrieb:>> Angelehnt an mathematische Funktionen stellen 'x' und 'y' reelle Zahlen>> dar. Einfaches Beispiel: y = ax + b>> Das steht ja jedem frei, es so zu halten, besonders, wenn er z.B. als> Prof Mathe- oder Informatik-Vorlesungen hält.
Im Kontext der Fragestellung "Ich will eine Zahl in die String-
Darstellung umwandeln und muß dafür ja immer wieder durch 10
dividieren" kommt man genau gar nicht auf die Idee, bei dieser
Zahl könnte es sich um einen Fließkommatyp handeln.
Das war eine reine Schutzbehauptung von Mino, um seine dämliche
Aussage "x/10 ist genau das gleiche wie x*0.1" zu retten.
Das Verfahren, suzessive durch 10 zu dividieren und die Divisions-
Reste als Dezimalstellen der Zahl zu verwenden, funktioniert genau
nur für Integer-Zahlen. Bei Fließkomma gibt es ja keinen Rest.
Stefan K. schrieb:> Den Typ einer Variablen im Namen zu spiegeln hatte zu Zeiten> von K&R noch Sinn.
IMNSHO hatte diese sogenannte "ungarische Notation" noch nie
irgendeinen Vorteil. Na gut, man konnte die Größe des Quellcodes
etwas aufblähen und so Produktivität vortäuschen ...
Achim schrieb:> Sollte, wenn ich mich nicht täusche keinen Rundungsfehler haben.
Der Fehler dieser Rechnung ist etwa -0,4%. Und dieser Fehler wird sich
natürlich über die anzuzeigenden Ziffern auswirken.
Wenn z.B. 1000 angezeigt werden sollen, dann kommt beim stellenweisen
Dividieren mit Integern(!) tatsächlich ein Wert von 990 auf dem Display
heraus:
1
1000 -> Einer = 0
2
1000*102/1024 -> 99 -> Einer = 9
3
99 *102/1024 -> 9 -> Einer = 9
m.n. schrieb:> Lothar M. schrieb:>> Was ist da der Unterschied? Man kann auch a, b und c nehmen...> Der Unterschied besteht im Programmierstil, der sich in Anlehnung an K&R> ergeben hat. ....> Das sollte doch nachvollziehbar sein?
Jetzt nach dieser zweiseitigen Erklärung schon.
Allein: das ist mir zu kompliziert und ich gebe deshalb den Variablen
einfach solche Namen, die auch ohne Insiderwissen selbsterklärend sind.
Und ich muss heute nicht mehr an jede Zeichenkette ein s oder ein c oder
ein ul anhängen, weil sich nämlich die Editoren so weiterentwickelt
haben, dass sie mir diese "Merkarbeit" abnehmen. Der Editor zeigt mir
den Datentyp, ich muss ihn nicht mehr in den Variablennamen packen...
> Angelehnt an mathematische Funktionen stellen 'x' und 'y' reelle Zahlen> dar. Einfaches Beispiel: y = ax + b
Genau diese "Grundschulrechnerei" mit x und y hat uns unser Professor
seinerzeit am Anfang des ersten Semesters ganz bewusst ausgetrieben. Und
wenn ich mich nicht irre tauchen auch in einer der Mitternachtsformeln p
und q auf...
m.n. schrieb:> Dann gehe ich mal davon aus, daß Du die K&R Zeiten nicht miterlebt hast,> und immer eine IDE mit Mauszeiger in der Tasche durch die Gegend> schleppst.
Ganz im Gegenteil, ich schätze meinen K&R (eine der ersten Ausgaben)
sehr. Und ebenso schätze ich die Vorzüge moderner
Entwicklungsumgebungen.
Für die Variablen-Namensgebung ist zuallererst der Projekt Styleguide
relevant. Die von Dir genannte Namensgebung habe ich bisher aber noch in
keinem beschrieben gesehen.
Gruß, Stefan
Axel S. schrieb:> Das Verfahren, suzessive durch 10 zu dividieren und die Divisions-> Reste als Dezimalstellen der Zahl zu verwenden, funktioniert genau> nur für Integer-Zahlen.
Das Verfahren - welches ich selbst bei gewissen Aufgabenstellungen gern
anwende - hat einen gravierenden Nachteil: Du bekommst die Ziffern in
der Reihenfolge Einer-Zehner-Hunderter-Tausender. Für eine Ausgabe per
RS232 oder Display mußt Du sie also zwischenspeichern und in umgekehrter
Reihenfolge ausgeben.
Deswegen verwende ich in diesen Fällen gern obige Routine. Da muß die
While-Schleife für jede Dezimalstelle maximal Neunmal durchlaufen
werden. Bei einer Softdivision mit Integer läuft jede Stelle schon 16mal
(Shift durch 16 Bits) durch, zuzüglich Aufrufs der Divisionsroutine und
Variablenübergabe.
Die Konvention, daß alle Variablen, die mit I,J,K,L,M oder N beginnen,
Interger sind, und alle anderen REAL (float), stammt ursprünglich von
FORTRAN. Wenn man sich in FORTRAN daran hält, kann man die
Typvereinbarung weglassen, und spart ein paar Lochkarten.
Leo C. schrieb:> Die Konvention, daß alle Variablen, die mit I,J,K,L,M oder N beginnen,> Interger sind, und alle anderen REAL (float), stammt ursprünglich von> FORTRAN.
Ich nehme grundsätzlich nur 1- und 2-stellige Variablennamen in
Großbuchstaben, weil Basic das so wollte. Und Zeilennummern! Ganz
wichtig sind Zeilennummern!
Axel S. schrieb:> Das war eine reine Schutzbehauptung von Mino, um seine dämliche> Aussage "x/10 ist genau das gleiche wie x*0.1" zu retten.
Na, schlecht geschlafen letzte Nacht?
> Das Verfahren, suzessive durch 10 zu dividieren und die Divisions-> Reste als Dezimalstellen der Zahl zu verwenden, funktioniert genau> nur für Integer-Zahlen. Bei Fließkomma gibt es ja keinen Rest.
Wenn man den ganzzahligen Anteil subtrahiert, kann man auch mit dem Rest
weiterarbeiten. Aber ich sehe schon, Vielfalft ist out - Monotonie in.
Leo C. schrieb:> Die Konvention, daß alle Variablen, die mit I,J,K,L,M oder N beginnen,> Interger sind, und alle anderen REAL (float), stammt ursprünglich von> FORTRAN.
Oh, vor über 40 Jahren habe ich FORTRAN gemacht; kann sein, daß ich es
damals verinnerlicht hatte. C war ja erst deutlich später angesagt.
Timm T. schrieb:> Axel S. schrieb:>> Das Verfahren, suzessive durch 10 zu dividieren und die Divisions->> Reste als Dezimalstellen der Zahl zu verwenden, funktioniert genau>> nur für Integer-Zahlen.>> Das Verfahren - welches ich selbst bei gewissen Aufgabenstellungen gern> anwende - hat einen gravierenden Nachteil: Du bekommst die Ziffern in> der Reihenfolge Einer-Zehner-Hunderter-Tausender. Für eine Ausgabe per> RS232 oder Display mußt Du sie also zwischenspeichern und in umgekehrter> Reihenfolge ausgeben.
Ganz recht. Andererseits ist dieser Nachteil vergleichsweise simpel zu
kompensieren: man schreibt die Dezimalzahlen einfach beginnend vom Ende
rückwärts in den Buffer (den man für einen String ja ohnehin braucht).
Schlimmstenfalls muß man den String danach noch einmal kopieren. Der
Overhead dafür ist vergleichsweise gering - die Division dominiert die
Laufzeit der Schleife bei weitem. Außer vielleicht bei Architekturen die
nativ dividieren können.
Der Vorteil des Verfahrens ist, daß man es jedem Anfänger leicht
erklären kann. Das Gegenbeispiel wäre "Schieben mit BCD-Korrektur" aka
"double dabble" [1] aka "Dreierkorrektur" [2].
Die Tatsache, daß es jetzt nur noch eine Division mit einer Konstante
gibt, macht dem Compiler das Leben (die Optimierung) leichter.
[1] was ist das für ein bescheuerter Name?
[2] unter diesem Namen steht es in diversen Fachbüchern hier
Axel S. schrieb:> den man für einen String ja ohnehin braucht
Nope, für die Ausgabe an RS232 oder Display brauchst Du eben gerade
keinen extra String, wie obige Routine zeigt. Du schmeißt die Zeichen
einfach einzeln raus. Und bei RS232 wartest Du eh die meiste Zeit aufs
Senden, wenn Du nicht mit Sendebuffer* und Interrupt arbeitest.
*) Wobei der Sendebuffer hier wieder was anderes als der
Zeichenkettenstring ist.
> Das war eine reine Schutzbehauptung von Mino, um seine dämliche> Aussage "x/10 ist genau das gleiche wie x*0.1" zu retten.
Nein, du hast da eine Aussage von MIR (nicht Mino) in den falschen
Kontext gestellt.
Ich habe behauptet "x/10 ist genau das gleiche wie x*0.1", nachdem
jemand die Multiplikation als bessere Alternative zur Division empfohlen
hat.
Nun habe ich allerdings selbst herausgefunden und hier gezeigt, dass
diese beiden Schreibweisen doch nicht den selben Code ergeben und dass
die Multiplikation in diesem Fall sogar noch schlechter ist, als die
Division.
Hallo,
wenn ich eine Ganzzahl nach ASCII konvertieren will kann ich, wie
bereits diskutiert:
1.:
sukzessive durch 10 dividieren und den Rest als ASCII Zeichen speichern
2.:
10er Stellen sukzessive durch Subtraktion ermitteln und nach ASCII
wandeln, hier muss ich mit der grössten Stelle anfangen
Auf den ersten Blick ist die erste Methode elegant und kompakt. Ohne
Divisionsbefehl, der das Ergebnis mit Rest liefert, wird das ganze
jedoch kompliziert. Man braucht eine Näherung für x/10, die für alle
Werte von x genau ist und man muss den Rest auch noch bilden.
der Ansatz:
x/10 = x*26/256 ist genau bis x=68
x/10 = (x*25+x/2+x/8)/256 ist genau bis x=1048
...
Und den Rest muss man bilden mit r=x-div(x,10)*10.
Ich denke also die Methode sukzessive zu Subtrahieren ist besser als man
denkt.
Waldo
Waldo schrieb:> Ich denke also die Methode sukzessive zu Subtrahieren ist besser als man> denkt.
Ja, ich dachte auch erst: Das ist ja billig. Aber es ist sehr flexibel
auf verschiedene Stellenzahlen, Nachkommastellen, mit und ohne Leading
Zero usw. anpaßbar.
Double Dabble gefällt mir, aber ist schwerer durchschaubar und damit
fehleranfällig, wenn man es anpassen muß. Und es benötigt die
Zwischenspeicherstellen, nachträgliches Extrahieren aus den Nibbles und
Wandeln von BCD zu ASCII.
Timm T. schrieb:>> den man für einen String ja ohnehin braucht>> Nope, für die Ausgabe an RS232 oder Display brauchst Du eben gerade> keinen extra String, wie obige Routine zeigt. Du schmeißt die Zeichen> einfach einzeln raus.
Ich halte es für extrem schlechten Stil, Konvertierung und Ausgabe
derart "verzahnt" zu implementieren. IMNSHO gehört die Konvertierung in
eine extra Funktion (itoa läßt grüßen). Und dann muß der Rückgabewert
irgendwo zwischengespeichert werden.
Waldo schrieb:> Ich denke also die Methode sukzessive zu Subtrahieren ist besser> als man denkt.
Sie skaliert halt nicht so gut für große Zahlen. Überhaupt muß man den
möglichen Umfang des Wertebereichs vorher kennen; und wehe, wenn die
übergebene Zahl größer ist als die Routine erwartet. Man sollte hier
keine Abkürzung vornehmen und entweder den gesamten Wertebereich des
Argumenttyps abdecken oder zumindest auf Überschreitung des
Wertebereichs testen.
Die Variante mit der Division paßt sich ganz von selber an den Werte-
bereich an. Und sie ist trivial auf eine andere Zahlenbasis umzustellen.
Ich glaube statt dessen, der Aufwand der Division/Modulus-Operation wird
notorisch überschätzt.
> Ich halte es für extrem schlechten Stil, Konvertierung und> Ausgabe derart "verzahnt" zu implementieren.
Andere Leute halten es für schlechten Stil, bei Mikrocontrollern
Speicher zu vergeuden.
Axel S. schrieb:> Ich glaube statt dessen, der Aufwand der Division/Modulus-Operation wird> notorisch überschätzt.
Eine echte Division mit 2 Unbekannten ist schon recht aufwendig. Aber
eine Division durch eine Konstante wird von guten Compilern eh durch
Multiplikation und Shiften ersetzt. Siehe oben.
Andere halten es für schlechten Stil, aufgrund von irgendwelchem rein
gefühlten Optimierungsbedarf eine simple Division mit viel Arbeitszeit
in die Unwartbarkeit zu "optimieren", ohne belegen zu können, dass sich
das lohnt.
Und alle diese Sichtweisen sind Religionen, deshalb bringt jede
Diskussion rein gar nichts, außer dass alle Teilnehmer sich hinterher
gegenseitig für Idioten halten.
Axel S. schrieb:> Ich halte es für extrem schlechten Stil, Konvertierung und Ausgabe> derart "verzahnt" zu implementieren. IMNSHO gehört die Konvertierung in> eine extra Funktion (itoa läßt grüßen). Und dann muß der Rückgabewert> irgendwo zwischengespeichert werden.
Wem soll man es denn nun recht machen?
Programmiert man sauber und alles schön getrennt (womöglich noch mit
'float'-Zahlen), wird man angemacht, den µC gandenlos zu überlasten.
Programmiert man 'verzahnt', was für den Ablauf eines Programmes sehr
bechleunigend werden kann, wird man angemacht, keine Struktur im
Programm zu haben.
Im vorliegenden Fall kann ich Timms Ansicht nur bekräftigen. Zum einen
werden die Zeichen von MSD nach LSD gewandelt und können verzahnt per
UART aber auch auf ein LCD ausgegeben werden. Gerade bei Letzterem
erspart man sich die 40-50 µs Wartezeit/Zeichen bei der Ausgabe.
Stefan U. schrieb:>>> Für a=b/2 und a=b<<1 erzeugt der Compiler exakt den selben Bytecode.>> Bestimmt nicht.>> Sorry, die Pfeile sind natürlich falsch herum.
Wenn b signed ist und der Compiler mit Zweierkomplement rechnet, darf er
das nicht machen.
Für z.B. b = -3 ergibt b/2 das erwartete -1
und bei b>>1 kommt -2 heraus.
> Wenn b signed ist
Es geht um unsigned Integer. Außerdem bist du nicht der erste, der
darauf hinweist.
Es wurde alles mehrfach gesagt, nur noch nicht von jedem.
Axel S. schrieb:> Ich halte es für extrem schlechten Stil, Konvertierung und Ausgabe> derart "verzahnt" zu implementieren.
Ich halte es für extrem schlechten Stil, im µC ineffizient zu
programmieren.
Axel S. schrieb:> Sie skaliert halt nicht so gut für große Zahlen. Überhaupt muß man den> möglichen Umfang des Wertebereichs vorher kennen
Wenn ich im µC mit Ganzzahlen / Festkommazahlen arbeite, muß ich IMMER
darauf achten, in welchem Wertebereich ich mich bewege.
Und was das Skalieren angeht: Eine Long-Division in Software skaliert
besser als eine Subtraktion mit 4 Bytes? Wohl kaum.
Timm T. schrieb:> Axel S. schrieb:>> Ich halte es für extrem schlechten Stil, Konvertierung und Ausgabe>> derart "verzahnt" zu implementieren.>> Ich halte es für extrem schlechten Stil, im µC ineffizient zu> programmieren.
Das ist noch nicht raus, ob das wirklich ineffizient ist. Ich habe
letzthin z.B. viel µC Code geschrieben, wo die gleiche Ausgabe einmal
lokal auf das LCD und einmal per UART an den PC geht. An dieser Stelle
wäre es ineffizient, die Konvertierung zweimal zu machen.
Es kommt immer auf das Umfeld an. Aber eine Konvertierungsfunktion a'la
itoa() hat zumindest den Vorteil, universell zu sein.
>> Sie skaliert halt nicht so gut für große Zahlen. Überhaupt muß man den>> möglichen Umfang des Wertebereichs vorher kennen>> ... was das Skalieren angeht: Eine Long-Division in Software skaliert> besser als eine Subtraktion mit 4 Bytes? Wohl kaum.
Die Division muß man nur einmal pro Dezimalstelle machen. Die
Subtraktion bis zu neunmal. Bei der Laufzeit wird sich das nicht viel
nehmen, bei der Codegröße gewinnt mit Sicherheit die Division. Mit um so
mehr Abstand, je größer die Zahlen werden.
Aber falls das so rüber gekommen sein sollte: ich will auf keinen Fall
die Methode des suzessiven Dividierens als silver bullet hinstellen.
Allein schon deswegen, weil es so etwas gar nicht gibt. Was die beste
Lösung ist, variiert mit der konkreten Aufgabe und der konkreten
Hardware.
Markus W. schrieb:> Mal 1.6 und durch 16 Teilen?
Ach was! Nägel mit Köpfen machen und durch NULL teilen. Dann sieht man
wenigstens, ob der Kompiler fehlertolerant ist.
SCNR
Paul
Axel S. schrieb:> Die Division muß man nur einmal pro Dezimalstelle machen. Die> Subtraktion bis zu neunmal.
Oha. Du weißt aber schon, was bei einer Software-Division passiert?
Ich verrate nur so viel: Schleife und jedes Bit darf mal vor.
Timm T. schrieb:> Axel S. schrieb:>> Die Division muß man nur einmal pro Dezimalstelle machen. Die>> Subtraktion bis zu neunmal.>> Oha. Du weißt aber schon, was bei einer Software-Division passiert?>> Ich verrate nur so viel: Schleife und jedes Bit darf mal vor.
Ahh. Es hat noch jemand gemerkt. Sehr schön.
Axel S. schrieb:> Ich halte es für extrem schlechten Stil, Konvertierung und Ausgabe> derart "verzahnt" zu implementieren.
Warum sollte das schlecht sein? Wenn eh' nur zur Ausgabe diese
Konvertierung nötig ist (der absolute Regelfall, denn wer rechnet schon
mit Zahlen in dezimaler ASCII-Repräsentation!!!), dann wäre es sogar
extrem dumm, Speicher und Rechenzeit zu verschwenden, nur um das zu
formal zu trennen.
Natürlich gibt es Anwendungsfälle, wo es trotzdem sinnvoll ist, das zu
trennen, z.B. wenn eine tabellierte Ausgabe wünschenswert ist. Dann tut
man es halt, aber eben nur dann, wenn sich daraus auch ein sachlicher
Vorteil ergibt, und nicht nur, um irgendeiner mehr oder weniger
idiotischen "Stil"-Vorgabe zu genügen...
"Form follows function". Das ist die einzig akzeptable Stilvorgabe.
c-hater schrieb:> Natürlich gibt es Anwendungsfälle, wo es trotzdem sinnvoll ist, das zu> trennen, z.B. wenn eine tabellierte Ausgabe wünschenswert ist.
Auch da und gerade da. Zum Bleistift habe ich bei meiner
Heizungssteuerung alle Temperaturen als Festpunktzahl mit einer
Dezimalstelle gespeichert. Für jede Temperatur, die auf dem Display
dargestellt wird, wird dann die gleiche Routine aufgerufen, die die
Gleitpunktzahl auseinandernimmt, in ASCII wandelt, führende Nullen durch
Leerzeichen ersetzt (damit die Position auf dem Display stimmt) und ein
°C dranhängt. Dieser Routine muß ich nur den Wert übergeben und bekomme
das Ergebnis aufs Display. Wenn ich das erst über itoa, String usw.
machen wollte, wäre das deutlich umständlicher.