XXIIVV

Using and operating on negative numbers in Uxntal.

Uxn doesn't have built-in support for negative integers. However, you can emulate signed numbers by treating some unsigned values as negative. For example, treating unsigned bytes as signed results in the following:

hex 000102 7e7f808182 fdfeff
unsigned 012 126127128129130 253254255
signed 012 126127-128-127-126 -3-2-1

The first 128 integers (0-127) are represented the same as unsigned and signed, but the latter 128 are different. The basic idea here is that for values greater than #7f (127) we subtract 256 to get their signed value:

signed = n < 128 ? n : n - 256

It turns out that many unsigned operations "work" even when treating the values as signed. (In other words, you get the same result as you would have using a language with signed integer types.) The following arithmetic instructions work correctly with "signed" values:

#13 #ff ADD returns #12
#02 #03 SUB returns #ff
#02 #ff MUL returns #fe

Be careful! The smallest negative value (-128 for bytes, -32768 for shorts) has no corresponding positive value. This means that some operations will not work as expected:

#80 #ff MUL returns #80 (-128 * -1 = -128)
#00 #80 SUB returns #80 (0 - (-128) = -128)

Also, negative and positive values will "wrap around" in the usual way when dealing with two's-complement representations:

#7f #01 ADD returns #80 (127 + 1 = -128)
#80 #01 SUB returns #7f (-128 - 1 = 127)
#80 #80 ADD returns #00 (-128 + (-128) = 0)

Other instructions will not handle "negative" integers correctly. These routines will safely compare "signed" bytes:

@signed-lth ( x y -- res )
	DUP2 #8080 AND2 EQU ?&diff LTH JMP2r &diff LTH #00 NEQ JMP2r

@signed-gth ( x y -- res )
	DUP2 #8080 AND2 EQU ?&diff GTH JMP2r &diff GTH #00 NEQ JMP2r

Similarly, division will not correctly handle signed values. The simplest way to handle this is to make both values non-negative, do unsigned division (i.e. DIV) and then set the correct sign at the end.

@abs ( x -- abs-x sign )
	DUP #7f GTH #fe MUL INC STHk MUL STHr JMP2r

@signed-div ( x y -- x/y )
	abs STH SWP abs STH SWP DIV MULr STHr MUL JMP2r

The unsigned shift operator treats the sign bit like any other. This means shifting left will lose the sign bit (reversing the sign) and that shifting right will convert the sign bit into a value bit. Signed numbers will also need their own routines for decimal input and output, if those are required by your program.

@signed-print ( num -- )
	( - ) DUP #80 LTH ?{ LIT "- #18 DEO #7f AND #80 SWP SUB }
	( 100 ) DUP #64 DIV signed-print/emit
	( 10 ) DUP #0a DIV signed-print/base
	&base ( digit -- ) #0a DIVk MUL SUB
	&emit ( digit -- ) LIT "0 ADD #18 DEO JMP2r

If you need a sign-aware shift you'll likely want to convert negatives to positive values, perform a shift, and then restore the sign. Keep in mind that -128 cannot be converted to a positive value, and may require special treatment.