Pfad: Home => AVR-Überblick => Programmiertechniken => Fließkommazahlen => Mantissen-Multiplikation    (This page in English: Flag EN) Logo

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:
  1. Addieren der beiden Exponenten, und
  2. 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

Zwei Mantissen zum Multiplizieren 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.

Multiplikation der Mantissen 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