6502 Signed Math

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
6502 Signed Math
by on (#225835)
Look, I'm not really looking to do anything fancy, it's just that I can't figure out how to do 16-bit signed addition/subtraction. I know the whole spiel about two's complement, however I'm lost in the details on how to extend 8-bit signed math to 16-bit land...

Could someone please help me? I'm specifically looking for examples of 16 bit signed add/subtract routines for the 6502, along with how 16-bit signed numbers are usually represented. I've searched in a lot of places so far and this is driving me nuts.
Re: 6502 Signed Math
by on (#225836)
This is the document I continually turn to for reference on this:
http://www.6502.org/tutorials/compare_beyond.html

It covers signed stuff, but also >=16-bit stuff too.

Though, just for addition and subtraction the signed operations are identical to the unsigned ones. CLC ADC or SEC SBC work the same for signed as unsigned, the only difference is when comparing things you have to look at different flags. (Signed comparison is where you have to look at that overflow flag, for example.)
Re: 6502 Signed Math
by on (#225837)
rainwarrior wrote:
This is the document I continually turn to for reference on this:
http://www.6502.org/tutorials/compare_beyond.html

It covers signed stuff, but also >=16-bit stuff too.

Though, just for addition and subtraction the signed operations are identical to the unsigned ones. CLC ADC or SEC SBC work the same for signed as unsigned, the only difference is when comparing things you have to look at different flags. (Signed comparison is where you have to look at that overflow flag, for example.)


I looked at that document.

I'm going to have to ask to be spoonfed, really. I've been trying for the past two days to properly add together a fixed number with a 16 bit signed offset with no success, and that's all because of the damn overflow/carry flags.

I mean, where IS the sign? On both bytes? If you add two values and it goes past the -127 +127 range the V flag gets set, but on a 16 bit signed number isn't the whole least significant byte used, and the V flag is for improper 7th bit/sign manipulation? How can you use the V flag to add/subtract anything into the MSB of the 16-bit number? I really, really need to see example code.
Re: 6502 Signed Math
by on (#225838)
The sign is the high bit on the high byte. The V flag is not used at all for doing simple adds and subtracts, but instead it's used when doing comparisons (16 bit CMP).

Adding two signed 16 bit numbers:

Code:
clc
lda foo+0
adc bar+0
sta result+0
lda foo+1
adc bar+1
sta result+1


For subtraction, replace adc with sbc.

One thing that might be confusing you is adding a signed 8 bit number to a signed 16 bit number. In that case, you have to first "sign extend" the 8 bit number to be 16 bits. If the 8 bit number is positive, the high byte is simply $00. If the 8 bit number is negative, the high byte is $FF.

For constants, you can use .lobyte and .hibyte:
Code:
clc
lda foo+0
adc #.lobyte(-30)
sta result+0
lda foo+1
adc #.hibyte(-30)
sta result+1
Re: 6502 Signed Math
by on (#225839)
Punch wrote:
I'm going to have to ask to be spoonfed, really. I've been trying for the past two days to properly add together a fixed number with a 16 bit signed offset with no success, and that's all because of the damn overflow/carry flags.

The overflow flag isn't part of the result of addition or subtraction, it's just information about whether an overflow has occurred when you did it. (This is important when doing a comparison though, you may need to look at it then.)

Punch wrote:
I mean, where IS the sign? On both bytes?

The sign is the highest bit. If it's an 8 bit number that's bit 7. If it's a 16 bit number that's bit 15.

When you convert an 8 bit signed number to a 16 bit signed number, you "extend the sign" by copying that high bit into every bit above it. $FE is -2 in 8-bit. $FFFE is -2 in 16-bit.

BTW, re: pubby's suggestion, you can also use < and > for .lobyte and .hibyte, if that's more succinct. (I find it easier to read.)

Punch wrote:
How can you use the V flag to add/subtract anything into the MSB of the 16-bit number? I really, really need to see example code.

V flag is only for checking for overflow (and relevant when you do comparisons). If you're doing multi-byte compares, the overflow result is only known after you've gone through the chain up to the high byte, so really only the last overflow value is relevant. (Carry on the other hand propagates through all the bytes, just like with unsigned.)
Re: 6502 Signed Math
by on (#225840)
You guys are life savers, thank you so much :mrgreen:

By the way, I also need to clip off the MSB of a signed number too for some unrelated stuff, a la what you would do with an unsigned number (just ignore the msb), and I came up with this:

Code:
;Gets lower 7 bits of signed 16 bit integer, conserves sign.
 .macro S16toS8
  .if (\# != 1)
   .fail
  .endif
   LDA \1 + 1
   BPL .plus\@
 .minus\@:
   LDA \1
   ORA #%1000_0000 ;Replace bit 7 with minus flag
               ;Negating +1 the # is unneeded since
               ;it's already two's complement.
   BRA .exit\@
 .plus\@:
   LDA \1
   AND #%0111_1111 ;That's it.
 .exit\@:
   .endm


Just copying the flag is enough to get the small 7 bit component of the whole number, right? I was playing around with the 6502 simulator on Easy 6502 and with a hex calculator and the results seemed to be correct, but I might be missing something.
Re: 6502 Signed Math
by on (#225843)
The usual way to truncate a signed number is the same as with unsigned. You just drop the high byte.

If the 16 bit (15+1) signed value was representable as 8 bit (7+1), the high bit of LSB is already appropriately set. (That sign extend principle above ensures this is true.)

If it was not representable, you'll get the modulo / wrapped around value just by dropping the MSB, like you would with unsigned numbers too.

You can copy the sign bit like you're suggesting, but I doubt that's a useful result. You've kept the sign the same, but the lower 7 bits are basically all bogus. I would say that if you do that the only useful bit of the result is the sign.

-129 = %1111,1111,0111,1111

Should it truncate to:

127 = %0111,1111 = wrapped 8-bit result of -128 - 1

Or:

-1 = %1111,1111 = (not sure how to justify this)