101 lines
3.5 KiB
Plaintext
101 lines
3.5 KiB
Plaintext
|
Some questions and answers about doing math in UXN
|
||
|
--------------------------------------------------
|
||
|
|
||
|
Q: How can I handle negative integers in UXN?
|
||
|
|
||
|
A: Uxn doesn't have any built-in support for signed integers. However, you can
|
||
|
emulate signed numbers using unsigned numbers by treating some of the values
|
||
|
as having different (negative) values from their unsigned values.
|
||
|
|
||
|
For example, treating unsigned bytes as signed results in the following:
|
||
|
|
||
|
unsigned unsigned signed
|
||
|
hex decimal decimal
|
||
|
#00 0 0
|
||
|
#01 1 1
|
||
|
#02 2 2
|
||
|
... ... ...
|
||
|
#7e 126 126
|
||
|
#7f 127 127
|
||
|
#80 128 -128
|
||
|
#81 129 -127
|
||
|
#82 130 -126
|
||
|
... ... ...
|
||
|
#fd 253 -3
|
||
|
#fe 254 -2
|
||
|
#ff 255 -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) = if n > 127 then n else 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:
|
||
|
|
||
|
EQU and NEQ
|
||
|
ADD (example: `#13 #ff ADD` returns #12)
|
||
|
SUB (exampel: `#02 #03 SUB` returns #ff)
|
||
|
MUL (example: `#02 #ff MUL` returns #fe)
|
||
|
|
||
|
Other instructions will not handle "negative" integers correctly:
|
||
|
|
||
|
GTH and LTH:
|
||
|
|
||
|
1. these work correctly when comparing values with the same sign
|
||
|
(example: `#80 #ff LTH` returns #01)
|
||
|
|
||
|
2. however, LTH will consider negative values greater than non-negative
|
||
|
values, which is incorrect.
|
||
|
(example: `#ff #00 GTH` returns #01, but -1 is less than 0)
|
||
|
|
||
|
These implementations will safely compare "signed" bytes:
|
||
|
|
||
|
@signed-lth ( x^ y^ -- x<y^ )
|
||
|
DUP2 #8080 AND2 EQU ?&diff LTH JMP2r &diff LTH #00 NEQ JMP2r
|
||
|
|
||
|
@signed-gth ( x^ y^ -- x<y^ )
|
||
|
DUP2 #8080 AND2 EQU ?&diff GTH JMP2r &diff GTH #00 NEQ JMP2r
|
||
|
|
||
|
DIV:
|
||
|
|
||
|
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.
|
||
|
|
||
|
@signed-div ( x^ y^ -- x/y^ )
|
||
|
DUP2 #8080 AND2 EQU STH DIV STHr ?&same #ff MUL &same JMP2r
|
||
|
|
||
|
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)
|
||
|
|
||
|
SFT:
|
||
|
|
||
|
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.
|
||
|
|
||
|
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.
|
||
|
|
||
|
Signed numbers will also need their own routines for decimal input and output,
|
||
|
if those are required by your program.
|
||
|
|
||
|
Good luck!
|