Table of Contents
| 1 | Introduction | |
| 1.1 | Types of comparisons | |
| 1.2 | Review of twos complement (signed) numbers | |
| 2 | Review of compare instructions | |
| 2.1 | The CMP, CPX, and CPY instructions | |
| 2.2 | A trick so simple that it's often overlooked | |
| 2.3 | Using EOR for equality comparisons | |
| 2.4 | Using EOR to compare only some of the bits in a byte | |
| 2.5 | A trick when using SBC for unsigned comparisons | |
| 3 | Extending the equality comparison beyond 8 bits | |
| 4 | Extending the unsigned comparison beyond 8 bits | |
| 4.1 | Comparing one byte at a time | |
| 4.2 | An equality and unsigned comparison | |
| 4.3 | Comparison by subtraction | |
| 5 | Signed comparisons | |
| 5.1 | Comparing two 8-bit numbers | |
| 5.2 | A caveat: the SO pin | |
| 6 | Extending the signed comparison beyond 8 bits |
Appendix A: A program that demonstrates that the signed comparison works
The 6502 has several options available for comparing numbers. Each option, naturally, has its pros and cons in terms of speed and size. There are also some infrequently used options that occasionally come in handy. Finally, and unfortunately, some misconceptions abound about signed (twos complement) numbers and the correct way to compare them. All of these topics will be covered below.
Generally, two numbers are compared to determine (a) if they are same, or (b) which one is smaller (or larger) than the other. For the sake of consistency, three terms are used from this point on:
1.2 REVIEW OF TWOS COMPLEMENT (SIGNED) NUMBERS
There are 256 possible values for a byte; in hex, they are: $00 to $FF. The range of an 8-bit unsigned number is 0 ($00) to 255 ($FF). The range of a 16-bit unsigned number is 0 ($0000) to 65535 ($FFFF), and so on. They are called unsigned numbers because they are zero or greater, i.e. there is no (minus) sign. A signed number, on the other hand, can be negative or positive (or zero). The term "signed number" is used below to mean a twos complement number (although there are other ways of representing signed numbers). The range of an 8-bit signed number is -128 to 127. The values -128 through -1 are, in hex, $80 through $FF, respectively. The values 0 through 127 are, in hex, $00 through $7F, respectively. So the minimum value of a signed number is $80 and the maximum value of a signed number is $7F. The range of a 16-bit signed number is -32768 ($8000) to 32767 ($7FFF) ($8000 through $FFFF are the negative numbers), and so on. This may seem like a strange way of handling negative numbers, but this method has several useful properties.
First, 0 to 127 (the overlap of the ranges of 8-bit signed and unsigned numbers) is, in hex, $00 to $7F, regardless of whether the number is signed or unsigned.
Second, the most significant bit (bit 7 for an 8-bit number) is zero when the number is non-negative (0 to 127), and one when the number is negative. In fact, this is how the N (negative) flag of 6502 got its name. (Notice that the N flag, when affected by an instruction, reflects bit 7 of the result of that instruction.) One other note: in mathematics, zero is not a postive or a negative number, but in the computer world, things are less formal; the term "positive number" typically includes zero because (a) all of the other possible values of a signed number whose most significant bit is zero are positive numbers, and (b) all of the other possible values for an unsigned number are positive numbers.
Third, consider the following addition:
CLC
LDA #$FF
ADC #$01
The result (in the accumulator) is $00, and the carry is set. The addition,
in unsigned numbers, is: 255 + 1 = 256 (remember, the carry is set). The
addition, in signed numbers, is -1 + 1 = 0. In other words, adding (and
subtracting) signed numbers is exactly the same as adding (and subtracting)
unsigned numbers.
2 REVIEW OF COMPARE INSTRUCTIONS
There are three compare instructions on the 6502: CMP, CPX, and CPY. However, EOR and SBC can also be used for comparisions, and occasionally this is useful (for EOR more so than SBC).
2.1 THE CMP, CPX, AND CPY INSTRUCTIONS
The CMP, CPX, and CPY instructions are used for comparisons as their mnemonics suggest. The way they work is that they perform a subtraction. In fact,
CMP NUM
is very similar to:
SEC
SBC NUM
Both affect the N, Z, and C flags in exactly the same way. However, unlike
SBC, (a) the CMP subtraction is not affected by the D (decimal) flag, (b) the
accumulator is not affected by a CMP, and (c) the V flag is not affected by
a CMP. A useful property of CMP is that it performs an equality comparison
and an unsigned comparison. After a CMP, the Z flag contains the equality
comparison result and the C flag contains the unsigned comparison result,
specifically:
The N flag contains most significant bit of the of the subtraction result. This is only occasionally useful. However, it is NOT the signed comparison result, as is sometimes claimed, as the following examples illustrate:
After:
LDA #$01 ; 1 (signed), 1 (unsigned)
CMP #$FF ; -1 (signed), 255 (unsigned)
A = $01, C = 0, N = 0 (the subtraction result is $01 - $FF = $02), and Z = 0.
The comparison results are:
LDA #$7F ; 127 (signed), 127 (unsigned)
CMP #$80 ; -128 (signed), 128 (unsigned)
A = $7F, C = 0, N = 1 (the subtraction result is $7F - $80 = $FF), and Z = 0.
The comparison results are:
The CPX and CPY instructions are exactly like the CMP instruction, except that they use the X and Y registers, respectively, instead of the accumulator.
2.2 A TRICK SO SIMPLE THAT IT'S OFTEN OVERLOOKED
A surprisingly common sequence in 6502 code is:
LDA NUM1
CMP NUM2
BCC LABEL
BEQ LABEL
(or something similar) which branches to LABEL when NUM1 <= NUM2. (In this
case NUM1 and NUM2 are unsigned numbers.) However, consider the following
sequence:
LDA NUM2
CMP NUM1
BCS LABEL
which branches to LABEL when NUM2 >= NUM1, which is the same as NUM1 <= NUM2.
Not only that, it's shorter and (in many cases) faster.
2.3 USING EOR FOR EQUALITY COMPARISONS
The EOR instruction can also be used for equality comparisions. Naturally, there are trade-offs. First, EOR, unlike CMP, affects the accumulator. Second, EOR is only available for an equality comparision to the accumulator and not the X or Y registers. Third, EOR does not affect the C flag. As it happens, not affecting the C flag is useful enough that EOR is often used for equality comparisons. After an EOR, the equality comparison result is in the Z flag, specifically:
2.4 USING EOR TO COMPARE ONLY SOME OF THE BITS IN A BYTE
An additional advantage EOR is that it is easy to compare only some of the bits in a byte. After the EOR, simply use the AND instruction to mask off the bits of interest, and the equality comparison result will be in Z, as usual. For example, after:
LDA BYTE1
EOR BYTE2
AND #$AB ; $AB = %10101011 (binary)
The Z flag will be 1 (BEQ branches) if bits 7, 5, 3, 1, and 0 of BYTE1 are
the same as bits 7, 5, 3, 1, and 0 of BYTE2, and the Z flag will be 0 (BNE
branches) if they are not.
2.5 A TRICK WHEN USING SBC FOR UNSIGNED COMPARISONS
Since CMP and SBC both subtract, SBC can also be used for unsigned comparisons, although such use is rare. This is because SBC affects the accumulator and CMP does not. There is one instance where SBC can occasionally be useful. In fact, this trick is more useful when combined with the method in Section 4.3 (comparison by subtraction) for extending unsigned comparisons beyond 8 bits than it is by itself (as it is presented here). The trick is to CLEAR the carry before the SBC instruction. For example, after:
CLC
SBC NUM
C still holds the unsigned comparison result, but in this case:
As as aside, remember that the D flag affects the result of a SBC. So there is the question of what happens when the D flag is 1. Believe it or not, after:
CLD
SBC NUM
the C flag will be the same as after:
SED
SBC NUM
assuming that the accumulator and NUM are the same in both cases, even if the
accumulator or NUM (or both) is not a valid BCD number! This is true of the
6502, the 65C02, and the 65C816. (This has been tested on a Rockwell 6502,
a Synertek 6502, a GTE 65C02, and a GTE 65C816.) Of course, the effect on
the accumulator is different in the two cases above, but the effect on the
carry is the same.
3 EXTENDING THE EQUALITY COMPARISON BEYOND 8 BITS
There aren't any tricks when extending the equality comparison beyond 8 bits. The bytes are simply compared one a time. A few examples are in order.
Example 3.1: a 16-bit equality comparison (low byte in Y, high byte in A) which branches to LABEL if the numbers are not equal
CPY NUML ; compare low bytes
BNE LABEL
CMP NUMH ; compare high bytes
BNE LABEL
Example 3.2: a 16-bit equality comparison (again, low byte in Y, high byte in
A) which branches to LABEL2 if the numbers are equal
CPY NUML ; compare low bytes
BNE LABEL1
CMP NUMH ; compare high bytes
BEQ LABEL2
LABEL1
Example 3.3: a 16-bit equality comparison (again, low byte in Y, high byte in
A) which leaves the usual equality comparison result in the Z flag
CPY NUML ; compare low bytes
BNE LABEL
CMP NUMH ; compare high bytes
LABEL
Example 3.4: a 24-bit equality comparison (low byte in Y, middle byte in X,
high byte in A) which branches to LABEL if the numbers are not equal
CPY NUML ; compare low bytes
BNE LABEL
CPX NUMM ; compare middle bytes
BNE LABEL
CMP NUMH ; compare high bytes
BNE LABEL
Example 3.5: a 24-bit equality comparison (again, low byte in Y, middle byte
in X, high byte in A) which branches to LABEL2 if the numbers are equal
CPY NUML ; compare low bytes
BNE LABEL1
CPX NUML ; compare middle bytes
BNE LABEL1
CMP NUMH ; compare high bytes
BEQ LABEL2
LABEL1
Note that in all five examples, (a) the bytes can be compared in any order
(e.g. the high bytes could be compared first), and (b) an EOR could be used
instead of CMP.
4 EXTENDING THE UNSIGNED COMPARISON BEYOND 8 BITS
There are several options for extending unsigned comparisons. As is usually the case, there are pros and cons to each option. There are trade-offs in terms of space and speed, so the best option depends on the situation.
4.1 COMPARING ONE BYTE AT A TIME
This is the most straightforward approach. It's similar to the approach for equality comparisons. The idea is to start by comparing the high bytes and work toward the low bytes. A few examples are in order.
Example 4.1.1: a 16-bit unsigned comparison which branches to LABEL2 if NUM1 < NUM2
LDA NUM1H ; compare high bytes
CMP NUM2H
BCC LABEL2 ; if NUM1H < NUM2H then NUM1 < NUM2
BNE LABEL1 ; if NUM1H <> NUM2H then NUM1 > NUM2 (so NUM1 >= NUM2)
LDA NUM1L ; compare low bytes
CMP NUM2L
BCC LABEL2 ; if NUM1L < NUM2L then NUM1 < NUM2
LABEL1
Example 4.1.2: a 16-bit unsigned comparison which branches to LABEL2 if
NUM1 >= NUM2
LDA NUM1H ; compare high bytes
CMP NUM2H
BCC LABEL1 ; if NUM1H < NUM2H then NUM1 < NUM2
BNE LABEL2 ; if NUM1H <> NUM2H then NUM1 > NUM2 (so NUM1 >= NUM2)
LDA NUM1L ; compare low bytes
CMP NUM2L
BCS LABEL2 ; if NUM1L >= NUM2L then NUM1 >= NUM2
LABEL1
Example 4.1.3: a 24-bit unsigned comparison which branches to LABEL2 if
NUM1 < NUM2
LDA NUM1H ; compare high bytes
CMP NUM2H
BCC LABEL2 ; if NUM1H < NUM2H then NUM1 < NUM2
BNE LABEL1 ; if NUM1H <> NUM2H then NUM1 > NUM2 (so NUM1 >= NUM2)
LDA NUM1M ; compare middle bytes
CMP NUM2M
BCC LABEL2 ; if NUM1M < NUM2M then NUM1 < NUM2
BNE LABEL1 ; if NUM1M <> NUM2M then NUM1 > NUM2 (so NUM1 >= NUM2)
LDA NUM1L ; compare low bytes
CMP NUM2L
BCC LABEL2 ; if NUM1L < NUM2L then NUM1 < NUM2
LABEL1
Example 4.1.4: a 24-bit unsigned comparison which branches to LABEL2 if
NUM1 >= NUM2
LDA NUM1H ; compare high bytes
CMP NUM2H
BCC LABEL1 ; if NUM1H < NUM2H then NUM1 < NUM2
BNE LABEL2 ; if NUM1H <> NUM2H then NUM1 > NUM2 (so NUM1 >= NUM2)
LDA NUM1M ; compare middle bytes
CMP NUM2M
BCC LABEL1 ; if NUM1M < NUM2M then NUM1 < NUM2
BNE LABEL2 ; if NUM1M <> NUM2M then NUM1 > NUM2 (so NUM1 >= NUM2)
LDA NUM1L ; compare low bytes
CMP NUM2L
BCS LABEL2 ; if NUM1L >= NUM2L then NUM1 >= NUM2
LABEL1
There are times when comparing one byte at a time is the fastest way to
perform an unsigned comparison. Consider an unsigned comparison of two
numbers NUM1 and NUM2. There is a BCC instruction right after NUM1H is
compared to NUM2H. So if circumstances are such that NUM1H is usually less
than NUM2H, the BCC will be taken and the comparison will
finish almost immediately. In this case, the other methods for extending
unsigned comparisons beyond 8 bits will not be as fast.
4.2 AN EQUALITY AND UNSIGNED COMPARISON
One of the useful things about using CMP is that it does an equality and a unsigned comparison. There is a method for doing comparisons beyond 8 bits that give an equality comparison result and an unsigned comparison result, without having to do the comparison twice. The idea is again to work from the high bytes to the low bytes. This idea is extended by noticing that if the high bytes are not equal, then no further bytes need to be compared. As it turns out, this approach will also provide an equality comparison result. A couple of examples are in order.
Example 4.2.1: a 16-bit comparison (low byte in Y, high byte in A) which leaves the usual equality comparison result in the Z flag, and the usual unsigned comparison result in the C flag
CMP NUMH ; compare high bytes
BNE LABEL
CPY NUML ; compare low bytes
LABEL
Note that this example is similar to Example 3.3 (in Section 3), but unlike
Example 3.3, the high byte is compared first, and the low byte is compared
second.
Example 4.2.2: a 24-bit unsigned comparison (low byte in Y, middle byte in X, high byte in A) which leaves the usual equality comparison result in the Z flag, and the usual unsigned comparison result in the C flag
CMP NUMH
BNE LABEL
CPX NUMM
BNE LABEL
CPY NUML
LABEL
Remember that the CMP instruction performs a subtraction. So one way do a multi-byte unsigned comparison is simply to do a multi-byte subtraction. After:
SEC ; NUM3 = NUM1 - NUM2
LDA NUM1L
SBC NUM2L
STA NUM3L
LDA NUM1H
SBC NUM2H
STA NUM3H
The C flag will contain the unsigned comparison result. But notice that the
subtraction result does not need to be stored, since the C flag is all that
is of interest for an unsigned comparison, so the two STA instructions can be
eliminated as follows:
SEC
LDA NUM1L
SBC NUM2L
LDA NUM1H
SBC NUM2H
Notice that after the first SBC, the accumulator is immediately overwritten.
The only purpose of the first SBC is to prepare the carry flag for the second
SBC. But remember, after:
CMP NUM
the C flag will be the same as after:
SEC
SBC NUM
so the SEC and the first SBC instructions can be replaced by a CMP
instruction as follows:
LDA NUM1L
CMP NUM2L
LDA NUM1H
SBC NUM2H
Notice that the this method works from low byte to high byte. A typical
24-bit unsigned comparison is:
LDA NUM1L
CMP NUM2L
LDA NUM1M
SBC NUM2M
LDA NUM1H
SBC NUM2H
As stated above, after a CMP instruction, the N flag is NOT the signed comparison result. A signed comparison works by performing a subtraction, but the signed comparison result is the exclusive-or (eor) of the N and V flags. Specifically, to compare the signed numbers NUM1 and NUM2, the subtraction NUM1-NUM2 is performed, and NUM1 < NUM2 when N eor V is 1, and NUM1 >= NUM2 when N eor V is 0. (A program that proves this is given in Appendix A.)
5.1 COMPARING TWO 8-BIT NUMBERS
Calulating N eor V has two difficulties. First, CMP does not affect the V flag. But SBC does, so the solution is simply to use SBC instead of CMP. The second difficulty is how to handle the exclusive-or of the N and V flags. One way is to handle the four possible cases (N=0 and V=0, N=0 and V=1, N=1 and V=0, and N=1 and V=1) with BMI, BPL, BVC, and BVS instructions. This works, but there is a faster and shorter way. Remember that SBC puts the subtraction result in the accumulator, and the N flag is bit 7 of the subtraction result. But bit 7 of the accumulator is also bit 7 of the subtraction result. So when the V flag is 1, use EOR to invert bit 7 of the accumulator (which is the same as N flag). After the EOR instruction, the N flag will contain N eor V (the signed comparison result), as will bit 7 of the accumulator. Specifically, after:
SEC ; prepare carry for SBC
SBC NUM ; A-NUM
BVC LABEL ; if V is 0, N eor V = N, otherwise N eor V = N eor 1
EOR #$80 ; A = A eor $80, and N = N eor 1
LABEL
If the N flag is 1, then A (signed) < NUM (signed) and BMI will branchOne way to remember which is which is to remember that minus (BMI) is less than, and plus (BPL) is greater than or equal to.
Since BVC and EOR do not affect the carry, note that the usual unsigned comparison result is the C flag.
It also possible to put the signed comparison result in the C flag with a little extra effort.
SEC
SBC NUM
BVS LABEL ; Note: BVS not BVC
EOR #$80
LABEL ASL
Notice that BVS is used instead of BVC, so bit 7 of the accumulator will be
0 when A < NUM, and 1 when A >= NUM. The ASL is used to shift this bit into
the carry, so that C = 0 (BCC branches) when A < NUM, and C = 1 (BCS
branches) when A >= NUM, as is the case with unsigned comparisons.
Note that the trick described in Section 2.5 also works with signed comparisons. When the carry is cleared before the subtraction, as with:
CLC ; Note: CLC, not SEC
SBC NUM
BVC LABEL
EOR #$80
LABEL
N will contain the signed comparison result, but in this case:
SEC
LDA #$7F
SBC #$FF
BVC LABEL
EOR #$80
LABEL ; The Z flag is 1 but $7F <> $FF !
Since the purpose of the EOR is to invert bit 7 of the accumulator, it is
possible to use any value between $80 and $FF, inclusive, with EOR. The Z
flag will be 0 in the example above if the EOR #$80 is replaced with
EOR #$FF, but EOR #$FF has its own counterexample, specifically:
SEC
LDA #$7F
SBC #$80
BVC LABEL
EOR #$FF
LABEL ; The Z flag is 1 but $7F <> $80 !
As as aside, remember that the D flag affects the result of a SBC. So again
the question of what happens when the D flag is 1 arises. Believe it or not,
as was the case with the C flag, after:
CLD
SBC NUM
the V flag will be the same as after:
SED
SBC NUM
Assuming that the accumulator and NUM are the same in both cases, even if the
accululator or NUM (or both) is not a valid BCD number! This is true of the
6502, the 65C02, and the 65C816. (This has been tested on a Rockwell 6502,
a Synertek 6502, a GTE 65C02, and a GTE 65C816.) Again, the effect on the
accumulator is different in the two cases above, but the effect on the the V
flag is the same.
Since the EOR #$80 uses the result in the accumulator, the signed comparison won't work properly in decimal mode. For example:
SED
SEC
LDA #$80
SBC #$10
BVC LABEL ; $80 - $10 = $70, V = 1 since -128 - 16 = -144
EOR #$80 ; $70 eor $80 = $F0, so N = 1
LABEL
returns N = 1, which is correct since $80 (-128) < $10 (16), but:
SED
SEC
LDA #$10
SBC #$80
BVC LABEL ; $10 - $80 = $30, V = 1 since 16 - (-128) = 144
EOR #$80 ; $30 eor $80 = $B0, so N = 1
LABEL
also returns N = 1, which is not correct.
The 6502 and the 65C02 have a seldom-used pin named SO (DIP pin 38), which stands for Set Overflow. As its name suggests, the SO pin allows the hardware to set the V (overflow) flag, without using software instructions that affect the V flag. Since the signed comparison makes use the V flag, the SO pin is something to be aware of. However, the SO pin is usually not a problem for several reasons. First, the SO pin is seldom used, as noted above. Second, when the SO pin is used, it will usually be for a specific purpose, so WHEN it sets the V flag will be known (i.e. normally, it's not used to go around setting the V flag at random). Third, the BVC instruction, which tests the V flag, immediately follows the SBC instruction, which affects the V flag, so setting the V flag elsewhere won't change anything. Of course, all of this depends what exactly is connected to the SO pin. When troubleshooting signed comparisons, if the V flag is getting set when it shouldn't, it may be good idea to look at what is connected to the SO pin.
If the SO pin is interfering with signed comparisons, there is an another way to do signed comparisons. Remember that an 8-bit signed number ranges from -128 ($80) to 127 ($7F), and that -128 to -1 are represented by $80 to $FF and 0 to 127 are represented by $00 to $7F. If the most significant bit is inverted, then -128 ($80) to -1 ($FF) becomes $00 to $7F, and 0 ($00) to 127 ($7F) becomes $80 to $FF. So to do a signed comparison, invert the most significant bit of EACH number first, THEN do an unsigned comparison. For example, after:
LDA NUM2
EOR #$80 ; invert the most significant bit of NUM1
STA TEMP ; store the result so it can be used by a CMP instruction
LDA NUM1
EOR #$80 ; invert the most significant bit of NUM1
CMP TEMP
If the C flag is 0, then NUM1 (signed) < NUM2 (signed) and BCC will branchNote that, unlike in Section 5.1, the usual equality comparison result will be in the Z flag, but the unsigned comparison result is not available.
6 EXTENDING THE SIGNED COMPARISON BEYOND 8 BITS
Since the signed comparison in Section 5.1 does not give a equality comparison result, using a method similar to the one in section 4.3 will usually be the most convenient way to extend the signed comparsion beyond 8 bits.
Example 6.1: a 16-bit signed comparison which leaves the usual signed comparison result in the N flag
LDA NUM1L ; NUM1-NUM2
CMP NUM2L
LDA NUM1H
SBC NUM2H
BVC LABEL ; N eor V
EOR #$80
LABEL
Notice that the V flag isn't needed until after the high bytes have been
subtracted, so the low bytes can be subtracted with a CMP instruction and
an SEC instruction isn't needed.
Example 6.2: a 24-bit signed comparison which leaves the usual signed comparison result in the N flag
LDA NUM1L ; NUM1-NUM2
CMP NUM2L
LDA NUM1M
SBC NUM2M
LDA NUM1H
SBC NUM2H
BVC LABEL ; N eor V
EOR #$80
LABEL
It is possible to take an approach similar to the ones in Sections 4.1 and
4.2 which compared one byte a time. This may be occasionally useful.
However, some additional effort is necessary to obtain the equality
comparison result (remember that both of those approaches made use of the
equality comparison result). One way to do this is shown in the following
example.
Example 6.3: a 16-bit signed comparison that branches to LABEL4 if NUM1 < NUM2 (similar to Example 4.1.1 in Section 4.1)
SEC
LDA NUM1H ; compare high bytes
SBC NUM2H
BVC LABEL1 ; the equality comparison is in the Z flag here
EOR #$80 ; the Z flag is affected here
LABEL1 BMI LABEL4 ; if NUM1H < NUM2H then NUM1 < NUM2
BVC LABEL2 ; the Z flag was affected only if V is 1
EOR #$80 ; restore the Z flag to the value it had after SBC NUM2H
LABEL2 BNE LABEL3 ; if NUM1H <> NUM2H then NUM1 > NUM2 (so NUM1 >= NUM2)
LDA NUM1L ; compare low bytes
SBC NUM2L
BCC LABEL4 ; if NUM1L < NUM2L then NUM1 < NUM2
LABEL3
APPENDIX A: A PROGRAM THAT DEMONSTRATES THAT THE SIGNED COMPARISON WORKS
The signed comparison routine was presented above without any proof that it is correct. The following program demonstrates that the signed comparison works as described. This program below was not written for maximum speed (it takes about 3 seconds to complete at 1 MHz) or minimal space; it was written to be easy to understand and easy to modify or extend (so that other claims above can be tested if desired). Also, it was written to provide a template for writing other test/proof-style programs.
The approach is simple: compare two (8-bit) numbers, N1 and N2, testing all 256 possible values of N1 and N2. There are four cases:
For case 2, N1 will always be greater than N2, so the signed comparison result is checked.
For case 3, N1 will always be less than N2, so the signed comparison result is checked.
For case 4, note a useful property of signed numbers: in the range $80 to $FF, the smallest number is $80 (-128) and the largest number is $FF (-1). For unsigned numbers, in the range $80 to $FF, the smallest number is also $80 (128) and the largest number is also $FF (255), so the signed comparison result can be verified by simply checking it against the unsigned comparison result, like case 1. In other words, the signed numbers $80 (-128) and $81 (-127) do not represent the same numbers that the unsigned numbers $80 (128) and $81 (129) do, but $80 < $81 regardless of whether $80 and $81 are signed or unsigned numbers.
One slightly sneaky part of the program below is the use of TSX and TXS. First, a 1 is stored in ERROR until all tests pass. Then TSX is used to save the stack pointer during TEST. Then, if test fails in a subroutine, a TXS followed by an RTS will return not to the loops in TEST, but to whatever called TEST, and ERROR will contain 1, and N1 and N2 will contain the values they had when the test failed. This comes in handy for debugging.
; Test the signed compare routine
;
; Returns with ERROR = 0 if the test passes, ERROR = 1 if the test fails
;
; Three (additional) memory locations are used: ERROR, N1, and N2
; These may be located anywhere convenient in RAM
;
TEST CLD ; Clear decimal mode for test
LDA #1
STA ERROR ; Store 1 in ERROR until test passes
TSX ; Save stack pointer so subroutines can exit with ERROR = 1
;
; Test N1 positive, N2 positive
;
LDA #$00 ; 0
STA N1
PP1 LDA #$00 ; 0
STA N2
PP2 JSR SUCMP ; Verify that the signed and unsigned comparison agree
INC N2
BPL PP2
INC N1
BPL PP1
;
; Test N1 positive, N2 negative
;
LDA #$00 ; 0
STA N1
PN1 LDA #$80 ; -128
STA N2
PN2 JSR SCMP ; Signed comparison
BMI TEST1 ; if N1 (positive) < N2 (negative) exit with ERROR = 1
INC N2
BMI PN2
INC N1
BPL PN1
;
; Test N1 negative, N2 positive
;
LDA #$80 ; -128
STA N1
NP1 LDA #$00 ; 0
STA N2
NP2 JSR SCMP ; Signed comparison
BPL TEST1 ; if N1 (negative) >= N2 (positive) exit with ERROR = 1
INC N2
BPL NP2
INC N1
BMI NP1
;
; Test N1 negative, N2 negative
;
LDA #$80 ; -128
STA N1
NN1 LDA #$80 ; -128
STA N2
NN2 JSR SUCMP ; Verify that the signed and unsigned comparisons agree
INC N2
BMI NN2
INC N1
BMI NN1
LDA #0
STA ERROR ; All tests pass, so store 0 in ERROR
TEST1 RTS
; Signed comparison
;
; Returns with:
; N=0 (BPL branches) if N1 >= N2 (signed)
; N=1 (BMI branches) if N1 < N2 (signed)
;
; The unsigned comparison result is returned in the C flag (for free)
;
SCMP SEC
LDA N1 ; Compare N1 and N2
SBC N2
BVC SCMP1 ; Branch if V = 0
EOR #$80 ; Invert Accumulator bit 7 (which also inverts the N flag)
SCMP1 RTS
; Test the signed and unsigned comparisons to confirm that they agree
;
SUCMP JSR SCMP ; Signed (and unsigned) comparison
BCC SUCMP2 ; Branch if N1 < N2 (unsigned)
BPL SUCMP1 ; N1 >= N2 (unsigned), branch if N1 >= N2 (signed)
TXS ; reset stack and exit with ERROR = 1
SUCMP1 RTS
SUCMP2 BMI SUCMP3 ; N1 < N2 (unsigned), branch if N1 < N2 (signed)
TXS ; reset stack and exit with ERROR = 1
SUCMP3 RTS
Last Updated April 3, 2004.