note that this is NOT official and you can make your own standard libraries and call it the same thing to your desire. this is purely a fun project that I started and I wanted to share my implementations of these functions. obviously some of these (mul, div, and mod) become obsolete if you add those to the ALU, though I wanted to try and create them using only vanilla Symphony.
this is a WIP, it's in beta v1.0 if you have any suggestions please let me know
note that all functions have been tested to some extent, and have not been boundary tested (i.e. they have not been tested for edge cases such as when an input is 0), though I think I have a pretty good idea of what would happen in those cases
#Standard Library for Math
33 messages · Page 1 of 1 (latest)
def the most computationally expensive one is lcm. it calls three functions, mul, gcd, and div, and gcd calls another function, mod, oh and not to mention all four of those functions contain loops.
actually all functions except lcm contain explicit loops, and even lcm contains implicit loops like i just mentioned
Support for negative/signed numbers would be great
You can either make a simple check that just returns 0 or you could infinite loop
updated pow to work with b = 0, here is the updated version:
pub pow:
push arg1
mov arg1, b
mov b, a
mov a, 1
powCheck:
cmp arg1, 0
je powEnd
powLoop:
call mul
dec arg1
jmp powCheck
powEnd:
pop arg1
ret
added absolute value function, this will help for implementing signed versions of each function
pub abs:
cmp a, 0
jge absEnd
neg a
absEnd:
ret
i also ditched the CS representations of each function next to each header (e.g. Multiplication, a = a * b has become Multiplication) because they weren't representing anything useful in my opinion
mul will work for b < 0, for a < 0 it ends up in an extremely long loop (it's not infinite because it will eventually loop back to 0, but my laptop can't handle a high enough tick speed to ensure that it will resolve to the correct value
updated mul to now work with any signed input
pub mul:
push arg1
push arg2
mov arg2, b
clr b
call abs
mov arg1, a
clr a
mulCheck:
cmp arg1, 0
je mulEnd
mulLoop:
add a, arg2
dec arg1
jmp mulCheck
mulEnd:
cmp b, 0
je mulPos
neg a
mulPos:
pop arg2
pop arg1
ret
div doesn't work if either are negative, for a < 0 it returns 0, and for b < 0 it gets stuck in a really long loop
here's the thing for pow, if you take a^-b this becomes 1/(a^b), and since i don't have fractions (no support for non-integers yet) this would truncate to 0
so i think if b is negative then pow should return 0
if a is negative, then if b is even pow returns |a|^b, and if b is odd then pow returns -(|a|^b)
-a % b is the same as (b-a) % b, but many programming langs do this thing where if a is negative then it outputs a negative modulus (for example, -6 == 1 in mod 7, but 1 % 7 gives 1 and -6 % 7 gives -6)
now we can't just do if a is negative then we return ((b-a) % b) - b, because if so then -7 % 7 gives -7 when we want it to give 0
so what we need to do is check if a = b, and if so return 0, except if b = 0 then we return a
lemme get some pseudocode
oh and one more thing, a % b is the same as a % -b, so we can just absolute b before we start
def mod(a, b):
if b == 0:
return a
b = abs(b)
if a < 0:
neg = 1
a = abs(a)
while a >= b:
a -= b
if neg == 1:
a = -a
return a
Repeated subtraction is a very slow division & mod method.
Long division is much quicker, especially for 32-bit numbers.
(Btw this currently does not work with the default ISA, as clr, dec and inc are not defined there, and add and sub always take 3 arguments. I assume clr * is an alias for mov *, 0?)
pub abs:
cmp r11, -1
jg absEnd
not r11, r11
add r11, 1, r11
absEnd:
mov r11, r13
ret
pub neg:
not r11, r13
add r13, 1, r13
ret
; r12: Divident | r11: Divisor | r13: Resultant
pub div:
and r11, 0x80000000, r13
push r13
and r12, 0x80000000, r13
push r13
call abs
mov r12, r11
mov r13, r12
call abs
mov r12, r11
mov r13, r12
mov zr, r13
DivStart:
cmp r12, r11
jl DivEnd
sub r12, r11, r12
add r13, 1, r13
jmp DivStart
DivEnd:
push16 r13
sub sp, 2, sp
pop r11
pop r13
add sp, 10, sp
xor r11, r13, r11
mov r12, r13
pop16 r12
cmp r11, 0x80000000
jne DivRet
not r12, r12
add r12, 1, r12
DivRet:
push r12
mov r13, r12
pop r13
sub sp, 8, sp
ret
; r12: Modulos | r13: Resultant
; r12: Divident | r11: Divisor
pub UnsignedDivide:
UnsignedDivideLoop:
cmp r12, r11
jl exit
sub r12, r11, r12
add r13, 1, r13
jmp UnsignedDivideLoop
exit:
ret
; r12: Modulos | r13: Resultant
I have these functions in my standard library. I have both versions of unsigned and signed division. Of course, it's op src, dst for me. You could put these to use
And of course, the library doesn't expect you to care about what's in r10, r11, r12, r13 so I never push those in the library.
yeah, inc and dec are aliases for add a, a, 1, and sub a, a, 1, and the two-argument functions are aliases for op a, a, b
and the one-argument functions are aliases of two-argument functions as in op a, a
Since we're sharing math functions, here is a (unsigned) division function with logarithmic runtime I wrote for my old snake implementation. It was written for 2.0, but it seems to still work:
; (r1, r2) -> (r1, r2)
; Uses r1-r4
;
; Returns the quotient (r2) and remainder (r1) of r1/r2
fn_divany:
mov r4, r2
mov r2, 0
mov r3, 1
_divany_msb:
cmp r4, 0
jl _divany_msbfound
lsl r4, r4, 1
lsl r3, r3, 1
jmp _divany_msb
_divany_msbfound:
cmp r1, r4
jb _divany_nosub
sub r1, r1, r4
add r2, r2, r3
_divany_nosub:
lsr r4, r4, 1
lsr r3, r3, 1
cmp r3, 0
jne _divany_msbfound
ret
I also found this fixed point multiply (optimized for allegro) in my old save that I don't think I ever shared:
const FIX = 6
; (r1, r2) -> r1
; Uses r1-r4
;
; Multiplies two unsigned fixed point numbers.
fn_fmul:
sub sp, sp, 4
; result
mov r3, 0
; save r5
store_32 [sp], r5
; remaining shift
mov r4, FIX
; ensure r1 is the smaller of the two inputs
cmp r1, r2
jbe _fmul_loopentry
mov flags, r2
mov r2, r1
mov r1, flags
jmp _fmul_loopentry
_fmul_loop:
; // All this in 5.5 cycles :D (on allegro)
; if (r1 & 1) != 0 {
; r3 += r2;
; }
; if r4 == 0 {
; r2 <<= 1;
; } else {
; r4 -= 1;
; r3 >>= 1;
; }
; r1 >>= 1;
and r5, r1, 1
add r3, r3, r2
sub r5, r5, 1
cmp r4, 0
and r5, r2, r5
lsl r2, r2, flags
lsr r1, r1, 1
xor flags, flags, 1
sub r3, r3, r5
sub r4, r4, flags
lsr r3, r3, flags
_fmul_loopentry:
; check if we are done
cmp r1, 0
jne _fmul_loop
; apply remaining shift
lsr r1, r3, r4
pop r5
ret
And this multiply function:
; Multiplies the values in r1 and r2, and returns the result in r3
fn_mul:
mov r3, 0
jmp _mul_test
_mul_loop:
mov flags, r1
jne _mul_after_add
add r3, r3, r2
_mul_after_add:
lsr r1, r1, 1
lsl r2, r2, 1
_mul_test:
cmp r1, 0
jne _mul_loop
ret
:D
Jokes aside. Absolutely insane work.