Im Folgenden werden wir in x86-64-Assembler die Pi-Funktion float pi(long n);
schreiben, welche für positive n folgende Formel berechnet:
Vorlage: https://openasp.aengelke.net/m/pi.tar.gz — Für die Bearbeitung der Aufgabe müssen Sie lediglich die enthaltene Assembler-Datei bearbeiten.
Laden Sie zunächst die Konstante 4.0
in ein SSE-Register. Nutzen Sie hierzu zunächs die Assembler-Direktiven .section .rodata
und .float
, um die Konstante zu definieren. In Ihrem Programm können Sie diesen Wert dann mit der Instruktion movss
in ein SSE-Register laden.
.section .rodata
.Lconst4:
.float 4.0
.text
.global pi
pi:
movsd xmm3, [rip + .Lconst4]
// ...
Alternativ kann die Konstante auch in IEEE-754-Darstellung in ein Integer-Register bewegt werden und dann in ein SSE-Register kopiert werden:
mov eax, 0x40800000
movd xmm3, eax
Theoretisch auch möglich ist die Nutzung von cvtsi2ss
. Dies ist aber nicht zu empfehlen, da die solche Konvertierungsinstruktionen vergleichsweise langsam sind.
mov eax, 4
cvtsi2ss xmm3, eax // typically takes 3-4 cycles
Initialisieren Sie dann Ihr akkumulierendes SSE-Register sowie Ihren Schleifencounter.
Aufgrund von Ungenauigkeiten bei der Rechnung mit Fließkommazahlen ist es empfehlenswert, die Schleife von n→0 iterieren zu lassen.
Sie werden eine Schleife benötigen, arbeiten Sie hierfür mit lokalen Labels. Sie können das unterste Bit des Schleifen-Zählers zur Entscheidung über Addition und Subtraktion verwenden. Inwiefern hilft Ihnen hierfür die Instruktion test
?
Die Instruktion test
führt ein logisches “und” durch und setzt das Zero-Flag genau dann wenn das Ergebnis der Operation 0 ist. In diesem Fall verwendet man also test reg, 1
, um das unterste Bit zu prüfen. Dies kann dann mit jz
(Zero-Flag gesetzt) oder jnz
abgefragt werden.
.intel_syntax noprefix
.global pi
.text
pi:
mov eax, 0x40800000 // 4.0 as float
movd xmm3, eax
xorps xmm0, xmm0 // xmm0 = sum = 0
.Lloop:
lea rdx, [2*rdi + 1] // rdx = 2k+1
cvtsi2ss xmm2, rdx // xmm2 = 2k+1
movss xmm1, xmm3 // xmm1 = 4
divss xmm1, xmm2 // xmm1 = 4/(2k+1)
test rdi, 1
jnz .Lsub // if k is odd subtract
addss xmm0, xmm1 // xmm0 = sum + 4/(2k+1)
jmp .Lcont
.Lsub:
subss xmm0, xmm1 // xmm1 = sum - 4/(2k+1)
.Lcont:
sub rdi, 1 // k--
jns .Lloop // loop while k >= 0
ret
Die Reihe konvergiert nur sehr langsam und Single-Precision-Fließkommazahlen haben eine relativ geringe Genauigkeit, weswegen selbst bei einer Obergrenze von 106 das Ergebnis der Berechnung 3.141593
(echter Wert 3,1415926...
) relativ früh abweicht.