Difference between revisions of "Z80 Routines:Math:Multiplication"

From WikiTI
Jump to: navigation, search
m (`sbc hl,bc` ==> `sbc hl,de`)
 
(4 intermediate revisions by 3 users not shown)
Line 15: Line 15:
 
  <nowiki>
 
  <nowiki>
 
mult_h_e
 
mult_h_e
   ld l, 0
+
   ld d, 0 ; Combining the overhead and
  ld d, l
+
   sla h ; optimised first iteration
 
+
   sbc a, a
   sla h ; optimised 1st iteration
+
  and e
   jr nc, $+3
+
   ld l, a
   ld l, e
+
 
    
 
    
   ld b, 7
+
   ld b, 7
 
_loop:
 
_loop:
 
   add hl, hl           
 
   add hl, hl           
Line 91: Line 90:
 
  </nowiki>
 
  </nowiki>
  
=== 24*24 multiplication ===
+
== Signed versions ==
  
The following routine multiplies bcd by ehl and places the result in bcdehl. It uses 2 bytes of ram, but those can be substituted for IX half registers. (By thepenguin77)
+
<!-- do the routines later, basically try to use cpu instruction that preserve bit 7 (sign) or if this is not possible, just take the sign out, do the multiplication and give the sign afterwards and accordingly. -->
  
<nowiki>
+
=== 8*8 multiplication ===
multDBCbyEHL:
+
+
push de
+
push hl
+
+
ld ix, $8000
+
ld (ix), l
+
xor a
+
ld h, a
+
ld l, a
+
  
call do8Bits
+
=== 16*8 multiplication ===
  
ld (ix+1), a
+
=== 16*16 multiplication ===
pop af
+
ld (ix), a
+
push de
+
ld a, (ix+1)
+
  
call do8Bits
+
The following routine multiplies bc by de and places the signed result in dehl.
 +
<nowiki>
  
ld (ix+1), a
+
;
pop af ;least sig number
+
;16 bit signed multiply
ex de, hl
+
;31 bytes
ex (sp), hl ;D and 2nd least sig in for new number
+
;min: 928cc
ld (ix), l
+
;max: 1257cc
pop hl ;D and 2nd least sig
+
;avg: 1088.5cc
push af ;least sig number
+
push hl ;2nd least sig number
+
ex de, hl
+
ld a, (ix+1)
+
  
call do8Bits
 
  
ld b, a
+
; call with bc, de = 16 bit signed numbers to multiply
ld c, h
+
; returns  de:hl = 32 bit signed product
ld d, l
+
; corrupts  a
pop hl
+
ld a, l
+
pop hl
+
ld h, a
+
ret
+
  
;####
+
; de:hl = bc*de
;input: DBC = 1 number
+
; AHL = running number
+
; (ix) = to multiply by
+
;output: AHLE = output
+
; E is done
+
; DBC = 1 number
+
  
do8Bits:
+
multiply:
ld (ix+1), 8
+
  xor a
loop:
+
  ld h,a
srl (ix)
+
  ld l,a
jr nc, skip
+
  
add hl, bc
+
  bit 7,d
adc a, d
+
  jr z,muldpos
skip:
+
  sbc hl,bc
rra
+
muldpos:
rr h
+
rr l
+
rr e
+
dec (ix+1)
+
jr nz, loop
+
ret
+
</nowiki>
+
  
== Signed versions ==
+
  or b
 +
  jp p,mulbpos
 +
  sbc hl,de
 +
mulbpos:
  
<!-- do the routines later, basically try to use cpu instruction that preserve bit 7 (sign) or if this is not possible, just take the sign out, do the multiplication and give the sign afterwards and accordingly. -->
+
  ld a,16
 +
mulloop:
 +
  add hl,hl
 +
  rl e
 +
  rl d
 +
  jr nc,mul0bit
 +
  add hl,bc
 +
  jr nc,mul0bit
 +
  inc de
 +
mul0bit:
 +
  dec a
 +
  jr nz,mulloop
 +
</nowiki>
  
=== 8*8 multiplication ===
 
  
=== 16*8 multiplication ===
+
'''Note:''' The code was originally John Metcalf's
 +
([https://github.com/impomatic/z80snippets/blob/master/mulsigned.asm here]),
 +
so this looks similar, but Sue Doenim found a bug with negative BC, so we fixed that.
 +
The fix is based on Goplat's pseudocode
 +
([https://www.omnimaga.org/asm-language/(z80)-16-bit-*-16-bit-32-bit-signed-multiplication/msg154690/#msg154690 here]):
 +
 
 +
<nowiki>
 +
Here's a possibillity: Since a negative signed number is 65536 less than its
 +
interpreted as unsigned, you can just do an unsigned multiplication and if any
 +
side is negative, subtract the other side from the high word of the result:
 +
 
 +
a b product
 +
pos pos ab                = ab
 +
pos neg a(b-65536)        = ab - 65536a
 +
neg pos (a-65536)b        = ab - 65536b
 +
neg neg (a-65536)(b-65536) = ab - 65536(a+b)
 +
</nowiki>

Latest revision as of 19:31, 28 April 2022


Introduction

All these routines use the restoring multiplication algorithm, adapted to the z80 architecture to maximize speed. They can easily be unrolled to gain some speed.

Unsigned versions

8*8 multiplication

The following routine multiplies h by e and places the result in hl

mult_h_e
   ld	d, 0	; Combining the overhead and
   sla	h	; optimised first iteration
   sbc	a, a
   and	e
   ld	l, a
   
   ld	b, 7
_loop:
   add	hl, hl          
   jr	nc, $+3
   add	hl, de
   
   djnz	_loop
   
   ret
 

16*8 multiplication

The following routine multiplies de by a and places the result in ahl (which means a is the most significant byte of the product, l the least significant and h the intermediate one...)

mult_a_de
   ld	c, 0
   ld	h, c
   ld	l, h

   add	a, a		; optimised 1st iteration
   jr	nc, $+4
   ld	h,d
   ld	l,e

   ld b, 7
_loop:
   add	hl, hl
   rla
   jr	nc, $+4
   add	hl, de
   adc	a, c            ; yes this is actually adc a, 0 but since c is free we set it to zero and so we can save 1 byte and up to 3 T-states per iteration
   
   djnz	_loop
   
   ret
 

16*16 multiplication

The following routine multiplies bc by de and places the result in dehl.

mult_de_bc
   ld	hl, 0

   sla	e		; optimised 1st iteration
   rl	d
   jr	nc, $+4
   ld	h, b
   ld	l, c

   ld	a, 15
_loop:
   add	hl, hl
   rl	e
   rl	d
   jr	nc, $+6
   add	hl, bc
   jr	nc, $+3
   inc	de
   
   dec	a
   jr	nz, _loop
   
   ret
 

Signed versions

8*8 multiplication

16*8 multiplication

16*16 multiplication

The following routine multiplies bc by de and places the signed result in dehl.


;
;16 bit signed multiply
;31 bytes
;min: 928cc
;max: 1257cc
;avg: 1088.5cc


; call with bc, de = 16 bit signed numbers to multiply
; returns   de:hl = 32 bit signed product
; corrupts  a

; de:hl = bc*de

multiply:
  xor a
  ld h,a
  ld l,a

  bit 7,d
  jr z,muldpos
  sbc hl,bc
muldpos:

  or b
  jp p,mulbpos
  sbc hl,de
mulbpos:

  ld a,16
mulloop:
  add hl,hl
  rl e
  rl d
  jr nc,mul0bit
  add hl,bc
  jr nc,mul0bit
  inc de
mul0bit:
  dec a
  jr nz,mulloop


Note: The code was originally John Metcalf's (here), so this looks similar, but Sue Doenim found a bug with negative BC, so we fixed that. The fix is based on Goplat's pseudocode (here):

Here's a possibillity: Since a negative signed number is 65536 less than its
interpreted as unsigned, you can just do an unsigned multiplication and if any
side is negative, subtract the other side from the high word of the result:

	a	b	product
	pos	pos	ab                 = ab
	pos	neg	a(b-65536)         = ab - 65536a
	neg	pos	(a-65536)b         = ab - 65536b
	neg	neg	(a-65536)(b-65536) = ab - 65536(a+b)