nxu/math-notes.txt

104 lines
3.6 KiB
Plaintext
Raw Normal View History

2023-10-25 13:03:52 -04:00
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.
2023-12-29 15:41:13 -05:00
@abs ( x^ -- abs-x^ sign^ )
DUP #7f GTH #fe MUL INC STHk MUL STHr JMP2r
2023-10-25 13:03:52 -04:00
@signed-div ( x^ y^ -- x/y^ )
2023-12-29 15:41:13 -05:00
abs STH SWP abs STH SWP DIV MULr STHr MUL JMP2r
2023-10-25 13:03:52 -04:00
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!