Someone was lucky enough to learn some set theory at school as a 6 year old, and binary and other number base systems a year later.
For those who didn't these may seem like totally foreign subjects so apologies if the following does not make any sense.
One aspect of Z80 programming that isn't covered, isn't taught well or just glossed over in text and reference books is the handling of signed integers.
The use of 2's complement numbers for negative number representation is so that we don't have a situation of having both a -0 and +0 number.
It's a great unheralded mathematics invention!
With the 2's complement, (i.e -x = 2^N - x where N is the number of bits and x is the number being made negative) the numbers are allocated so that for an 8 bit byte:
00000000 = 0
00000001 to 01111111 = 1 to 127
11111110 to 10000000 = -1 to - 128
essentially 7 bits of magnitude and a sign bit (with 1= negative numbers and 0= positive numbers & zero).
(When it gets extended over 2 bytes we get a range of -32,768 to +32,767 - Does this look familiar? Like the INTEGER value limits with MWBASIC?)
Unlike the 8080, the Z80 contains the NEG instruction to perform 2's complement easily.
(For the 8080, an alternative way to calculate the 2's complement is taking the 1's complement then adding 1.)
Contrary to some Internet BS, the Z80 arithmetic functions work with both signed and unsigned numbers.
For unsigned numbers, usually just the Carry, Zero bits (of the Flag Register) are used for test the results of the operation but for signed numbers the Overflow(V) and Sign(S) bits are also used making the code support slightly more complex thus marginally slower.
One could perform a bunch of conditional jump and call instructions. This approach can end up with a complex solution.
A trick to avoid this pitfall is to perform a V XOR S and use the results of it to identify the result.
For an arithmetic operation on the most significant byte of a signed multibyte integer value
If V XOR S is 0, then the first argument >= second argument (for the current byte only)
If V XOR S is 1, then the first argument < second argument (for the current byte only).
* - the remainder of the bytes still require processing.
(e.g. if the first arg = second arg, then check the next arg, so on and so forth.)
But how does one do this V XOR S stuff?
The trick is to read the contents of the Flag register into another register which can be done by not so obvious PUSH and PULL.
For example. PUSH AF, POP BC will load the C register with the Flag register's value.
From that you can test and manipulate the copy of the F register bits in the C register as you desire.
How does the overflow (V) status bit work?
Well, when the appropriate arithmetic instruction is executed such as an addition or subtraction, the CPU's ALU tests out see if the result will fit into 7 lowest bits of an 8 bit register. If it doesn't then the V flag is set. This differs from the Carry flag where the CPU's ALU tests to see if the result will fit into all 8 bits.
e.g.s
Well, it's just the value of bit 7 of the result.
For the first example the sign bit is set and for the second example, it's not.
But remember don't fall into the trap that the sign bit alone is an indication of the sign of the result!
For those who didn't these may seem like totally foreign subjects so apologies if the following does not make any sense.
One aspect of Z80 programming that isn't covered, isn't taught well or just glossed over in text and reference books is the handling of signed integers.
The use of 2's complement numbers for negative number representation is so that we don't have a situation of having both a -0 and +0 number.
It's a great unheralded mathematics invention!
With the 2's complement, (i.e -x = 2^N - x where N is the number of bits and x is the number being made negative) the numbers are allocated so that for an 8 bit byte:
00000000 = 0
00000001 to 01111111 = 1 to 127
11111110 to 10000000 = -1 to - 128
essentially 7 bits of magnitude and a sign bit (with 1= negative numbers and 0= positive numbers & zero).
(When it gets extended over 2 bytes we get a range of -32,768 to +32,767 - Does this look familiar? Like the INTEGER value limits with MWBASIC?)
Unlike the 8080, the Z80 contains the NEG instruction to perform 2's complement easily.
(For the 8080, an alternative way to calculate the 2's complement is taking the 1's complement then adding 1.)
Contrary to some Internet BS, the Z80 arithmetic functions work with both signed and unsigned numbers.
For unsigned numbers, usually just the Carry, Zero bits (of the Flag Register) are used for test the results of the operation but for signed numbers the Overflow(V) and Sign(S) bits are also used making the code support slightly more complex thus marginally slower.
One could perform a bunch of conditional jump and call instructions. This approach can end up with a complex solution.
A trick to avoid this pitfall is to perform a V XOR S and use the results of it to identify the result.
For an arithmetic operation on the most significant byte of a signed multibyte integer value
If V XOR S is 0, then the first argument >= second argument (for the current byte only)
If V XOR S is 1, then the first argument < second argument (for the current byte only).
* - the remainder of the bytes still require processing.
(e.g. if the first arg = second arg, then check the next arg, so on and so forth.)
But how does one do this V XOR S stuff?
The trick is to read the contents of the Flag register into another register which can be done by not so obvious PUSH and PULL.
For example. PUSH AF, POP BC will load the C register with the Flag register's value.
From that you can test and manipulate the copy of the F register bits in the C register as you desire.
Code:
; READ F REGISTER INTO C
PUSH AF
POP BC
; PUT A COPY OF THE FLAGS INTO A AND B
LD A,C
LD B,C
; PREP THE C REGISTER FOR THE UPCOMING XOR
; ALIGN OVERFLOW BIT WITH SIGN BIT
RRCA ; TO BIT 1
RRCA ; TO BIT 0
RRCA ; TO BIT 7 (NOW ALIGNED WITH S FLAG)
AND #10000000B ; MASK OFF THE UNWANTED BITS
LD C,A
; PREP THE A REGISTER FOR THE UPCOMING XOR
LD A,B
AND #10000000B ; MASK OFF THE UNWANTED BITS
; PERFORM THE XOR
XOR C
; YAY!How does the overflow (V) status bit work?
Well, when the appropriate arithmetic instruction is executed such as an addition or subtraction, the CPU's ALU tests out see if the result will fit into 7 lowest bits of an 8 bit register. If it doesn't then the V flag is set. This differs from the Carry flag where the CPU's ALU tests to see if the result will fit into all 8 bits.
e.g.s
- [+]127 + [+]64 = [+]191 --> V = 1 flag set because 191 doesn't fit into 7 bits.
- [+]32 + [+]32 = [+]64 ---> V = 0 flag reset because 64 fits into 7 bits.
Well, it's just the value of bit 7 of the result.
For the first example the sign bit is set and for the second example, it's not.
But remember don't fall into the trap that the sign bit alone is an indication of the sign of the result!
