Hallo,
programmiere gerade einen Attiny2313 in Assembler.
Habe auch schon fast alles hinbekommen was ich mir vorgestellt habe.
Nur jetzt stehe ich aufm Schlauch.
Möchte eine Ganzzahl in 2 Registern durch eine fixe Kommazahl teilen.
Ganzzahlen: 1 bis 4000
Feste Kommazahl: 0,8789
Also z.B. 1800/0,8789=2048
Beim Ergebnis benötige ich keine Nachkommastellen.
Ich hoffe mir kann jemand einen Denkanstoß geben.
Vielen Dank schonmal
Peter Stuckl schrieb:> Hallo,>> programmiere gerade einen Attiny2313 in Assembler.>> Habe auch schon fast alles hinbekommen was ich mir vorgestellt habe.>> Nur jetzt stehe ich aufm Schlauch.>> Möchte eine Ganzzahl in 2 Registern durch eine fixe Kommazahl teilen.
Dividieren ist etwas aufwendig. Ich würde es vermeiden, indem ich mit
dem Reziprokwert der fixen Zahl multipliziere.
>> Ganzzahlen: 1 bis 4000>> Feste Kommazahl: 0,8789>> Also z.B. 1800/0,8789=2048
Da bietet sich an, mit 291 zu multiplizieren und das untere Byte
wegzuwerfen, um durch 256 zu teilen.
Also 1800 mal 291 (ergibt drei Bytes)
Nur die beiden oberen Bytes beachten (ersetzt eine Division durch 256)
Denn: Der Reziprokwert von 0,8789 ist 1,1377858.
Dies wird mit 256 multipliziert (Bruch erweitert) und ergibt 291,27...
Der Fehler beträgt knapp 3/256, also gut 0,1%.
Die Erweiterung mit 256 wird dann durch Weglassen des unteren Bytes
kompensiert. Wenn es sein muss, kann man zuvor noch 128 addieren um das
Ergebnis zu runden.
Das wäre dann: 1800 x 291 (= 523800) / 256 = 2046
>> Beim Ergebnis benötige ich keine Nachkommastellen.
Die wären bei Bedarf aber da.
>> Ich hoffe mir kann jemand einen Denkanstoß geben.
Die Multiplikation mit 291 erreichst Du übrigens durch 9 mal Shiften
(über 3 Bytes) und 3 Additionen (je 3 Bytes). ;-)
>> Vielen Dank schonmal
Such dir nen Bruch der möglichst nahe an deiner Zahl liegt.
Dann multiplizierst du ganzzahlig deine Zahl mit dem Zähler (auf
Überlaufe achten -> Zahlenbereich), und dann dividerst du durch den
Nenner.
Ideal wäre ein Bruch, der im Nenner eine 2er-Potenz hat, dann kann man
das Dividieren durch Schieben ersetzen.
Viel dank für die schnellen und zahlreichen Antworten.
1800*289/254= 2048
Das kommt dem ganzen noch am nächsten denke ich.
Was mich aber stört sind die 3 Bytes (1800*289).
Aber das bekomme ich wohl hin.
@ Peter Stuckl (Gast)
>1800*289/254= 2048
Das hat aber KEINER erzählt.
Eher
1 / 0,8789 = 1,1377859 = 291 / 256
Macht einen Fehler von 0,93%, Hmmm.
>Das kommt dem ganzen noch am nächsten denke ich.
Ich würde auf 1165 / 1024 gehen, macht 0,08% Fehler für gerade mal zwei
Bit mehr.
MFG
Falk
Da ich kleine kompakte Umrechnungen öfter brauche, habe ich mal
mein Verfahren als Code-Schnipsel angefügt.
Die Multiplikationsroutine kann man immer brauchen,
das Ergebnis ist nicht exakt, aber optimal genau.
Hi
>; K = 1 / 0,8789>;>; Konstante muss < 1 sein, also:>;>; A / 0,8789 = A * 1,137786>: = A 2 0,568893>; = A 2 (0,568893 * 65536) / 65536>; = A 2 37283 / 65536
Die Berechnung kann der Assembler2 auch allein:
0,8789r*65536=74565,93469109113 hex 1.2345 binär 1.0010001101000101
das genaue Ergebnis für 4000 ist
4000/0,8789=4551,1434747
d.h. Du kopierst die Zahl in ein Summierregister (alles
16bit-Operationen)
shiftest die Zahl um 3 nach rechts und addierst ins Summierregister
shiftest die Zahl um weitere 4 nach rechts und addierst ins Register
shiftest die Zahl um weitere 1 nach rechts und addierst ins Register
shiftest die Zahl um weitere 2 nach rechts und addierst ins Register
shiftest die Zahl um weitere 4 nach rechts und addierst ins Register *)
fertig - Ergebnis steht im Summierregister
das sind 2*(1+4+5+2+3)=30 Befehle
*) diese Zeile bringt nur etwas, wenn wie unten beschrieben vorher nach
links geschoben wurde
dann sind es 2*(1+4+5+2+3+5)=40 Befehle
Wenn's etwas schneller, dafür ungenauer sein darf
(/0,8767123287 entspricht *1,140625):
d.h. Du kopierst die Zahl in ein Summierregister (alles
16bit-Operationen)
shiftest die Zahl um 3 nach rechts und addierst ins Summierregister
shiftest die Zahl um 3 nach rechts und addierst ins Summierregister
4000 wird so zu 4000+500+62=4562
so sind es 2*(1+4+4)=18 Befehle
Wenns genauer sein soll: Der Wert 4000 passt in 12 Bit, also zuerst um
1-4 Bits nach links schieben, dann wie oben beschrieben rechnen und
Ergebnis wieder um 1-4 Bits nach rechts schieben.
Das kann man noch optimieren:
Zahl ins Register kopieren
Zahl um 3 nach links schieben und ins Register addieren
Originalzahl um 4 nach rechts schieben und ins Register addieren
weiter wie oben
zum Schluss Ergebnis wieder um 3 nach rechts
Das Runden bringt auch mehr Genauigkeit:
Einfach das "Nachkommabit" auf das Ergebnis addieren.
also beim letzten Beispiel nur noch das Carry addieren (adc #0), da sich
das Nachkommabit nach dem letzten shr im Carry befindet.
Falls noch Fragen, bitte fragen!
Mich würde interessieren, wie Du es gelöst hast.
eProfi schrieb:> Nein, ich meine schon nach rechts schieben. Zum Schluss soll ja nichts> mehr übrigbleiben.> Nochmal zur Verdeutlichung ein Beispiel:> 1. 0 0 1 0 0 0 1 1 0 1 0 0 0 1 0 1> 4000 2000 1000 500 250 125 62 31 15 7 3 1> 4000+ 500+ 31+15+ 3 =4549>> Und das Beispiel mit erhöhter Genauigkeit durch "shl 3":> 1. 0 0 1 0 0 0 1 1 0 1 0 0 0 1 0 1> 32000 16000 8000 4000 2000 1000 500 250 125 62 31 15 7 3 1> 32000+ 4000+ 250+125+ 31+ 1=36407> 36407/8=4550,875 Runden ergibt 4551, da das Nachkommabit 1 ist.>>> Wieso hättest Du auf Linksschieben getippt?
Sorry, ich bin von meinem Vorschlag mit der Multiplikation mit 291 und
Pseudo-Division durch 256 (unteres Byte wegwerfen) ausgegangen:
Beitrag "Re: AVR Assembler Ganzzahl durch Kommazahl"
Dies wäre mit Linksschieben und Addieren recht einfach zu erledigen. 291
ist ja 0b100100011. Das sind 9 Bit mit insgesamt 4 Einsern, also 9
Links-Shifts mit 3 Additionen (da wo die Einser stehen).
Aber was soll's, es sind genügend verschiedene Vorschläge gemacht
worden, ich erwarte daher nicht, dass mein Vorschlag umgesetzt wird. ;-)