#Standard Library for Math

33 messages · Page 1 of 1 (latest)

long lily
#

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

#

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

gritty loom
#

Support for negative/signed numbers would be great

gritty loom
long lily
#

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
bleak comet
#

Repeated subtraction is a very slow division & mod method.
Long division is much quicker, especially for 32-bit numbers.

elfin oasis
#

(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?)

gritty loom
#
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.

long lily
#

and the one-argument functions are aliases of two-argument functions as in op a, a

elfin oasis
#

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
elfin oasis
#

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
gritty loom
long lily
#

we are now in beta v1.1
haven't had time to implement your suggestions, but I will certainly do so when I can
looking to move division from iterative subtraction to long division and add signed support for all functions
thank you all for your help so far ❤️

#

gtg now