Berechnung von Pi

Im Folgenden werden wir in x86-64-Assembler die Pi-Funktion float pi(long n); schreiben, welche für positive n folgende Formel berechnet:

π=k=0n(1)k42k+1

Vorlage: https://openasp.aengelke.net/m/pi.tar.gz — Für die Bearbeitung der Aufgabe müssen Sie lediglich die enthaltene Assembler-Datei bearbeiten.

  1. 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.

    Lösung
      .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
    
  2. Initialisieren Sie dann Ihr akkumulierendes SSE-Register sowie Ihren Schleifencounter.

    Lösung

    Aufgrund von Ungenauigkeiten bei der Rechnung mit Fließkommazahlen ist es empfehlenswert, die Schleife von n→0 iterieren zu lassen.

  3. 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?

    Lösung

    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.

Lösung
  .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.