Math

z80 » Beginner

All the mathmatical operations you'll need to start programming can be broken down into four groups:

You can test these and your own math routines using MathTest.asm. In its current form it only supports the answer in the 8 bit register a; but, you can follow the directions in the comments to change it to use hl in 2 seconds.

Remember also that we are only working with integers here. Floating point math is taken care of in the Binary Coded Decimal section. You can always write your own routines which will probably be faster than TI's, but why reinvent the wheel?

I wouldn't go into any of these routines if either of your operands is zero. Infinite loops and bogus answers are sure to follow.

Addition

Seems simple enough, right? You're using the accumulator (a) so you load it with one operand and load the other into another 8 bit register. After you've got that setup, go ahead and add them. Let's add 4 and 5 to get 9. Using add, the result will be stored in a.
	ld a,4	;one operand
	ld b,5	;second operand
	add a,b	;a+b (saved in a)

Don't forget the 16 bit registers. You can appply the same methods to them; but, instead of revolving around the accumulator (a), they revolve around hl. The advantage of using 16 bit registers is that you can have way bigger operands.

	ld hl,4000	;4,000
	ld de,15000	;15,000
	add hl,de	;4,000+15,000=19,000

Things can get a little more complicated when you start using the carry flag. The general idea is, add the two operands and, if the carry flag is set, add one to the answer. Don't add one if the carry flag was reset.

This is helpful, let's say, if you want to account for an overflow in the previous calculation. You have a calculation ( add, sub, dec, inc, etc.) which may or may not have resulted in an answer that was too big for it to store. The carry flag will be set if so.

	ld a,200
	ld b,150
	add a,b		;200+150=350
			; way too big
			; to store in
			; 8 bit register
			;max it can hold
			; is 255
			;carry set!
	ld a,4
	ld b,5
	adc a,b		;4+5=9
			;carry is set
			; so 9+1=10
			;10 is stored in
			; a
The practicality of this feature can be seen in the Simulated 16 bit Addition section, if you want to get more indepth.

Subtraction

Similar but opposite, sub is used for subtraction. The value you want subtracted from is loaded into the accumulator (a), the value you want to subtract is put into another register, and then you are ready to perform the actual subtraction.
	ld a,5	;one operand
	ld b,4	;second operand
	sub b	;a-b=5-4=1
		;1 stored in a
You can get into a mess if you try subtracting a big number from a little number. You won't get the answer you expect and the carry flag will be set.
	ld a,4	;one operand
	ld b,5	;second operand
	sub b	;a-b=4-5=-1=255
From the Two's Compliment section, you can see that a value of -1 means the same as 255. You can go ahead and add this to another 8 bit register and it will be the same as adding -1.
	ld c,6
	ld a,4	;one operand
	ld b,5	;second operand
	sub b	;a-b=4-5=-1=255
	add a,c	;a+c=-1+6=5
		;5 is stored in a
As with adc, there is sbc. It works the same way but adds one to the number being subtracted if the carry is set (not the number subtracted from).
	ld a,200
	ld b,150
	add a,b		;200+150=350
			; way too big
			; to store in
			; 8 bit register
			;max it can hold
			; is 255
			;carry set!
	ld a,5
	ld b,3
	sbc a,b		;5-(3+1)=1
			;carry is set
			; so 3+1=4
			; 5-4=1
			;1 is stored in a

Multiplication

If you haven't figured it out yet, there aren't any instructions to directly multiply. You have to mix some add's, sub's, shifts, and rotates to get what you want. The first few examples assume the answer doesn't excede the capacity of an 8 bit register (255).

You've been through elementary school, you know how the basics of multiplication: add repeatedly.

Since it's always better to use code that you understand, this routine is about as simple as it gets. B is one operand and c is the other with the answer in a

	sub a	;zero a
	or b	;is b=0?
	ret z	;exit if b=0 so ret
multiply_loop:
	add a,c	;add c to total so far
	djnz multiply_loop
The effect is that c is repeatedly added to itself, the very idea of multiplication in 6 bytes!

There are two drawbacks to this method.

  1. The larger the numbers, the more loops executed, the slower the routine.
  2. The answer can't excede 255 or there's an overflow. You need to use 16 bit registers.

Time is of the essence, you need a fast routine. You need to multiply a number (x) loaded in a to a constant--let's try 2 first. The advantage of using a constant is that you don't need loops, it goes through the code once and only once. You know exactly how long it's going to take.

	add a,a	;a=a=2*a
That simple! Try times 5.
	ld b,a	;save it away temporarily
	add a,a	;a*2
	add a,a	;a*4
	add a,b	;a*4+a=5*a
Eight is a good number too.
	add a,a	;*2
	add a,a	;*2
	add a,a	;*2=2*2*2=8
Try another odd number like 13 using sub too. Just subtract the original a few times.
	ld b,a	;save it away temporarily
	add a,a	;a*2
	add a,a	;a*4
	add a,a	;a*8
	add a,a	;a*16
	sub b	;a*16-a
	sub b	;a*15-a
	sub b	;a*14-a=13*a
None of these will hold an answer over 255. Try using 16 bit register pairs to multiply 35 and hl.
	ld d,h		;save it away temporarily
	ld e,l		;save it away temporarily
	add hl,hl	;hl*2
	add hl,hl	;hl*4
	add hl,hl	;hl*8
	add hl,hl	;hl*16
	add hl,hl	;hl*32
	add hl,de	;hl*32+hl
	add hl,de	;hl*32+hl+hl
	add hl,de	;hl*32+hl+hl+hl=32*hl+3hl=35*hl

In some cases, it's easier to add in that saved away original at an earlier date, like when the constant is not near a power of 2. 20's like that.

	ld b,a	;save away original
	add a,a	;a*2
	add a,a	;a*4
	add a,b	;a*4+a=a*5
	add a,a	;a*10
	add a,a	;a*20
If we didn't add in the original early like above, our code would be a lot longer!
	ld b,a	;save away original
	add a,a	;a*2
	add a,a	;a*4
	add a,a	;a*8
	add a,a	;a*16
	add a,b	;a*16+a
	add a,b	;a*16+a+a
	add a,b	;a*16+a+a+a
	add a,b	;a*16+a+a+a+a=a*20
It's a good thing we thought ahead. Sometimes you might have to add in the original a couple times along the way.

Division

Similiar to multiplication, just use subtraction.

Let's divide a by b (a/b). In this routine, the answer is stored into a. Remember that this is for integers that divide evenly. If you have two numbers that you know do not divide evenly, you will get an infinite loop usually (Your calculator has crashed).

	ld c,$00
divide_loop:
	inc c
	sub b
	jr nz,divide_loop
	ld a,c
	ret
If you don't mind the final answer stored in c, and want to save a byte.
	ld c,$00
divide_loop:
	inc c
	sub b
	ret z
	jr divide_loop
This is similiar to the basic multiplication routine: subtract repeatedly. This routine will also exit if a is zero and become slower the larger the operands are (more loops).

Let's divide a by a constant of 8 using srl a.

	srl a	;/2
	srl a	;/4
	srl a	;/8
I don't recommend using shift logically because it's two bytes long. It is nice because it shifts the bits right out of here. Rotate is faster but it just circulates those bits to the other side. If you're going to use rotate, make sure to mask off those bits using and. This is one byte shorter. You'll save more bytes if you divide by higher powers of 2 (8=2^3=2*2*2)
	and %11111000	;mask off (get
			; rid of) last
			; three bits which
			; will be rotated
			; out
			;we don't want them
			; coming back in
			; on the other side
	rrca		;/2
	rrca		;/4
	rrca		;/8


More from z80 » Beginner
Aliases // Convert from decimal to hexadecimal or binary // Flags and Conditions // Format and Compiling // Instructions // Math // Number Bases // Oh, No! It Crashed! // Registers // TI-BASIC to Asm Comparisons // TI86 Specifications // Two's Compliment // z80 Processor