In Beitrag "Wurzefunktion für uint32_t in C" werden verschiedene
Algorithmen zum Wurzelziehen aus 32bit Werten angeboten.
Insbesondere die Assembler Funktion von Johann L. ist sehr effektiv.
Möchte man die Wurzel nun aus einem Fixed Point Wert U(a,b) mit b
Nachkommastellen ziehen kann man diese Funktion verwenden:
Pseudocode für b=8
1
uint16_tfixQ8val=X;// mit X = float * 2^b
2
uint16_tres;
3
res=sqrt32(val);
4
res=mult_u16_fixQ8(res,Y);// mit Y = sqrt(2^b)*2^b
Im vorliegenden Sonderfall von b=8 ist eine einfache (nicht fixed point)
Multiplikation möglich. Da sqrt(2^8)=16 kann man ohne
Genauigkeitsverlust direkt Multiplizieren.
Die Abarbeitung ist ebenfalls recht flott (sqrt32~400cycle; mul_u16~50).
Der Nachteil hierbei ist die mangelnde Genauigkeit, da die sqrt32
Funktion den Wert gerundet zurückgibt und u.U. zusätzliche
Rundungsfehler beim umwandeln der Konstante sqrt(2^b) in fixed point
Repräsentation entstehen.
Eine genauere Funktion wurde von Turkowski (Turkowski, Ken, Fixed Point
Square Root, p. 22-24, code: p. 23 -
http://tog.acm.org/resources/GraphicsGems/) für 32bit fixed point Werte
veröffentlicht.
Für 16bit fixed Point Werte sieht die Funktion so aus:
1
int16_tsqrt16_fixQ8(int16_ta)
2
{
3
uint16_ttestDiv=0;
4
uint16_troot=0;
5
uint16_tremHi=0;
6
uint16_tremLo=(int16_t)a;
7
uint8_tcount=11;//(15 + b)/2
8
do
9
{
10
remHi=(remHi<<2)|(remLo>>14);
11
remLo<<=2;
12
root<<=1;
13
testDiv=(root<<1)+1;
14
if(remHi>=testDiv)
15
{
16
remHi-=testDiv;
17
root+=1;
18
}
19
}
20
while(count--!=0);
21
return(int16_t)root;
22
}
Für andere Werte von b muss lediglich die Variable count entsprechend
initialisiert werden.
Das Ergebnis ist deutlich genauer als das mit vorheriger vorgehensweise
allerdings braucht der von gcc erstellte Code bei mir ~1700cycles!
Ich habe die Funktion deshalb in Assembler umgesetzt
or REMHI0, TEMP0 ; remhi = remhi<<2(which is REMHI1:REMHI0) | remlo>>14(which is 0x00:TEMP0)
49
; REMHI1 stays REMHI1, since or with 0x00
50
51
lsl REMLO0 ; remlo <<= 2
52
rol REMLO1
53
lsl REMLO0
54
rol REMLO1
55
56
lsl ROOT0 ; root <<= 1
57
rol ROOT1
58
59
movw TESTDIV0, ROOT0 ; testdiv = (root<<1) + 1
60
lsl TESTDIV0
61
rol TESTDIV1
62
subi TESTDIV0,-1 ; addi 1
63
sbci TESTDIV1,-1 ; adci 0
64
65
; if(remhi >= testdiv)
66
cp REMHI1, TESTDIV1 ; carry set if REMHI1 < TESTDIV1, Z set if REMHI1 == TESTDIV1
67
breq sqrt16_fixQ8_test_low ; when Z is set test low byte
68
brsh sqrt16_fixQ8_same_higher ; when carry is not set REMHI1 >= TESTDIV,
69
; the case == is covered above so reaching this means REMHI1 > TESTDIV
70
rjmp sqrt16_fixQ8_lower ; when carry was set REMHI1 is smaller
71
72
sqrt16_fixQ8_test_low: cp REMHI0, TESTDIV0 ; carry is set if REMHI0 < TESTDIV0
73
brlo sqrt16_fixQ8_lower ; leave if carry was set
74
rjmp sqrt16_fixQ8_same_higher ; when carry was not set REMHI0 >= TESTDIV0
75
76
sqrt16_fixQ8_same_higher: sub REMHI0, TESTDIV0 ; remhi -= testdiv
77
sbc REMHI1, TESTDIV1
78
79
subi ROOT0,-1 ; root += 1
80
sbci ROOT1,-1
81
82
83
sqrt16_fixQ8_lower: dec CNT
84
brne sqrt16_fixQ8_loop
85
86
movw r24, ROOT0 ; store result in R25:R24
87
ret
88
89
#undef TESTDIV0
90
#undef TESTDIV1
91
92
#undef ROOT0
93
#undef ROOT1
94
95
#undef REMLO0
96
#undef REMLO1
97
98
#undef REMHI0
99
#undef REMHI1
100
101
#undef TEMP0
102
#undef TEMP1
103
104
#undef CNT
105
106
.endfunc
Sie läuft mit ~500cycle fast so schnell wie die zuerst genannte
Möglichkeit bei höherer Genauigkeit.
Ich hoffe die Funktion ist für jemanden nützlich. Über
Verbesserungsvorschläge im Assemblercode oder Anmerkungen zu versteckten
Fehlern (bin Assembler Neuling) würde ich mich freuen.
Viele Grüße
>res=mult_u16_fixQ8(res,Y);// mit Y = sqrt(2^b)*2^b
5
>
Spricht bei dir was gegen 4^b? Dass ist einfacher und zudem ohne Fehler
zu wurzeln.
> Im vorliegenden Sonderfall von b=8 ist eine einfache (nicht fixed point)> Multiplikation möglich. Da sqrt(2^8)=16 kann man ohne> Genauigkeitsverlust direkt Multiplizieren.> Die Abarbeitung ist ebenfalls recht flott (sqrt32~400cycle; mul_u16~50).
mul_u16~50??? Doch hoffentlich auf einer Maschine ohne MUL*.
> Der Nachteil hierbei ist die mangelnde Genauigkeit, da die sqrt32> Funktion den Wert gerundet zurückgibt und u.U. zusätzliche> Rundungsfehler beim umwandeln der Konstante sqrt(2^b) in fixed point> Repräsentation entstehen.>> Eine genauere Funktion wurde von Turkowski (Turkowski, Ken, Fixed Point> Square Root, p. 22-24, code: p. 23 -> http://tog.acm.org/resources/GraphicsGems/) für 32bit fixed point Werte> veröffentlicht.> Für 16bit fixed Point Werte sieht die Funktion so aus:>> [c]> int16_t sqrt16_fixQ8(int16_t a)> {> uint16_t testDiv = 0;> uint16_t root = 0;> uint16_t remHi = 0;> uint16_t remLo = (int16_t)a;> uint8_t count = 11; //(15 + b)/2> do> {> remHi =(remHi << 2) | (remLo >> 14);> remLo <<= 2;
Das geht wesentlich schmerzfreier mit
1
LSL remLo.lo
2
ROL remLo.hi
3
ROL remHi.lo
4
ROL remHi.hi
5
6
LSL remLo.lo
7
ROL remLo.hi
8
ROL remHi.lo
9
ROL remHi.hi
> root <<= 1;> testDiv = (root << 1) + 1;
Das ist schlicht (da Bit 0 vor der Addition = 0 ist):
1
SEC
2
ROL root.lo
3
ROL root.hi
4
;; oder
5
LSL root.lo
6
ROL root.hi
7
INC root.lo
Ich hab nicht auf's Interface geschaut, ist das avr-gcc ABI?
Wär sinnvoll...
Johann L. schrieb:> Spricht bei dir was gegen 4^b? Dass ist einfacher und zudem ohne Fehler> zu wurzeln.
Dabei kann ich dir nicht ganz folgen, was meinst du damit?
Johann L. schrieb:>mul_u16~50??? Doch hoffentlich auf einer Maschine ohne MUL*.
mh ne ist mit mul, aber ich ich seh grad das ich in meinem test die
signed multiplikation erwischt habe.
Johann L. schrieb:> LSL remLo.lo> ROL remLo.hi> ROL remHi.lo> ROL remHi.hi>> LSL remLo.lo> ROL remLo.hi> ROL remHi.lo> ROL remHi.hi
Da kann ich dir wieder nicht folgen.. kann ich das so in den C code
reinschreiben?
Johann L. schrieb:> Ich hab nicht auf's Interface geschaut, ist das avr-gcc ABI?> Wär sinnvoll...
Als Neuling kann ich damit auch net so viel anfangen... ich hab
AVRStudio 5 installiert.
Viele Grüße
StefanK schrieb:> Johann L. schrieb:>> LSL remLo.lo>> ROL remLo.hi>> ROL remHi.lo>> ROL remHi.hi>>>> LSL remLo.lo>> ROL remLo.hi>> ROL remHi.lo>> ROL remHi.hi
Ach du meinst im Assember code... jetzt seh ichs...
das ist ja tatsächlich deutlich angenehmer. Danke!
Wenn ich sowas auch nur mal sehen würde... bei mir werden das immer ganz
wirre konstrukte
StefanK schrieb:> Johann L. schrieb:>> Spricht bei dir was gegen 4^b? Dass ist einfacher und zudem ohne Fehler>> zu wurzeln.>> Dabei kann ich dir nicht ganz folgen, was meinst du damit?
Oben schreibst du was von Rundingsfehlern bei der Darstellung von
wenn b gerade ist, was bei dir offenbar so ist, dann schreibt sich b als
b = 2·b' und also
Ich sehe nicht, wo's da einen Rundungsfehler hat, denn die Darstellung
ist im 2er-System. Auch negative b' sind ohne Rundungsfehler
darzustellen. Bei der Division (ein Links-Shift, da b' < 0) gibt's auch
keine Rundungsfehler.
Freilich verlierst du damit wie korrekt angemerkt Genauigkeit, bzw. sie
ist durch den 32-Bit-Wurzler garnicht vorhanden.
> Johann L. schrieb:>>mul_u16~50??? Doch hoffentlich auf einer Maschine ohne MUL*.>> mh ne ist mit mul, aber ich ich seh grad das ich in meinem test die> signed multiplikation erwischt habe.
50 Ticks für ne 16-bit signed-Multiplikation ist immer nocht stolz. Du
hast Optimierung aktiviert?
> Johann L. schrieb:>> Ich hab nicht auf's Interface geschaut, ist das avr-gcc ABI?>> Wär sinnvoll...>> Als Neuling kann ich damit auch net so viel anfangen... ich hab> AVRStudio 5 installiert.
Vielleicht nur eine Fehlinterpretation von mir: Du verwendest ein Mix
von Assembler und C? Oder ist es ein reines Assembler-Projekt.
Im zweiten Falle ist das ABI egal.
Im ersten Falle will man idR die Assembler-Funktion von C aus aufrufen,
weil man sich nicht den Wolf machen will das komplette Projekt in asm zu
klöppeln. Dann muss allerdings die Aufruf-Konvention von avr-gcc
eingehalten werden; siehe zB
http://www.rn-wissen.de/index.php/Avr-gcc/Interna#Registerverwendung
Johann L. schrieb:> LSL root.lo> ROL root.hi>> CP remHi.lo, testDiv.lo> CPC remHi.hi, testDiv.hi>> BRLO 0f>> SUB remHi.lo, testDiv.lo> SBC remHi.hi, testDiv.hi> INC root.lo>> 0:
Eine Instruktion kann man da noch sparen wenn ich mich net irre:
1
LSL root.lo
2
ROL root.hi
3
4
SUB remHi.lo, testDiv.lo
5
SBC remHi.hi, testDiv.hi
6
7
BRSH 0f
8
9
MOVW remHi, testDiv
10
11
0:
12
SBCI root.lo, -1
Allerdings muss root.lo dann in einem Register >= R16 sein und man
brauch ein Device mit MOVW. In dem Falle geht dann auch die
Initialisierung mit 0 kürzer: Anstatt CLR, CLR, CLR, CLR anfangs geht
CLR, CLR, MOVW.
Johann L. schrieb:> wenn b gerade ist, was bei dir offenbar so ist, dann schreibt sich b als> b = 2·b'
Ach, alles klar, das geht - hab ich verstanden. Bleibt das Problem, dass
sqrt32 immernoch gerundete Werte zurückgibt. Das kann man mit dem o.g.
Algorithmus umgehen.
Dank deiner Verbesserungen ist er sogar flotter als sqrt32 alleine.
Johann L. schrieb:> Dann muss allerdings die Aufruf-Konvention von avr-gcc> eingehalten werden; siehe zB
Ja das beachte ich.
Johann L. schrieb:> Eine Instruktion kann man da noch sparen wenn ich mich net irre:> LSL root.lo> ROL root.hi>> SUB remHi.lo, testDiv.lo> SBC remHi.hi, testDiv.hi>> BRSH 0f>> MOVW remHi, testDiv>> 0:> SBCI root.lo, -1
geht hier nicht, für den fall das remhi<testdiv, mein remhi verloren?
movw setzt ja einfach testdiv ein, nicht das alte remhi?
Johann L. schrieb:> 50 Ticks für ne 16-bit signed-Multiplikation ist immer nocht stolz. Du> hast Optimierung aktiviert?
Nein Optimierung ist nicht aktiv. Die Multiplikation ist allerdings auch
eine Assembler Routine (32bit = 16bit*16bit). Die Zeit zum Aufrufen
(Registerzuweisungsgeplänkel von gcc) ist allerdings in den 50 cycles
dabei.