Eigentlich wollte ich meine Kiste vor dreieinhalb Stunden
abstürzen lassen herunterfahren, doch angeregt
hiervon wollte ich mir einmal kurz die Power-Implementierung der
RTL anschauen. Bei "kurz" ist es zwar nicht ganz geblieben, aber ich darf nun behaupten, den Code verstanden zu haben; mit der Zeit bin ich ebenfalls sehr zufrieden, denn es gab da ein kleines Problem: Ich beherrsche kein Wort Assembler
[1] . Ausgestattet allein mit dem Verständnis des abstrakten Stack-Typs (soll heißen: Push und Pop
) und einer Webseite über FPU-Instruktionen habe ich mich also debuggend und kommentierend durch den
Asm-Code gequält. Das Resultat sieht so aus [2]:
Delphi-Quellcode:
function Power(
const Base, Exponent: Extended): Extended;
const
Max : Double = MaxInt;
var
IntExp : Integer;
asm
// Kommentare stellen den FPU-Stack dar: // ST0, ST1
fld Exponent
// b
[...]
//schnipp
fld Base
// a, b
[...]
// schnipp
fldln2
// ln 2, a, b
fxch
// a, ln 2, b
fyl2x
// ln 2 * ld a, b
fxch
// b, ln 2 * ld a
fmulp st(1), st
// ln 2 * ld a * b
fldl2e
// ld e, ln 2 * ld a * b
fmulp st(1), st
// ld e * ln 2 * ld a * b = ld a * b = x
fld st(0)
// x, x
frndint
// Round(x), x
fsub st(1), st
// Round(x), x - Round(x) // sagen wir einfach Int(x) und Frac(x), auch wenn wir ja auch aufgerundet haben könnten // siehe auch [url]http://t-a-w.blogspot.com/2006/06/docking-assembly.html[/url]
fxch st(1)
// Frac(x), Int(x)
f2xm1
// 2^Frac(x) - 1, x
fld1
// 1, 2^Frac(x) - 1, x
faddp st(1), st
// 1 + 2^Frac(x)-1 = 2^Frac(x)
fscale
// 2^Frac(x) * 2^Int(x) = 2^z = 2^(ld a * b) = a^b
end;
Das Ergebnis stimmt, trotzdem müsst ihr mir noch beim Füllen zweier Verständnislücken assistieren: Denn die Funktion zweier Teile habe ich zwar begriffen, doch ... kommen mir sie einfach überflüssig vor
.
I
Delphi-Quellcode:
fxch // b, ln 2 * ld a
fmulp st(1), st // ln 2 * ld a * b
Bei der Kommentierung dieses Codestücks wurde ich vage an ein Ding namens Kommutativgesetz erinnert... Kurz und knapp: ich sehe keinen Grund, weshalb man die Register vertauschen sollte, wenn man danach sowieso beide in einer Multiplikation kombiniert.
II
fmulp st(1), st // ld e * ln 2 * ld a * b = ld a * b = x
Nicht gerade das bekannteste Logarithmusgesetz, aber ihr dürft es gern selbst nachrechnen: log_a(b) * log_b(a) = 1, womit die Umformung im Kommentar gültig ist. Da der Code "einfach nur" 2^(ld a * b) berechnet, muss an dieser Stelle natürlich auch ld a * b herauskommen, das wussten also auch die Entwickler des Codes - doch wozu dann überhaupt das ld e und ln 2, wozu so kompliziert?
Fazit:
Nun denke ich natürlich in beiden Punkten zuerst, dass dies einfach zwei Tricks in diesem Machwerk hochperformaten Assemblers sei, doch irgendetwas lässt mich daran zweifeln... ist wohl die Tatsache, dass meine eigengebaute Power-Routine, die diese zwei Punkte verbessert, 2% schneller ist als das Original
.
Hier noch der verbesserte Abschnitt:
Delphi-Quellcode:
fld1 { fldln2 }
fxch // a, >1<, b
fyl2x // >1< * ld a, b
{ fxch // b, ln 2 * ld a }
fmulp st(1), st // ld a * b = x
{ fldl2e // ld e, ln 2 * ld a * b
fmulp st(1), st // ld e * ln 2 * ld a * b = ld a * b }
Mal sehen, ob der Titel ansprechend genug formuliert ist
.
[1]Ich beherrsche wahrscheinlich sogar
CIL besser als Assembler - allein durch den Reflector
.
[2]Hoffe mal, das Ausmaß der zitierten Code-Abschnitte liegt noch im (Copyright-)Rahmen.
Nacht ihr
.