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 | 00 | 01 | 02 | 7e | 7f | 80 | 81 | 82 | fd | fe | ff | ||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
unsigned | 0 | 1 | 2 | 126 | 127 | 128 | 129 | 130 | 253 | 254 | 255 | ||
signed | 0 | 1 | 2 | 126 | 127 | -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.
- Guide by d_m