nxu/test-fix16.py

212 lines
6.5 KiB
Python

#!/usr/bin/python
from math import ceil, copysign, cos, floor, log, sin, sqrt, tan
from os import environ
from random import randint
from subprocess import Popen, PIPE, run
from sys import argv
def tosigned(x):
return x if x < 32768 else x - 65536
u8 = {'sz': 1 << 8, 'fmt': b'%02x'}
u16 = {'sz': 1 << 16, 'fmt': b'%04x'}
z16 = {'sz': 1 << 16, 'fmt': b'%04x'} # non-zero
p16 = {'sz': 1 << 16, 'fmt': b'%04x'} # positive
t16 = {'sz': 1 << 16, 'fmt': b'%04x'} # tangent, must not be pi/2
def eq(got, expected):
return got == expected
def booleq(got, expected):
return bool(got) == bool(expected)
def releq(got0, expected0):
got, expected = tosigned(got0), tosigned(expected0)
if (expected - 1) <= got and got <= (expected + 1):
return True
else:
error = abs(got - expected) / (abs(expected) + 0.001)
return error < 0.01
def sineq(got0, expected0):
got, expected = tosigned(got0), tosigned(expected0)
if (expected - 10) <= got and got <= (expected + 10):
return True
else:
return False
def taneq(got0, expected0):
got, expected = tosigned(got0), tosigned(expected0)
if (expected - 1) <= got and got <= (expected + 1):
return True
else:
error = abs(got - expected) / (abs(expected) + 0.001)
return error < 0.1
def testcase(p, sym, args, out, f, eq):
vals = []
for name, g in args:
val = randint(0, g['sz'] - 1)
while ((val == 0 and (g is z16 or g is p16)) or
(val >= 0x8000 and g is p16) or
(g is t16 and ((val >= 804) or ((val % 804) == 402)))):
val = randint(0, g['sz'] - 1)
vals.append((name, g, val))
p.stdin.write(sym)
for _, g, x in vals:
p.stdin.write(g['fmt'] % x)
p.stdin.write(b'\n')
p.stdin.flush()
got = int(p.stdout.readline().strip().decode('utf-8'), 16)
xs = [x for _, _, x in vals]
z = f(*xs)
expected = z
if eq(got, expected):
return None
else:
res = {'got': got, 'expected': expected}
for name, _, x in vals:
res[name] = x
return res
def test(p, trials, sym, args, out, f, eq=eq):
fails = 0
cases = []
for i in range(0, trials):
case1 = testcase(p, sym, args, out, f, eq)
if case1 is not None:
fails += 1
cases.append(case1)
name = sym.decode('utf-8')
if fails == 0:
print('%s passed %d trials' % (name, trials))
else:
print('%s failed %d/%d trials (%r)' % (name, fails, trials, cases))
def fromfix(n):
assert 0 <= n and n <= 65535
if n >= 32768:
res = (n - 65536) / 256
else:
res = n / 256
return res
bound = 32767 / 256
def tofix(x):
y = min(max(x, -bound), bound)
if y < 0:
res = int(ceil(65536 + y * 256))
else:
res = int(y * 256)
return res % 65536
def pipe():
return Popen(['uxncli', 'run.rom'], stdin=PIPE, stdout=PIPE)
def x16_add(x, y):
return (x + y) % 65536
def x16_sub(x, y):
return (x - y) % 65536
def x16_mul(x, y):
return tofix(fromfix(x) * fromfix(y))
def x16_div(x, y):
return tofix(fromfix(x) / fromfix(y))
def x16_quot(x, y):
n = x16_div(x, y)
if n < 0x7fff:
return n & 0xff00
elif n > 0x8001:
return (n + 255) & 0xff00
else:
return n
def x16_rem(x, y):
return x % y
def x16_is_whole(x):
return int((x & 0xff) == 0)
def x16_negate(x):
if x == 32768 or x == 0:
return x
else:
return 65536 - x
def x16_eq(x, y):
return x == y
def x16_ne(x, y):
return x != y
def x16_lt(x, y):
return int(tosigned(x) < tosigned(y))
def x16_gt(x, y):
return int(tosigned(x) > tosigned(y))
def x16_lteq(x, y):
return int(tosigned(x) <= tosigned(y))
def x16_gteq(x, y):
return int(tosigned(x) >= tosigned(y))
def x16_sqrt(x):
return int(sqrt(x / 256) * 256)
def x16_sin(x):
z = round(sin(x / 256) * 256)
return z if z >= 0 else 65536 + z
def x16_cos(x):
z = round(cos(x / 256) * 256)
return z if z >= 0 else 65536 + z
def x16_tan(x):
z0 = round(tan(x / 256) * 256)
z = min(max(z0, -0x7fff), 0x7fff)
return z if z >= 0 else 65536 + z
def x16_log(x):
z = round(log(x / 256) * 256)
return z if z >= 0 else 65536 + z
def x16_floor(x):
return floor(x / 256) * 256
def x16_ceil(x):
return (ceil(x / 256) * 256) % 65536
def x16_round(x):
return (round(x / 256) * 256) % 65536
def x16_trunc16(x):
r = tosigned(x) / 256
if r < 0:
return (65536 + ceil(r)) % 65536
else:
return floor(x / 256)
def x16_trunc8(x):
if tosigned(x) < 0:
return ceil(x / 256) % 256
else:
return floor(x / 256)
def main():
trials = int(argv[1]) if argv[1:] else 100
run(['uxnasm', 'test-fix16.tal', 'run.rom'])
p = pipe()
test(p, trials, b'+', [('x', u16), ('y', u16)], u16, x16_add)
test(p, trials, b'-', [('x', u16), ('y', u16)], u16, x16_sub)
test(p, trials, b'*', [('x', u16), ('y', u16)], u16, x16_mul)
test(p, trials, b'/', [('x', u16), ('y', z16)], u16, x16_div)
test(p, trials, b'\\', [('x', u16), ('y', z16)], u16, x16_quot)
test(p, trials, b'%', [('x', u16), ('y', z16)], u16, x16_rem)
test(p, trials, b'w', [('x', u16)], u8, x16_is_whole, eq=booleq)
test(p, trials, b'N', [('x', u16)], u16, x16_negate)
test(p, trials, b'=', [('x', u16), ('y', u16)], u8, x16_eq)
test(p, trials, b'!', [('x', u16), ('y', u16)], u8, x16_ne)
test(p, trials, b'<', [('x', u16), ('y', u16)], u8, x16_lt)
test(p, trials, b'>', [('x', u16), ('y', u16)], u8, x16_gt)
test(p, trials, b'{', [('x', u16), ('y', u16)], u8, x16_lteq)
test(p, trials, b'}', [('x', u16), ('y', u16)], u8, x16_gteq)
test(p, trials, b'F', [('x', u16)], u16, x16_floor)
test(p, trials, b'C', [('x', u16)], u16, x16_ceil)
test(p, trials, b'R', [('x', u16)], u16, x16_round)
test(p, trials, b'8', [('x', u16)], u16, x16_trunc8)
test(p, trials, b'T', [('x', u16)], u16, x16_trunc16)
# the next five are known to be somewhat inaccurate and use
# a "relaxed" equality predicate for testing purposes.
test(p, trials, b'r', [('x', p16)], u16, x16_sqrt, eq=releq)
test(p, trials, b's', [('x', p16)], u16, x16_sin, eq=sineq)
test(p, trials, b'c', [('x', p16)], u16, x16_cos, eq=sineq)
test(p, trials, b't', [('x', t16)], u16, x16_tan, eq=taneq)
test(p, trials, b'l', [('x', p16)], u16, x16_log, eq=releq)
p.stdin.write(b'\n\n')
p.stdin.flush()
p.stdin.close()
p.stdout.close()
p.kill()
if __name__ == "__main__":
main()