Pfad:
Home =>
AVR-Überblick =>
Programmiertechniken =>
Fließkommazahlen => Mantissen-Multiplikation
(This page in English:
)
Programmiertechnik für Anfänger in AVR Assemblersprache
Multiplikation der Mantissen von Fließkommazahlen in Assembler
Fließkomma-Multiplikation: ja, wenn nötig
Um zwei Fließkommazahlen miteinander zu multiplizieren muss man Folgendes tun:
- Addieren der beiden Exponenten, und
- Multiplizieren der beiden Mantissen.
Während der erste Teil ziemlich einfach geht, wird es, zumindest bei
Mantissen, die sich aus mehreren Bytes zusammensetzen, etwas komplizierter.
Um zu demonstrieren, dass Fließkommas nicht so einfach zu handhaben
sind, wird hier die Multiplikation von zwei 56-Bit-Mantissen
(64-Bit-Fließkomma mit 8-Bit-Exponent) eingehender beschrieben.
Am Ende steht, da bin ich ganz sicher: lass die Finger von Fließkomma,
sie sind keine gute und effektive Wahl.
56-Bit-Fließkomma-Mantissen
Hier sehen wir zwei solche 56-Bit-Mantissen-Monster zum Multiplizieren.
Wie Du schon gelernt hast, beginnt es mit dezimal 0,5, dann 0,25 usw. Je
weiter rechts das Bit steht, desto geringer ist sein Wert. Jedes weitere
Bit ist zwei mal weniger wert als das links von ihm Stehende. Das
drücken die 2-N-Zahlen aus.
Wenn wir beide Mantissen multiplizieren, kriegen wir eine ähnliche
Fließkomma-Mantisse wie die beiden Ausgangs-Mantissen. Wenn wir
allerdings zum Beispiel 2-56 (dezimal ca. 1E-17) mit
2-32 malnehmen, kriegen wir ein Ergebnis von 2-88
heraus, was dezimal 1E-27 entspricht und daher gar nicht mehr im
Ergebnis ins Gewicht fällt. Um diesen Rest noch ein wenig zum Runden
verwenden zu können, hängen wir an die 56-Bit-Zahl noch mal
16 Bits dran und landen bei 2-72 als kleinstem Beitrag.
Diese 16 Extra-Bits verwenden wir später zum Runden.
Ich habe die Bytes der zwei Mantissen als Z1N und Z2N benannt, wobei
N von 1 bis 7 reicht. Die Bytes der Mantisse heißen ResN (Res =
Result oder Ergebnis), sie reichen von N=1 bis N=9 (sieben plus zwei
Extra-Bytes zum Runden.
Um beide Mantissen in Assembler zu multiplizieren habe ich für
alle 7 + 7 + 9 Bytes Register zugeordnet. Dabei habe ich die
Register-Reihenfolge umgekehrt: je niedriger das Register, desto
höher sein Wert. Alle Zahlen (Z1, Z2, Res) beginnen mit 1 (Z11,
Z21, Res1) und nicht mit 0. Reine Festlegungssache.
Man könnte diese Bytes statt in Registern auch im SRAM speichern,
aber das kostet viele zusätzliche Instruktionen und verlängert
die Ausführungszeiten immens. Hat man nicht so viele Register frei,
macht man das so.
Die Mathematik der Multiplikation
Nun bestehen unsere Fließkomma-Mantissen aus jeweils sieben Bytes,
jedes ist um das 2-8-fache weniger wert als sein linker Nachbar.
Wenn wir beide multiplizieren wollen, brauchen wir die binären
Exponenten, die hinter allen Registern unsichtbar stehen: 2-8
für Z11/Z21/Res1, 2-16 für Z12/Z22/Res2, etc.
Die Bytes werden mit der Zweierpotenz mit den negativen Exponenten
multipliziert, um sie zu Z1, Z2 und Res aufzusummieren. Das ergibt
jeweils Z11*2-8, Z12*2-16, Z13*2-24,
usw.
Da jede der zwei Zahlen aus sieben Bytes besteht, jeweils mit ihren
Zweierpotenzen multipliziert, müssen wir jedes Byte von Z1 mit jedem
Byte von Z2 multiplizieren und dieses zum Ergebnis in Res addieren.
Hier sind alle Formeln der Multiplikation versammelt. Da beide Zahlen
als Summe in den beiden Klammern stehen, müssen diese Klammern
aufgelöst werden, indem alle Z1N mit allen Z2N multipliziert
werden müssen. Da sich beim Multiplizieren die Zweier-Exponenten
addieren erhalten wir Multiplikatoren zwischen 2-16 (aus
2-8 * 2-8) und 2-112 (aus
2-56 * 2-56). Zahlen, die kleiner sind als
2-72, kriegen einen roten Hintergrund: sie tragen nicht
genügend zum Ergebnis bei.
Das Ergebnis ist die Summe aus allen diesen einzelnen Multiplikationen.
Zu Beginn ist das Ergebnis zu initiieren, normalerweise würde
man dieses Null setzen. Dieser Schritt kann aber entfallen, wenn wir
die gelb hinterlegten Multiplikationen durchführen und mit diesen
Ergebnissen die Summe initiieren. Nur Res9 ist etwas ungewöhnlich,
da nur das MSB ohne das LSB geschrieben werden muss.
Die Multiplikation der Z1N mit den Z2N verwendet den eingebauten
Hardware-Multiplikator. Die MUL-Instruktion wird mit den zwei Registern
aufgerufen und liefert das Multiplikationsergebnis im Registerpaar
R1:R0. Diese beiden Bytes werden an der zutreffenden Stelle zum
Ergebnis addiert. Eventuell bei der Addition auftretende
Überläufe werden zu den höherwertigen Registern hinzu
addiert.
Der Assembler-Quellcode
Der Assembler-Quellcode (hier in
.asm-Format) ist einfach gestrickt. Die beiden 56-Bit-Zahlen Z1 und Z2
werden zu Beginn gesetzt und zum Programmstart in die betreffenden
Register geladen.
Bitte beachten, dass der Quellcode weder Unterprogramme noch Makros
oder Verzweigungen verwendet und daher ziemlich gleichförmig
daherkommt. Beim Addieren des Registerpaars R1:R0 auftretende
Überläufe werden durch ADC einer Null in die
höherwertigen Register weitergegeben. Würden stattdessen
Verzweigungen mit gelöschtem Übertragsbit eingefügt,
wäre der Quellcode zwar länger, aber schneller. Allerdings
macht das die Ausführungszeit abhängig von den beiden
Zahlenwerten.
Die 286 Instruktionen des Codes, von denen die ersten 28 nur die
beiden Konstanten in die Register verfrachten, benötigen bei
1 MHz Takt 321 µs. Diese Dauer ist nicht von den
beiden Zahlenwerten beeinflusst.
Der Code:
;
; ****************************************
; * Multiply two 56-bit float mantissas *
; * with a hardware multiplier, here m48 *
; * (C)2022 by Gerhard Schmidt *
; ****************************************
;
.nolist
.include "m48adef.inc" ; Define device ATmega48A
.list
;
; Multiplies two 56-bit float mantissas by using
; the hardware multiplier
;
; Test numbers
.equ Z1 = 0x7FFFFFFFFFFFFF
.equ Z2 = 0x7FFFFFFFFFFFFF
;
; **********************************
; R E G I S T E R S
; **********************************
;
; Used: R1:R0 for multiplication
.def rZ11 = R2 ; Z1, byte 1
.def rZ12 = R3 ; dto., byte 2
.def rZ13 = R4 ; dto., byte 3
.def rZ14 = R5 ; dto., byte 4
.def rZ15 = R6 ; dto., byte 5
.def rZ16 = R7 ; dto., byte 6
.def rZ17 = R8 ; dto., byte 7
.def rZ21 = R9 ; Z2, byte 1
.def rZ22 = R10 ; dto., byte 2
.def rZ23 = R11 ; dto., byte 3
.def rZ24 = R12 ; dto., byte 4
.def rZ25 = R13 ; dto., byte 5
.def rZ26 = R14 ; dto., byte 6
.def rZ27 = R15 ; dto., byte 7
.def rmp = R16 ; Define multipurpose register
.def rDummy = R17 ; Not used
.def rRes1 = R18 ; Result, byte 1
.def rRes2 = R19 ; dto., byte 2
.def rRes3 = R20 ; dto., byte 3
.def rRes4 = R21 ; dto., byte 4
.def rRes5 = R22 ; dto., byte 5
.def rRes6 = R23 ; dto., byte 6
.def rRes7 = R24 ; dto., byte 7
.def rRes8 = R25 ; dto., byte 8, rounding MSB
.def rRes9 = R26 ; dto., byte 9, rounding LSB
; free: R27 to R31
;
.cseg
.org 000000
; **********************************
; M A I N P R O G R A M I N I T
; **********************************
;
Main:
; Init the mantissas
ldi rmp,Byte3(Z1 / (1<<32))
mov rZ11,rmp
ldi rmp,Byte2(Z1 / (1<<32))
mov rZ12,rmp
ldi rmp,Byte1(Z1 / (1<<32))
mov rZ13,rmp
ldi rmp,Byte4(Z1)
mov rZ14,rmp
ldi rmp,Byte3(Z1)
mov rZ15,rmp
ldi rmp,Byte2(Z1)
mov rZ16,rmp
ldi rmp,Byte1(Z1)
mov rZ17,rmp
ldi rmp,Byte3(Z2 / (1<<32))
mov rZ21,rmp
ldi rmp,Byte2(Z2 / (1<<32))
mov rZ22,rmp
ldi rmp,Byte1(Z2 / (1<<32))
mov rZ23,rmp
ldi rmp,Byte4(Z2)
mov rZ24,rmp
ldi rmp,Byte3(Z2)
mov rZ25,rmp
ldi rmp,Byte2(Z2)
mov rZ26,rmp
ldi rmp,Byte1(Z2)
mov rZ27,rmp
;
; Multiply the mantissas
mul rZ11,rZ21 ; Init the result registers
mov rRes2,R0
mov rRes1,R1
mul rZ13,rZ21
mov rRes4,R0
mov rRes3,R1
mul rZ15,rZ21
mov rRes6,R0
mov rRes5,R1
mul rZ17,rZ21
mov rRes8,R0
mov rRes7,R1
mul rZ13,rZ27
mov rRes9,R1
; Multiply Z11
clr rmp ; Adder for carry
mul rZ11,rZ22
add rRes3,R0
adc rRes2,R1
adc rRes1,rmp
mul rZ11,rZ23
add rRes4,R0
adc rRes3,R1
adc rRes2,rmp
adc rRes1,rmp
mul rZ11,rZ24
add rRes5,R0
adc rRes4,R1
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
mul rZ11,rZ25
add rRes6,R0
adc rRes5,R1
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
mul rZ11,rZ26
add rRes7,R0
adc rRes6,R1
adc rRes5,rmp
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
mul rZ11,rZ27
add rRes8,R0
adc rRes7,R1
adc rRes6,rmp
adc rRes5,rmp
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
; Multiply rZ12
mul rZ12,rZ21
add rRes3,R0
adc rRes2,R1
adc rRes1,rmp
mul rZ12,rZ22
add rRes4,R0
adc rRes3,R1
adc rRes2,rmp
adc rRes1,rmp
mul rZ12,rZ23
add rRes5,R0
adc rRes4,R1
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
mul rZ12,rZ24
add rRes6,R0
adc rRes5,R1
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
mul rZ12,rZ25
add rRes7,R0
adc rRes6,R1
adc rRes5,rmp
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
mul rZ12,rZ26
add rRes8,R0
adc rRes7,R1
adc rRes6,rmp
adc rRes5,rmp
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
mul rZ12,rZ27
add rRes9,R0
adc rRes8,R1
adc rRes7,rmp
adc rRes6,rmp
adc rRes5,rmp
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
; Multiply Z13
mul rZ13,rZ22
add rRes5,R0
adc rRes4,R1
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
mul rZ13,rZ23
add rRes6,R0
adc rRes5,R1
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
mul rZ13,rZ24
add rRes7,R0
adc rRes6,R1
adc rRes5,rmp
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
mul rZ13,rZ25
add rRes8,R0
adc rRes7,R1
adc rRes6,rmp
adc rRes5,rmp
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
mul rZ13,rZ26
add rRes9,R0
adc rRes8,R1
adc rRes7,rmp
adc rRes6,rmp
adc rRes5,rmp
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
; Multiply Z14
mul rZ14,rZ21
add rRes5,R0
adc rRes4,R1
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
mul rZ14,rZ22
add rRes6,R0
adc rRes5,R1
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
mul rZ14,rZ23
add rRes7,R0
adc rRes6,R1
adc rRes5,rmp
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
mul rZ14,rZ24
add rRes8,R0
adc rRes7,R1
adc rRes6,rmp
adc rRes5,rmp
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
mul rZ14,rZ25
add rRes9,R0
adc rRes8,R1
adc rRes7,rmp
adc rRes6,rmp
adc rRes5,rmp
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
; Multiply Z15
mul rZ15,rZ22
add rRes7,R0
adc rRes6,R1
adc rRes5,rmp
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
mul rZ15,rZ23
add rRes8,R0
adc rRes7,R1
adc rRes6,rmp
adc rRes5,rmp
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
mul rZ15,rZ24
add rRes9,R0
adc rRes8,R1
adc rRes7,rmp
adc rRes6,rmp
adc rRes5,rmp
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
; Multiply Z16
mul rZ16,rZ21
add rRes7,R0
adc rRes6,R1
adc rRes5,rmp
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
mul rZ16,rZ22
add rRes8,R0
adc rRes7,R1
adc rRes6,rmp
adc rRes5,rmp
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
mul rZ16,rZ23
add rRes9,R0
adc rRes8,R1
adc rRes7,rmp
adc rRes6,rmp
adc rRes5,rmp
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
; Multiply Z17
mul rZ17,rZ22
add rRes9,R0
adc rRes8,R1
adc rRes7,rmp
adc rRes6,rmp
adc rRes5,rmp
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
; Rounding to 56 bits length
ldi rmp,0x80
add rRes9,rmp
adc rRes8,rmp
ldi rmp,0
adc rRes7,rmp
adc rRes6,rmp
adc rRes5,rmp
adc rRes4,rmp
adc rRes3,rmp
adc rRes2,rmp
adc rRes1,rmp
Loop:
rjmp loop
;
; End of source code
;
; Copyright information
.db "(C)2022 by Gerhard Schmidt " ; Source code readable
.db "C(2)20 2ybG reahdrS hcimtd " ; Machine code format
Schlussfolgerungen
Die Multiplikation von zwei 56-Bit-Mantissen braucht mehr als 300 µs
und mehr als 250 Instruktionen. Wenn Du Deinen Prozessor mit Unfug
beschäftigen willst und unbedingt 15 Stellen lange Fließkommazahlen
haben willst: nur zu, stelle auf Fließkomma-Zahlen um und fühle Dich
wohl.
Alle anderen: Hände weg, schalte das Gehirn an und verwende lieber
irgendeinen Pseudo-Fließkomma/Festkomma-Algorithmus.
Zum Seitenanfang
©2022 by http://www.avr-asm-tutorial.net