#!/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, exit def tosigned(x): return x if x < 32768 else x - 65536 u8 = {'sz': 1 << 8, 'fmt': b'%02x'} x16 = {'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 (val == 0x8000 and g is x16) 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 e = run(['uxnasm', 'test-fix16.tal', 'run.rom']) if e.returncode != 0: print('the command `uxnasm test-fix16.tal run.rom` failed!') exit(e.returncode) p = pipe() test(p, trials, b'+', [('x', x16), ('y', x16)], x16, x16_add) test(p, trials, b'-', [('x', x16), ('y', x16)], x16, x16_sub) test(p, trials, b'*', [('x', x16), ('y', x16)], x16, x16_mul) test(p, trials, b'/', [('x', x16), ('y', z16)], x16, x16_div) test(p, trials, b'\\', [('x', x16), ('y', z16)], x16, x16_quot) test(p, trials, b'%', [('x', x16), ('y', z16)], x16, x16_rem) test(p, trials, b'w', [('x', x16)], u8, x16_is_whole, eq=booleq) test(p, trials, b'N', [('x', x16)], x16, x16_negate) test(p, trials, b'=', [('x', x16), ('y', x16)], u8, x16_eq) test(p, trials, b'!', [('x', x16), ('y', x16)], u8, x16_ne) test(p, trials, b'<', [('x', x16), ('y', x16)], u8, x16_lt) test(p, trials, b'>', [('x', x16), ('y', x16)], u8, x16_gt) test(p, trials, b'{', [('x', x16), ('y', x16)], u8, x16_lteq) test(p, trials, b'}', [('x', x16), ('y', x16)], u8, x16_gteq) test(p, trials, b'F', [('x', x16)], x16, x16_floor) test(p, trials, b'C', [('x', x16)], x16, x16_ceil) test(p, trials, b'R', [('x', x16)], x16, x16_round) test(p, trials, b'8', [('x', x16)], x16, x16_trunc8) test(p, trials, b'T', [('x', x16)], x16, 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)], x16, x16_sqrt, eq=releq) test(p, trials, b's', [('x', p16)], x16, x16_sin, eq=sineq) test(p, trials, b'c', [('x', p16)], x16, x16_cos, eq=sineq) test(p, trials, b't', [('x', t16)], x16, x16_tan, eq=taneq) test(p, trials, b'l', [('x', p16)], x16, 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()