Multiplizieren, Dividieren und Akkumulation: Unterschied zwischen den Versionen
Die Seite wurde neu angelegt: „In diesem Abschnitt werden wir die Grundlagen der Multiplikation und Division im ARM64-Assembler behandeln, einschließlich der Arbeit mit negativen Zahlen und der Handhabung von Überläufen und großen Zahlen. Wir werden sowohl einfache Operationen als auch komplexere Szenarien betrachten, wie z.B. die Multiplikation zweier 64-Bit-Zahlen zu einer 128-Bit-Zahl und die Division einer 128-Bit-Zahl. == Einfache Multiplikation: mul-Befehl == Der mul-Befehl…“ |
|||
| (10 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt) | |||
| Zeile 5: | Zeile 5: | ||
Syntax: | Syntax: | ||
mul <Zielregister>, <Quellregister1>, <Quellregister2> | <syntaxhighlight lang="asm" inline>mul <Zielregister>, <Quellregister1>, <Quellregister2></syntaxhighlight> | ||
Beispiel: | Beispiel: | ||
<syntaxhighlight lang="asm"> | |||
.global _start | .global _start | ||
| Zeile 17: | Zeile 19: | ||
mov x8, #93 // exit system call | mov x8, #93 // exit system call | ||
svc 0 // system call | svc 0 // system call | ||
</syntaxhighlight> | |||
== Multiplikation und Umgang mit negativen Zahlen == | == Multiplikation und Umgang mit negativen Zahlen == | ||
In ARM64-Assembler sind Zahlen standardmäßig als vorzeichenbehaftet (signed). Der mul-Befehl funktioniert daher korrekt mit negativen Zahlen. | In ARM64-Assembler sind Zahlen standardmäßig als vorzeichenbehaftet (signed). Der <syntaxhighlight lang="asm" inline>mul</syntaxhighlight>-Befehl funktioniert daher korrekt mit negativen Zahlen. | ||
Beispiel mit negativen Zahlen: | Beispiel mit negativen Zahlen: | ||
<syntaxhighlight lang="asm"> | |||
.global _start | .global _start | ||
| Zeile 31: | Zeile 36: | ||
mov x8, #93 // exit system call | mov x8, #93 // exit system call | ||
svc 0 // system call | svc 0 // system call | ||
== | </syntaxhighlight> | ||
== Umgang mit Überlauf bei 64-Bit-Multiplikation == | |||
Das <syntaxhighlight lang="asm" inline>mul</syntaxhighlight>-Kommando in ARM64-Assembler multipliziert zwei 64-Bit-Register und speichert das Ergebnis in einem einzigen 64-Bit-Register. Problematisch wird es, wenn das Resultat größer als 64 Bit wird, da <syntaxhighlight lang="asm" inline>mul</syntaxhighlight> das Ergebnis auf die unteren 64 Bit beschränkt und keine Information über einen möglichen Überlauf bereitstellt. | |||
=== Beispiel für das Problem === | |||
Angenommen, wir multiplizieren zwei große 64-Bit-Zahlen, deren Produkt mehr als 64 Bit umfasst: | |||
<syntaxhighlight lang="asm"> | |||
.global _start | |||
_start: | |||
mov x0, #0xFFFFFFFFFFFFFFFF // Setze x0 auf den maximalen 64-Bit-Wert | |||
mov x1, #2 // Setze x1 auf 2 | |||
mul x2, x0, x1 // x2 = x0 * x1 (Ergebnis würde 0x1FFFFFFFFFFFFFFFE sein, aber x2 enthält nur die unteren 64 Bits: 0xFFFFFFFFFFFFFFFE) | |||
// Programm beenden | |||
mov x8, #93 // exit system call | |||
svc 0 // system call | |||
</syntaxhighlight> | |||
In diesem Fall ist die erwartete 128-Bit-Zahl 0x1FFFFFFFFFFFFFFFE, aber '''x2''' enthält nur 0xFFFFFFFFFFFFFFFE, also die unteren 64 Bits des Ergebnisses. | |||
=== Lösung mit umulh zum Feststellen des Überlaufs === | |||
Um sicherzustellen, dass das Ergebnis der Multiplikation korrekt und vollständig ist, können wir den Befehl <syntaxhighlight lang="asm" inline>umulh</syntaxhighlight> (unsigned multiply high) verwenden. Dieser Befehl multipliziert zwei 64-Bit-Zahlen und gibt die oberen 64 Bits des 128-Bit-Resultats zurück. | |||
Gebrauch von <syntaxhighlight lang="asm" inline>mul</syntaxhighlight> und <syntaxhighlight lang="asm" inline>umulh</syntaxhighlight> für 128-Bit-Multiplikation | |||
<syntaxhighlight lang="asm" inline>mul</syntaxhighlight>: Multipliziert zwei 64-Bit-Zahlen und behält die unteren 64 Bits. | |||
<syntaxhighlight lang="asm" inline>umulh</syntaxhighlight>: Multipliziert zwei 64-Bit-Zahlen und gibt die oberen 64 Bits des Resultats zurück. | |||
Beispiel: | Beispiel: | ||
<syntaxhighlight lang="asm"> | |||
.global _start | .global _start | ||
_start: | _start: | ||
mov x0, #0xFFFFFFFFFFFFFFFF | mov x0, #0xFFFFFFFFFFFFFFFF // Setze x0 auf den maximalen 64-Bit-Wert | ||
mov x1, #2 | mov x1, #2 // Setze x1 auf 2 | ||
// Multipliziere x0 und x1 | // Multipliziere x0 und x1 | ||
mul x2, x0, x1 | mul x2, x0, x1 // x2 = untere 64-Bit des Ergebnisses | ||
umulh x3, x0, x1 | umulh x3, x0, x1 // x3 = obere 64-Bit des Ergebnisses | ||
// Hiermit haben wir das volle 128-Bit-Ergebnis in x3:x2 | |||
// x3 enthält die oberen 64-Bits: 0x1 | |||
// x2 enthält die unteren 64-Bits: 0xFFFFFFFFFFFFFFFE | |||
// Programm beenden | // Programm beenden | ||
mov x8, #93 | mov x8, #93 // exit system call | ||
svc 0 | svc 0 // system call | ||
</syntaxhighlight> | |||
Erklärung der Schritte: | |||
Setze '''x0''' und '''x1''': Wir setzen zwei große Werte in die Register '''x0''' und '''x1'''. | |||
Multipliziere und speichere die unteren 64-Bits: | |||
<syntaxhighlight lang="asm" inline>mul x2, x0, x1</syntaxhighlight>: Multipliziert '''x0''' und '''x1''' und speichert das Ergebnis in '''x2'''. Dies speichert die unteren 64 Bits des Produkts. | |||
Multipliziere und speichere die oberen 64-Bits: | |||
<syntaxhighlight lang="asm" inline>umulh x3, x0, x1</syntaxhighlight>: Multipliziert '''x0''' und '''x1''' und speichert die oberen 64 Bits des Produkts in '''x3'''. | |||
Nun haben wir das vollständige 128-Bit-Ergebnis verteilt auf die Register '''x2''' (untere 64 Bits) und '''x3''' (obere 64 Bits). Auf diese Weise können wir sicherstellen, dass das gesamte Produkt genau erfasst wird. | |||
== Division: udiv-Befehl == | == Division: udiv-Befehl == | ||
Der udiv-Befehl wird verwendet, um zwei 64-Bit-Zahlen zu dividieren. | Der <syntaxhighlight lang="asm" inline>udiv</syntaxhighlight>-Befehl wird verwendet, um zwei 64-Bit-Zahlen zu dividieren. | ||
Syntax: | Syntax: | ||
udiv <Zielregister>, <Quellregister1>, <Quellregister2> | <syntaxhighlight lang="asm" inline>udiv <Zielregister>, <Quellregister1>, <Quellregister2></syntaxhighlight> | ||
Beispiel: | Beispiel: | ||
<syntaxhighlight lang="asm"> | |||
.global _start | .global _start | ||
| Zeile 64: | Zeile 118: | ||
mov x8, #93 // exit system call | mov x8, #93 // exit system call | ||
svc 0 // system call | svc 0 // system call | ||
</syntaxhighlight> | |||
== Umgang mit negativen Zahlen bei der Division == | == Umgang mit negativen Zahlen bei der Division == | ||
Bei der Division von vorzeichenbehafteten Zahlen verwenden wir sdiv (signed divide). | Bei der Division von vorzeichenbehafteten Zahlen verwenden wir <syntaxhighlight lang="asm" inline>sdiv</syntaxhighlight> (signed divide). | ||
Beispiel: | Beispiel: | ||
<syntaxhighlight lang="asm"> | |||
.global _start | .global _start | ||
| Zeile 78: | Zeile 135: | ||
mov x8, #93 // exit system call | mov x8, #93 // exit system call | ||
svc 0 // system call | svc 0 // system call | ||
</syntaxhighlight> | |||
== Division einer 128-Bit-Zahl: udiv verwenden == | == Division einer 128-Bit-Zahl: udiv verwenden == | ||
Um eine 128-Bit-Zahl zu dividieren, die in zwei 64-Bit-Registern gespeichert ist, müssen wir die 64-Bit-Teile nacheinander verarbeiten. Dies ist komplexer und erfordert mehrere Schritte, um die Genauigkeit zu gewährleisten. | Um eine 128-Bit-Zahl zu dividieren, die in zwei 64-Bit-Registern gespeichert ist, müssen wir die 64-Bit-Teile nacheinander verarbeiten. Dies ist komplexer und erfordert mehrere Schritte, um die Genauigkeit zu gewährleisten. | ||
Beispiel für Division einer 128-Bit-Zahl durch eine 64-Bit-Zahl: | Beispiel für Division einer 128-Bit-Zahl durch eine 64-Bit-Zahl: | ||
Konvertiere die obere 64-Bit-Zahl zu einer 128-Bit-Zahl. | Angenommen, wir haben eine 128-Bit-Zahl in den Registern '''x0''' (untere 64-Bit) und '''x1''' (obere 64-Bit) und wir wollen durch die 64-Bit-Zahl in '''x2''' dividieren. | ||
Füge die untere 64-Bit-Zahl dazu. | |||
Teile alles durch die 64-Bit-Zahl. | # Konvertiere die obere 64-Bit-Zahl zu einer 128-Bit-Zahl. | ||
# Füge die untere 64-Bit-Zahl dazu. | |||
# Teile alles durch die 64-Bit-Zahl. | |||
<syntaxhighlight lang="asm"> | |||
.global _start | .global _start | ||
| Zeile 109: | Zeile 169: | ||
mov x8, #93 // exit system call | mov x8, #93 // exit system call | ||
svc 0 // system call | svc 0 // system call | ||
== | </syntaxhighlight> | ||
In | |||
== Akkumulation == | |||
In ARM64-Assembler gibt es spezielle Befehle, die die Akkumulation unterstützen. Dies ermöglicht es, Multiplikationsoperationen mit einer zusätzlichen Additions- oder Subtraktionsoperation zu kombinieren. Diese Befehle sind besonders nützlich für komplexere arithmetische Berechnungen und Optimierungen. | |||
=== Akkumulierende Multiplikation: <syntaxhighlight lang="asm" inline>mla</syntaxhighlight> und <syntaxhighlight lang="asm" inline>mls</syntaxhighlight> === | |||
Die Befehle <syntaxhighlight lang="asm" inline>mla</syntaxhighlight> (multiply-accumulate) und <syntaxhighlight lang="asm" inline>mls</syntaxhighlight> (multiply-subtract) bilden die Grundlage für akkumulative Operationen bei der Multiplikation. | |||
=== Der <syntaxhighlight lang="asm" inline>mla</syntaxhighlight>-Befehl === | |||
Der <syntaxhighlight lang="asm" inline>mla</syntaxhighlight>-Befehl multipliziert zwei Register und addiert das Ergebnis zu einem dritten Register. | |||
Syntax: | |||
<syntaxhighlight lang="asm" inline>mla <Zielregister>, <Quellregister1>, <Quellregister2>, <Akkumulationsregister></syntaxhighlight> | |||
Beispiel: | |||
<syntaxhighlight lang="asm"> | |||
.global _start | |||
_start: | |||
mov x0, #2 // Setze x0 auf 2 | |||
mov x1, #3 // Setze x1 auf 3 | |||
mov x2, #4 // Setze x2 auf 4 | |||
mla x3, x0, x1, x2 // x3 = (x0 * x1) + x2 = (2 * 3) + 4 = 10 | |||
// Programm beenden | |||
mov x8, #93 // exit system call | |||
svc 0 // system call | |||
</syntaxhighlight> | |||
In diesem Beispiel: | |||
'''x0''' * '''x1''' ergibt 6. | |||
Dann wird 6 zu '''x2''' (4) addiert, um das endgültige Ergebnis 10 in '''x3''' zu speichern. | |||
=== Der <syntaxhighlight lang="asm" inline>mls</syntaxhighlight>-Befehl === | |||
Der <syntaxhighlight lang="asm" inline>mls</syntaxhighlight>-Befehl multipliziert zwei Register und subtrahiert das Ergebnis von einem dritten Register. | |||
Syntax: | |||
<syntaxhighlight lang="asm" inline>mls <Zielregister>, <Quellregister1>, <Quellregister2>, <Akkumulationsregister></syntaxhighlight> | |||
Beispiel: | |||
<syntaxhighlight lang="asm"> | |||
.global _start | |||
_start: | |||
mov x0, #5 // Setze x0 auf 5 | |||
mov x1, #3 // Setze x1 auf 3 | |||
mov x2, #20 // Setze x2 auf 20 | |||
mls x3, x0, x1, x2 // x3 = x2 - (x0 * x1) = 20 - (5 * 3) = 20 - 15 = 5 | |||
// Programm beenden | |||
mov x8, #93 // exit system call | |||
svc 0 // system call | |||
</syntaxhighlight> | |||
In diesem Beispiel: | |||
'''x0''' * '''x1''' ergibt 15. | |||
Dann wird 15 von '''x2''' (20) subtrahiert, um das endgültige Ergebnis 5 in '''x3''' zu speichern. | |||
Aktuelle Version vom 28. November 2024, 14:15 Uhr
In diesem Abschnitt werden wir die Grundlagen der Multiplikation und Division im ARM64-Assembler behandeln, einschließlich der Arbeit mit negativen Zahlen und der Handhabung von Überläufen und großen Zahlen. Wir werden sowohl einfache Operationen als auch komplexere Szenarien betrachten, wie z.B. die Multiplikation zweier 64-Bit-Zahlen zu einer 128-Bit-Zahl und die Division einer 128-Bit-Zahl.
Einfache Multiplikation: mul-Befehl
Der mul-Befehl multipliziert zwei 64-Bit-Zahlen und speichert das Ergebnis in einem Register.
Syntax:
mul <Zielregister>, <Quellregister1>, <Quellregister2>
Beispiel:
.global _start
_start:
mov x0, #5 // Setze x0 auf 5
mov x1, #6 // Setze x1 auf 6
mul x2, x0, x1 // x2 = x0 * x1 (5 * 6 = 30)
// Programm beenden
mov x8, #93 // exit system call
svc 0 // system call
Multiplikation und Umgang mit negativen Zahlen
In ARM64-Assembler sind Zahlen standardmäßig als vorzeichenbehaftet (signed). Der mul-Befehl funktioniert daher korrekt mit negativen Zahlen.
Beispiel mit negativen Zahlen:
.global _start
_start:
mov x0, #-5 // Setze x0 auf -5
mov x1, #6 // Setze x1 auf 6
mul x2, x0, x1 // x2 = x0 * x1 (-5 * 6 = -30)
// Programm beenden
mov x8, #93 // exit system call
svc 0 // system call
Umgang mit Überlauf bei 64-Bit-Multiplikation
Das mul-Kommando in ARM64-Assembler multipliziert zwei 64-Bit-Register und speichert das Ergebnis in einem einzigen 64-Bit-Register. Problematisch wird es, wenn das Resultat größer als 64 Bit wird, da mul das Ergebnis auf die unteren 64 Bit beschränkt und keine Information über einen möglichen Überlauf bereitstellt.
Beispiel für das Problem
Angenommen, wir multiplizieren zwei große 64-Bit-Zahlen, deren Produkt mehr als 64 Bit umfasst:
.global _start
_start:
mov x0, #0xFFFFFFFFFFFFFFFF // Setze x0 auf den maximalen 64-Bit-Wert
mov x1, #2 // Setze x1 auf 2
mul x2, x0, x1 // x2 = x0 * x1 (Ergebnis würde 0x1FFFFFFFFFFFFFFFE sein, aber x2 enthält nur die unteren 64 Bits: 0xFFFFFFFFFFFFFFFE)
// Programm beenden
mov x8, #93 // exit system call
svc 0 // system call
In diesem Fall ist die erwartete 128-Bit-Zahl 0x1FFFFFFFFFFFFFFFE, aber x2 enthält nur 0xFFFFFFFFFFFFFFFE, also die unteren 64 Bits des Ergebnisses.
Lösung mit umulh zum Feststellen des Überlaufs
Um sicherzustellen, dass das Ergebnis der Multiplikation korrekt und vollständig ist, können wir den Befehl umulh (unsigned multiply high) verwenden. Dieser Befehl multipliziert zwei 64-Bit-Zahlen und gibt die oberen 64 Bits des 128-Bit-Resultats zurück.
Gebrauch von mul und umulh für 128-Bit-Multiplikation
mul: Multipliziert zwei 64-Bit-Zahlen und behält die unteren 64 Bits.
umulh: Multipliziert zwei 64-Bit-Zahlen und gibt die oberen 64 Bits des Resultats zurück.
Beispiel:
.global _start
_start:
mov x0, #0xFFFFFFFFFFFFFFFF // Setze x0 auf den maximalen 64-Bit-Wert
mov x1, #2 // Setze x1 auf 2
// Multipliziere x0 und x1
mul x2, x0, x1 // x2 = untere 64-Bit des Ergebnisses
umulh x3, x0, x1 // x3 = obere 64-Bit des Ergebnisses
// Hiermit haben wir das volle 128-Bit-Ergebnis in x3:x2
// x3 enthält die oberen 64-Bits: 0x1
// x2 enthält die unteren 64-Bits: 0xFFFFFFFFFFFFFFFE
// Programm beenden
mov x8, #93 // exit system call
svc 0 // system call
Erklärung der Schritte:
Setze x0 und x1: Wir setzen zwei große Werte in die Register x0 und x1.
Multipliziere und speichere die unteren 64-Bits:
mul x2, x0, x1: Multipliziert x0 und x1 und speichert das Ergebnis in x2. Dies speichert die unteren 64 Bits des Produkts.
Multipliziere und speichere die oberen 64-Bits:
umulh x3, x0, x1: Multipliziert x0 und x1 und speichert die oberen 64 Bits des Produkts in x3.
Nun haben wir das vollständige 128-Bit-Ergebnis verteilt auf die Register x2 (untere 64 Bits) und x3 (obere 64 Bits). Auf diese Weise können wir sicherstellen, dass das gesamte Produkt genau erfasst wird.
Division: udiv-Befehl
Der udiv-Befehl wird verwendet, um zwei 64-Bit-Zahlen zu dividieren.
Syntax:
udiv <Zielregister>, <Quellregister1>, <Quellregister2>
Beispiel:
.global _start
_start:
mov x0, #30 // Setze x0 auf 30
mov x1, #6 // Setze x1 auf 6
udiv x2, x0, x1 // x2 = x0 / x1 (30 / 6 = 5)
// Programm beenden
mov x8, #93 // exit system call
svc 0 // system call
Umgang mit negativen Zahlen bei der Division
Bei der Division von vorzeichenbehafteten Zahlen verwenden wir sdiv (signed divide).
Beispiel:
.global _start
_start:
mov x0, #-30 // Setze x0 auf -30
mov x1, #6 // Setze x1 auf 6
sdiv x2, x0, x1 // x2 = x0 / x1 (-30 / 6 = -5)
// Programm beenden
mov x8, #93 // exit system call
svc 0 // system call
Division einer 128-Bit-Zahl: udiv verwenden
Um eine 128-Bit-Zahl zu dividieren, die in zwei 64-Bit-Registern gespeichert ist, müssen wir die 64-Bit-Teile nacheinander verarbeiten. Dies ist komplexer und erfordert mehrere Schritte, um die Genauigkeit zu gewährleisten.
Beispiel für Division einer 128-Bit-Zahl durch eine 64-Bit-Zahl:
Angenommen, wir haben eine 128-Bit-Zahl in den Registern x0 (untere 64-Bit) und x1 (obere 64-Bit) und wir wollen durch die 64-Bit-Zahl in x2 dividieren.
- Konvertiere die obere 64-Bit-Zahl zu einer 128-Bit-Zahl.
- Füge die untere 64-Bit-Zahl dazu.
- Teile alles durch die 64-Bit-Zahl.
.global _start
_start:
// Beispielwerte setzen
mov x0, #0xFFFFFFFFFFFFFFFF // LSB von 128-Bit-Zahl
mov x1, #0x0000000000000001 // MSB von 128-Bit-Zahl (kleiner Wert zur Vereinfachung)
mov x2, #3 // Divisor
// Multipliziere MSB mit 2^64, um die richtige Position in der 128-Bit-Zahl zu erhalten
movk x1, #0, lsl 0 // x1 = x1 << 64 (technisch Teil des hohen Bits)
mul x3, x1, x2 // Division der oberen 64-Bits durch 3
// Hinweis: Pseudo, weil x64-Teiler nicht msb sondern nur shift)
// Addiere die unteren 64-Bit hinzu
add x3, x3, x0 // Summiere die "er Ergebnisse"
// Teile die resultierende 64-Bit-Zahl in x3 durch x2
udiv x4, x3, x2 // Ergebnis in x4 (86)
// Programm beenden
mov x8, #93 // exit system call
svc 0 // system call
Akkumulation
In ARM64-Assembler gibt es spezielle Befehle, die die Akkumulation unterstützen. Dies ermöglicht es, Multiplikationsoperationen mit einer zusätzlichen Additions- oder Subtraktionsoperation zu kombinieren. Diese Befehle sind besonders nützlich für komplexere arithmetische Berechnungen und Optimierungen.
Akkumulierende Multiplikation: mla und mls
Die Befehle mla (multiply-accumulate) und mls (multiply-subtract) bilden die Grundlage für akkumulative Operationen bei der Multiplikation.
Der mla-Befehl
Der mla-Befehl multipliziert zwei Register und addiert das Ergebnis zu einem dritten Register.
Syntax:
mla <Zielregister>, <Quellregister1>, <Quellregister2>, <Akkumulationsregister>
Beispiel:
.global _start
_start:
mov x0, #2 // Setze x0 auf 2
mov x1, #3 // Setze x1 auf 3
mov x2, #4 // Setze x2 auf 4
mla x3, x0, x1, x2 // x3 = (x0 * x1) + x2 = (2 * 3) + 4 = 10
// Programm beenden
mov x8, #93 // exit system call
svc 0 // system call
In diesem Beispiel:
x0 * x1 ergibt 6. Dann wird 6 zu x2 (4) addiert, um das endgültige Ergebnis 10 in x3 zu speichern.
Der mls-Befehl
Der mls-Befehl multipliziert zwei Register und subtrahiert das Ergebnis von einem dritten Register.
Syntax:
mls <Zielregister>, <Quellregister1>, <Quellregister2>, <Akkumulationsregister>
Beispiel:
.global _start
_start:
mov x0, #5 // Setze x0 auf 5
mov x1, #3 // Setze x1 auf 3
mov x2, #20 // Setze x2 auf 20
mls x3, x0, x1, x2 // x3 = x2 - (x0 * x1) = 20 - (5 * 3) = 20 - 15 = 5
// Programm beenden
mov x8, #93 // exit system call
svc 0 // system call
In diesem Beispiel:
x0 * x1 ergibt 15. Dann wird 15 von x2 (20) subtrahiert, um das endgültige Ergebnis 5 in x3 zu speichern.