Merge pull request #5311 from Kelimion/big-tests

Turn `core:math/bìg` tests into regular `core:testing` tests.
This commit is contained in:
Jeroen van Rijn
2025-06-11 00:56:21 +02:00
committed by GitHub
9 changed files with 5649 additions and 1163 deletions
-6
View File
@@ -257,12 +257,6 @@ jobs:
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
cd tests\documentation cd tests\documentation
call build.bat call build.bat
- name: core:math/big tests
shell: cmd
run: |
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat
cd tests\core\math\big
call build.bat
- name: Odin check examples/all for Windows 32bits - name: Odin check examples/all for Windows 32bits
shell: cmd shell: cmd
run: | run: |
+2 -2
View File
@@ -1660,13 +1660,13 @@ internal_int_sqrt :: proc(dest, src: ^Int, allocator := context.allocator) -> (e
if internal_gte(y, x) { if internal_gte(y, x) {
internal_swap(dest, x) internal_swap(dest, x)
return nil return internal_clamp(dest)
} }
internal_swap(x, y) internal_swap(x, y)
} }
internal_swap(dest, x) internal_swap(dest, x)
return err return internal_clamp(dest)
} }
internal_sqrt :: proc { internal_int_sqrt, } internal_sqrt :: proc { internal_int_sqrt, }
+1 -1
View File
@@ -310,7 +310,7 @@ int_atoi :: proc(res: ^Int, input: string, radix := i8(10), allocator := context
res.sign = sign res.sign = sign
} }
return nil return internal_clamp(res)
} }
-16
View File
@@ -1,16 +0,0 @@
@echo off
rem math/big tests
set PATH_TO_ODIN==..\..\..\..\odin
set TEST_ARGS=-fast-tests
set TEST_ARGS=-no-random
set TEST_ARGS=
set OUT_NAME=math_big_test_library.dll
set COMMON=-build-mode:shared -show-timings -no-bounds-check -define:MATH_BIG_EXE=false -vet -strict-style
echo ---
echo Running core:math/big tests
echo ---
%PATH_TO_ODIN% build . %COMMON% -o:speed -out:%OUT_NAME%
python3 test.py %TEST_ARGS%
%PATH_TO_ODIN% test test_core_math_big.odin -file
@@ -7,7 +7,6 @@
# The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks. # The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
# #
from ctypes import *
from random import * from random import *
import math import math
import os import os
@@ -17,19 +16,53 @@ import gc
from enum import Enum from enum import Enum
import argparse import argparse
parser = argparse.ArgumentParser( LEG_BITS = 60
description = "Odin core:math/big test suite",
epilog = "By default we run regression and random tests with preset parameters.",
formatter_class = argparse.ArgumentDefaultsHelpFormatter,
)
# vectors = open('../test_vectors.odin', 'w')
# Normally, we report the number of passes and fails. With this option set, we exit at first fail. vectors.write("""package test_core_math_big
#
parser.add_argument( import "core:math/big"
"-exit-on-fail",
help = "Exit when a test fails", // GENERATED -=- GENERATED -=- GENERATED -=- GENERATED -=- GENERATED -=- GENERATED -=- GENERATED -=- GENERATED -=- GENERATED
action = "store_true", //
// This file is generated using `test_generator.py`
//
// GENERATED -=- GENERATED -=- GENERATED -=- GENERATED -=- GENERATED -=- GENERATED -=- GENERATED -=- GENERATED -=- GENERATED
Big_Test_Operation :: enum {
Add,
Sub,
Mul,
Div,
Sqr,
Log,
Sqrt,
Pow,
Root,
Shl,
Shr,
Shr_Signed,
Factorial,
Gcd,
Lcm,
Is_Square,
}
Big_Test_Vector :: struct {
op: Big_Test_Operation,
a: string,
b: string,
exp: string,
err: big.Error,
}
big_test_vectors := []Big_Test_Vector{
""")
parser = argparse.ArgumentParser(
description = "Odin core:math/big test suite generator",
epilog = "By default we generate regression and random tests with preset parameters.",
formatter_class = argparse.ArgumentDefaultsHelpFormatter,
) )
# #
@@ -39,7 +72,7 @@ no_random = parser.add_mutually_exclusive_group()
no_random.add_argument( no_random.add_argument(
"-no-random", "-no-random",
help = "No random tests", help = "Don't generate random tests",
action = "store_true", action = "store_true",
) )
@@ -76,14 +109,12 @@ timed_or_fast.add_argument(
args = parser.parse_args() args = parser.parse_args()
EXIT_ON_FAIL = args.exit_on_fail
# #
# How many iterations of each random test do we want to run? # How many iterations of each random test do we want to run?
# #
BITS_AND_ITERATIONS = [ BITS_AND_ITERATIONS = [
( 120, 10_000), ( 120, 100),
( 1_200, 1_000), ( 1_200, 100),
( 4_096, 100), ( 4_096, 100),
(12_000, 10), (12_000, 10),
] ]
@@ -96,20 +127,6 @@ if args.fast_tests:
if args.no_random: if args.no_random:
BITS_AND_ITERATIONS = [] BITS_AND_ITERATIONS = []
#
# Where is the DLL? If missing, build using: `odin build . -build-mode:shared`
#
if platform.system() == "Windows":
LIB_PATH = os.getcwd() + os.sep + "math_big_test_library.dll"
elif platform.system() == "Linux":
LIB_PATH = os.getcwd() + os.sep + "math_big_test_library.so"
elif platform.system() == "Darwin":
LIB_PATH = os.getcwd() + os.sep + "math_big_test_library.dylib"
else:
print("Platform is unsupported.")
exit(1)
TOTAL_TIME = 0 TOTAL_TIME = 0
UNTIL_TIME = 0 UNTIL_TIME = 0
UNTIL_ITERS = 0 UNTIL_ITERS = 0
@@ -148,96 +165,6 @@ class Error(Enum):
# #
gc.disable() gc.disable()
#
# Set up exported procedures
#
try:
l = cdll.LoadLibrary(LIB_PATH)
except:
print("Couldn't find or load " + LIB_PATH + ".")
exit(1)
def load(export_name, args, res):
export_name.argtypes = args
export_name.restype = res
return export_name
#
# Result values will be passed in a struct { res: cstring, err: Error }
#
class Res(Structure):
_fields_ = [("res", c_char_p), ("err", c_uint64)]
initialize_constants = load(l.test_initialize_constants, [], c_uint64)
NAILS = initialize_constants()
LEG_BITS = 64 - NAILS
print("LEG BITS: ", LEG_BITS)
error_string = load(l.test_error_string, [c_byte], c_char_p)
add = load(l.test_add, [c_char_p, c_char_p ], Res)
sub = load(l.test_sub, [c_char_p, c_char_p ], Res)
mul = load(l.test_mul, [c_char_p, c_char_p ], Res)
sqr = load(l.test_sqr, [c_char_p ], Res)
div = load(l.test_div, [c_char_p, c_char_p ], Res)
# Powers and such
int_log = load(l.test_log, [c_char_p, c_longlong], Res)
int_pow = load(l.test_pow, [c_char_p, c_longlong], Res)
int_sqrt = load(l.test_sqrt, [c_char_p ], Res)
int_root_n = load(l.test_root_n, [c_char_p, c_longlong], Res)
# Logical operations
int_shl_leg = load(l.test_shl_leg, [c_char_p, c_longlong], Res)
int_shr_leg = load(l.test_shr_leg, [c_char_p, c_longlong], Res)
int_shl = load(l.test_shl, [c_char_p, c_longlong], Res)
int_shr = load(l.test_shr, [c_char_p, c_longlong], Res)
int_shr_signed = load(l.test_shr_signed, [c_char_p, c_longlong], Res)
int_factorial = load(l.test_factorial, [c_uint64 ], Res)
int_gcd = load(l.test_gcd, [c_char_p, c_char_p ], Res)
int_lcm = load(l.test_lcm, [c_char_p, c_char_p ], Res)
is_square = load(l.test_is_square, [c_char_p ], Res)
def test(test_name: "", res: Res, param=[], expected_error = Error.Okay, expected_result = "", radix=16):
passed = True
r = None
err = Error(res.err)
if err != expected_error:
error_loc = res.res.decode('utf-8')
error = "{}: {} in '{}'".format(test_name, err, error_loc)
if len(param):
error += " with params {}".format(param)
print(error, flush=True)
passed = False
elif err == Error.Okay:
r = None
try:
r = res.res.decode('utf-8')
r = int(res.res, radix)
except:
pass
if r != expected_result:
error = "{}: Result was '{}', expected '{}'".format(test_name, r, expected_result)
if len(param):
error += " with params {}".format(param)
print(error, flush=True)
passed = False
if EXIT_ON_FAIL and not passed: exit(res.err)
return passed
def arg_to_odin(a): def arg_to_odin(a):
if a >= 0: if a >= 0:
s = hex(a)[2:] s = hex(a)[2:]
@@ -281,58 +208,58 @@ def big_integer_lcm(a, b):
lcm = b // math.gcd(a, b) lcm = b // math.gcd(a, b)
return abs(a * lcm) return abs(a * lcm)
def write_test_case(op = "", a = 0, b = 0, expected_result = 0, expected_error = Error.Okay):
def test_arg_to_odin(a):
if a >= 0:
s = hex(a)[2:]
else:
s = '-' + hex(a)[3:]
return s
vectors.write("\t{{{}, ".format(op))
vectors.write("\"{}\", ".format(test_arg_to_odin(a)))
vectors.write("\"{}\", ".format(test_arg_to_odin(b)))
if expected_result == None:
vectors.write("\"0\", ")
else:
vectors.write("\"{}\", ".format(test_arg_to_odin(expected_result)))
vectors.write("{}}},\n".format(expected_error)[5:])
def test_add(a = 0, b = 0, expected_error = Error.Okay): def test_add(a = 0, b = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), arg_to_odin(b)] args = [arg_to_odin(a), arg_to_odin(b)]
res = add(*args)
expected_result = None expected_result = None
if expected_error == Error.Okay: if expected_error == Error.Okay:
expected_result = a + b expected_result = a + b
return test("test_add", res, [a, b], expected_error, expected_result)
write_test_case(".Add", a, b, expected_result, expected_error)
def test_sub(a = 0, b = 0, expected_error = Error.Okay): def test_sub(a = 0, b = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), arg_to_odin(b)] args = [arg_to_odin(a), arg_to_odin(b)]
res = sub(*args)
expected_result = None expected_result = None
if expected_error == Error.Okay: if expected_error == Error.Okay:
expected_result = a - b expected_result = a - b
return test("test_sub", res, [a, b], expected_error, expected_result)
write_test_case(".Sub", a, b, expected_result, expected_error)
def test_mul(a = 0, b = 0, expected_error = Error.Okay): def test_mul(a = 0, b = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), arg_to_odin(b)] args = [arg_to_odin(a), arg_to_odin(b)]
try:
res = mul(*args)
except OSError as e:
print("{} while trying to multiply {} x {}.".format(e, a, b))
if EXIT_ON_FAIL: exit(3)
return False
expected_result = None expected_result = None
if expected_error == Error.Okay: if expected_error == Error.Okay:
expected_result = a * b expected_result = a * b
return test("test_mul", res, [a, b], expected_error, expected_result)
write_test_case(".Mul", a, b, expected_result, expected_error)
def test_sqr(a = 0, b = 0, expected_error = Error.Okay): def test_sqr(a = 0, b = 0, expected_error = Error.Okay):
args = [arg_to_odin(a)] args = [arg_to_odin(a)]
try:
res = sqr(*args)
except OSError as e:
print("{} while trying to square {}.".format(e, a))
if EXIT_ON_FAIL: exit(3)
return False
expected_result = None expected_result = None
if expected_error == Error.Okay: if expected_error == Error.Okay:
expected_result = a * a expected_result = a * a
return test("test_sqr", res, [a], expected_error, expected_result)
write_test_case(".Sqr", a, b, expected_result, expected_error)
def test_div(a = 0, b = 0, expected_error = Error.Okay): def test_div(a = 0, b = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), arg_to_odin(b)] args = [arg_to_odin(a), arg_to_odin(b)]
try:
res = div(*args)
except OSError as e:
print("{} while trying divide to {} / {}.".format(e, a, b))
if EXIT_ON_FAIL: exit(3)
return False
expected_result = None expected_result = None
if expected_error == Error.Okay: if expected_error == Error.Okay:
# #
@@ -344,22 +271,19 @@ def test_div(a = 0, b = 0, expected_error = Error.Okay):
expected_result = int(-(a // abs((b)))) expected_result = int(-(a // abs((b))))
else: else:
expected_result = a // b if b != 0 else None expected_result = a // b if b != 0 else None
return test("test_div", res, [a, b], expected_error, expected_result)
write_test_case(".Div", a, b, expected_result, expected_error)
def test_log(a = 0, base = 0, expected_error = Error.Okay): def test_log(a = 0, base = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), base] args = [arg_to_odin(a), base]
res = int_log(*args)
expected_result = None expected_result = None
if expected_error == Error.Okay: if expected_error == Error.Okay:
expected_result = int(math.log(a, base)) expected_result = int(math.log(a, base))
return test("test_log", res, [a, base], expected_error, expected_result)
write_test_case(".Log", a, base, expected_result, expected_error)
def test_pow(base = 0, power = 0, expected_error = Error.Okay): def test_pow(base = 0, power = 0, expected_error = Error.Okay):
args = [arg_to_odin(base), power] args = [arg_to_odin(base), power]
res = int_pow(*args)
expected_result = None expected_result = None
if expected_error == Error.Okay: if expected_error == Error.Okay:
if power < 0: if power < 0:
@@ -368,24 +292,19 @@ def test_pow(base = 0, power = 0, expected_error = Error.Okay):
# NOTE(Jeroen): Don't use `math.pow`, it's a floating point approximation. # NOTE(Jeroen): Don't use `math.pow`, it's a floating point approximation.
# Use built-in `pow` or `a**b` instead. # Use built-in `pow` or `a**b` instead.
expected_result = pow(base, power) expected_result = pow(base, power)
return test("test_pow", res, [base, power], expected_error, expected_result)
write_test_case(".Pow", base, power, expected_result, expected_error)
def test_sqrt(number = 0, expected_error = Error.Okay): def test_sqrt(number = 0, expected_error = Error.Okay):
args = [arg_to_odin(number)] args = [arg_to_odin(number)]
try:
res = int_sqrt(*args)
except OSError as e:
print("{} while trying to sqrt {}.".format(e, number))
if EXIT_ON_FAIL: exit(3)
return False
expected_result = None expected_result = None
if expected_error == Error.Okay: if expected_error == Error.Okay:
if number < 0: if number < 0:
expected_result = 0 expected_result = 0
else: else:
expected_result = big_integer_sqrt(number) expected_result = big_integer_sqrt(number)
return test("test_sqrt", res, [number], expected_error, expected_result)
write_test_case(".Sqrt", number, 0, expected_result, expected_error)
def root_n(number, root): def root_n(number, root):
u, s = number, number + 1 u, s = number, number + 1
@@ -397,7 +316,6 @@ def root_n(number, root):
def test_root_n(number = 0, root = 0, expected_error = Error.Okay): def test_root_n(number = 0, root = 0, expected_error = Error.Okay):
args = [arg_to_odin(number), root] args = [arg_to_odin(number), root]
res = int_root_n(*args)
expected_result = None expected_result = None
if expected_error == Error.Okay: if expected_error == Error.Okay:
if number < 0: if number < 0:
@@ -405,19 +323,18 @@ def test_root_n(number = 0, root = 0, expected_error = Error.Okay):
else: else:
expected_result = root_n(number, root) expected_result = root_n(number, root)
return test("test_root_n", res, [number, root], expected_error, expected_result) write_test_case(".Root", number, root, expected_result, expected_error)
def test_shl_leg(a = 0, digits = 0, expected_error = Error.Okay): def test_shl_leg(a = 0, digits = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), digits] args = [arg_to_odin(a), digits]
res = int_shl_leg(*args)
expected_result = None expected_result = None
if expected_error == Error.Okay: if expected_error == Error.Okay:
expected_result = a << (digits * LEG_BITS) expected_result = a << (digits * LEG_BITS)
return test("test_shl_leg", res, [a, digits], expected_error, expected_result)
write_test_case(".Shl", a, (digits * LEG_BITS), expected_result, expected_error)
def test_shr_leg(a = 0, digits = 0, expected_error = Error.Okay): def test_shr_leg(a = 0, digits = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), digits] args = [arg_to_odin(a), digits]
res = int_shr_leg(*args)
expected_result = None expected_result = None
if expected_error == Error.Okay: if expected_error == Error.Okay:
if a < 0: if a < 0:
@@ -426,19 +343,18 @@ def test_shr_leg(a = 0, digits = 0, expected_error = Error.Okay):
else: else:
expected_result = a >> (digits * LEG_BITS) expected_result = a >> (digits * LEG_BITS)
return test("test_shr_leg", res, [a, digits], expected_error, expected_result) write_test_case(".Shr", a, (digits * LEG_BITS), expected_result, expected_error)
def test_shl(a = 0, bits = 0, expected_error = Error.Okay): def test_shl(a = 0, bits = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), bits] args = [arg_to_odin(a), bits]
res = int_shl(*args)
expected_result = None expected_result = None
if expected_error == Error.Okay: if expected_error == Error.Okay:
expected_result = a << bits expected_result = a << bits
return test("test_shl", res, [a, bits], expected_error, expected_result)
write_test_case(".Shl", a, bits, expected_result, expected_error)
def test_shr(a = 0, bits = 0, expected_error = Error.Okay): def test_shr(a = 0, bits = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), bits] args = [arg_to_odin(a), bits]
res = int_shr(*args)
expected_result = None expected_result = None
if expected_error == Error.Okay: if expected_error == Error.Okay:
if a < 0: if a < 0:
@@ -447,58 +363,47 @@ def test_shr(a = 0, bits = 0, expected_error = Error.Okay):
else: else:
expected_result = a >> bits expected_result = a >> bits
return test("test_shr", res, [a, bits], expected_error, expected_result) write_test_case(".Shr", a, bits, expected_result, expected_error)
def test_shr_signed(a = 0, bits = 0, expected_error = Error.Okay): def test_shr_signed(a = 0, bits = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), bits] args = [arg_to_odin(a), bits]
res = int_shr_signed(*args)
expected_result = None expected_result = None
if expected_error == Error.Okay: if expected_error == Error.Okay:
expected_result = a >> bits expected_result = a >> bits
return test("test_shr_signed", res, [a, bits], expected_error, expected_result) write_test_case(".Shr_Signed", a, bits, expected_result, expected_error)
def test_factorial(number = 0, expected_error = Error.Okay): def test_factorial(number = 0, expected_error = Error.Okay):
args = [number] args = [number]
try:
res = int_factorial(*args)
except OSError as e:
print("{} while trying to factorial {}.".format(e, number))
if EXIT_ON_FAIL: exit(3)
return False
expected_result = None expected_result = None
if expected_error == Error.Okay: if expected_error == Error.Okay:
expected_result = math.factorial(number) expected_result = math.factorial(number)
return test("test_factorial", res, [number], expected_error, expected_result) write_test_case(".Factorial", number, 0, expected_result, expected_error)
def test_gcd(a = 0, b = 0, expected_error = Error.Okay): def test_gcd(a = 0, b = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), arg_to_odin(b)] args = [arg_to_odin(a), arg_to_odin(b)]
res = int_gcd(*args)
expected_result = None expected_result = None
if expected_error == Error.Okay: if expected_error == Error.Okay:
expected_result = math.gcd(a, b) expected_result = math.gcd(a, b)
return test("test_gcd", res, [a, b], expected_error, expected_result) write_test_case(".Gcd", a, b, expected_result, expected_error)
def test_lcm(a = 0, b = 0, expected_error = Error.Okay): def test_lcm(a = 0, b = 0, expected_error = Error.Okay):
args = [arg_to_odin(a), arg_to_odin(b)] args = [arg_to_odin(a), arg_to_odin(b)]
res = int_lcm(*args)
expected_result = None expected_result = None
if expected_error == Error.Okay: if expected_error == Error.Okay:
expected_result = big_integer_lcm(a, b) expected_result = big_integer_lcm(a, b)
return test("test_lcm", res, [a, b], expected_error, expected_result) write_test_case(".Lcm", a, b, expected_result, expected_error)
def test_is_square(a = 0, b = 0, expected_error = Error.Okay): def test_is_square(a = 0, b = 0, expected_error = Error.Okay):
args = [arg_to_odin(a)] args = [arg_to_odin(a)]
res = is_square(*args) expected_result = False
expected_result = None if expected_error == Error.Okay and a > 0:
if expected_error == Error.Okay: expected_result = big_integer_sqrt(a) ** 2 == a
expected_result = str(big_integer_sqrt(a) ** 2 == a) if a > 0 else "False"
return test("test_is_square", res, [a], expected_error, expected_result) write_test_case(".Is_Square", a, 0, expected_result, expected_error)
# TODO(Jeroen): Make sure tests cover edge cases, fast paths, and so on. # TODO(Jeroen): Make sure tests cover edge cases, fast paths, and so on.
# #
@@ -647,32 +552,16 @@ if __name__ == '__main__':
for test_proc in TESTS: for test_proc in TESTS:
max_name = max(max_name, len(test_proc.__name__)) max_name = max(max_name, len(test_proc.__name__))
fmt_string = "{name:>{max_name}}: {count_pass:7,} passes and {count_fail:7,} failures in {timing:9.3f} ms." fmt_string = "{name:>{max_name}}, {test_count} tests"
fmt_string = fmt_string.replace("{max_name}", str(max_name)) fmt_string = fmt_string.replace("{max_name}", str(max_name))
for test_proc in TESTS: for test_proc in TESTS:
count_pass = 0 count = 0
count_fail = 0
TIMINGS = {}
for t in TESTS[test_proc]: for t in TESTS[test_proc]:
start = time.perf_counter() count += 1
res = test_proc(*t) test_proc(*t)
diff = time.perf_counter() - start
TOTAL_TIME += diff
if test_proc not in TIMINGS: print(fmt_string.format(name=test_proc.__name__, test_count=count))
TIMINGS[test_proc] = diff
else:
TIMINGS[test_proc] += diff
if res:
count_pass += 1
total_passes += 1
else:
count_fail += 1
total_failures += 1
print(fmt_string.format(name=test_proc.__name__, count_pass=count_pass, count_fail=count_fail, timing=TIMINGS[test_proc] * 1_000))
for BITS, ITERATIONS in BITS_AND_ITERATIONS: for BITS, ITERATIONS in BITS_AND_ITERATIONS:
print() print()
@@ -688,9 +577,7 @@ if __name__ == '__main__':
if BITS > 1_200 and test_proc in SKIP_LARGE: continue if BITS > 1_200 and test_proc in SKIP_LARGE: continue
if BITS > 4_096 and test_proc in SKIP_LARGEST: continue if BITS > 4_096 and test_proc in SKIP_LARGEST: continue
count_pass = 0 count = 0
count_fail = 0
TIMINGS = {}
UNTIL_ITERS = ITERATIONS UNTIL_ITERS = ITERATIONS
if test_proc == test_root_n and BITS == 1_200: if test_proc == test_root_n and BITS == 1_200:
@@ -747,30 +634,15 @@ if __name__ == '__main__':
else: else:
b = randint(0, 1 << BITS) b = randint(0, 1 << BITS)
res = None count += 1
test_proc(a, b)
start = time.perf_counter() print(fmt_string.format(name=test_proc.__name__, test_count=count))
res = test_proc(a, b)
diff = time.perf_counter() - start
TOTAL_TIME += diff
if test_proc not in TIMINGS:
TIMINGS[test_proc] = diff
else:
TIMINGS[test_proc] += diff
if res:
count_pass += 1; total_passes += 1
else:
count_fail += 1; total_failures += 1
print(fmt_string.format(name=test_proc.__name__, count_pass=count_pass, count_fail=count_fail, timing=TIMINGS[test_proc] * 1_000))
print() print()
print("---- THE END ----") print("---- THE END ----")
print()
print(fmt_string.format(name="total", count_pass=total_passes, count_fail=total_failures, timing=TOTAL_TIME * 1_000)) vectors.write("}")
if total_failures: if total_failures:
exit(1) exit(1)
-362
View File
@@ -1,362 +0,0 @@
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
An arbitrary precision mathematics implementation in Odin.
For the theoretical underpinnings, see Knuth's The Art of Computer Programming, Volume 2, section 4.3.
The code started out as an idiomatic source port of libTomMath, which is in the public domain, with thanks.
This file exports procedures for use with the test.py test suite.
*/
package test_core_math_big
/*
TODO: Write tests for `internal_*` and test reusing parameters with the public implementations.
*/
import "base:runtime"
import "core:strings"
import "core:math/big"
PyRes :: struct {
res: cstring,
err: big.Error,
}
print_to_buffer :: proc(val: ^big.Int) -> cstring {
context = runtime.default_context()
r, _ := big.int_itoa_cstring(val, 16, context.allocator)
return r
}
@export test_initialize_constants :: proc "c" () -> (res: u64) {
context = runtime.default_context()
_ = big.initialize_constants()
return u64(big._DIGIT_NAILS)
}
@export test_error_string :: proc "c" (err: big.Error) -> (res: cstring) {
context = runtime.default_context()
es := big.Error_String
return strings.clone_to_cstring(es[err], context.allocator)
}
@export test_add :: proc "c" (a, b: cstring) -> (res: PyRes) {
context = runtime.default_context()
err: big.Error
aa, bb, sum := &big.Int{}, &big.Int{}, &big.Int{}
defer big.internal_destroy(aa, bb, sum)
if err = big.atoi(aa, string(a), 16); err != nil { return PyRes{res=":add:atoi(a):", err=err} }
if err = big.atoi(bb, string(b), 16); err != nil { return PyRes{res=":add:atoi(b):", err=err} }
if bb.used == 1 {
if err = #force_inline big.internal_add(sum, aa, bb.digit[0]); err != nil { return PyRes{res=":add:add(sum,a,b):", err=err} }
} else {
if err = #force_inline big.internal_add(sum, aa, bb); err != nil { return PyRes{res=":add:add(sum,a,b):", err=err} }
}
r := print_to_buffer(sum)
return PyRes{res = r, err = nil}
}
@export test_sub :: proc "c" (a, b: cstring) -> (res: PyRes) {
context = runtime.default_context()
err: big.Error
aa, bb, sum := &big.Int{}, &big.Int{}, &big.Int{}
defer big.internal_destroy(aa, bb, sum)
if err = big.atoi(aa, string(a), 16); err != nil { return PyRes{res=":sub:atoi(a):", err=err} }
if err = big.atoi(bb, string(b), 16); err != nil { return PyRes{res=":sub:atoi(b):", err=err} }
if bb.used == 1 {
if err = #force_inline big.internal_sub(sum, aa, bb.digit[0]); err != nil { return PyRes{res=":sub:sub(sum,a,b):", err=err} }
} else {
if err = #force_inline big.internal_sub(sum, aa, bb); err != nil { return PyRes{res=":sub:sub(sum,a,b):", err=err} }
}
r := print_to_buffer(sum)
if err != nil { return PyRes{res=":sub:itoa(sum):", err=err} }
return PyRes{res = r, err = nil}
}
@export test_mul :: proc "c" (a, b: cstring) -> (res: PyRes) {
context = runtime.default_context()
err: big.Error
aa, bb, product := &big.Int{}, &big.Int{}, &big.Int{}
defer big.internal_destroy(aa, bb, product)
if err = big.atoi(aa, string(a), 16); err != nil { return PyRes{res=":mul:atoi(a):", err=err} }
if err = big.atoi(bb, string(b), 16); err != nil { return PyRes{res=":mul:atoi(b):", err=err} }
if err = #force_inline big.internal_mul(product, aa, bb); err != nil { return PyRes{res=":mul:mul(product,a,b):", err=err} }
r := print_to_buffer(product)
return PyRes{res = r, err = nil}
}
@export test_sqr :: proc "c" (a: cstring) -> (res: PyRes) {
context = runtime.default_context()
err: big.Error
aa, square := &big.Int{}, &big.Int{}
defer big.internal_destroy(aa, square)
if err = big.atoi(aa, string(a), 16); err != nil { return PyRes{res=":sqr:atoi(a):", err=err} }
if err = #force_inline big.internal_sqr(square, aa); err != nil { return PyRes{res=":sqr:sqr(square,a):", err=err} }
r := print_to_buffer(square)
return PyRes{res = r, err = nil}
}
/*
NOTE(Jeroen): For simplicity, we don't return the quotient and the remainder, just the quotient.
*/
@export test_div :: proc "c" (a, b: cstring) -> (res: PyRes) {
context = runtime.default_context()
err: big.Error
aa, bb, quotient := &big.Int{}, &big.Int{}, &big.Int{}
defer big.internal_destroy(aa, bb, quotient)
if err = big.atoi(aa, string(a), 16); err != nil { return PyRes{res=":div:atoi(a):", err=err} }
if err = big.atoi(bb, string(b), 16); err != nil { return PyRes{res=":div:atoi(b):", err=err} }
if err = #force_inline big.internal_div(quotient, aa, bb); err != nil { return PyRes{res=":div:div(quotient,a,b):", err=err} }
r := print_to_buffer(quotient)
return PyRes{res = r, err = nil}
}
/*
res = log(a, base)
*/
@export test_log :: proc "c" (a: cstring, base := big.DIGIT(2)) -> (res: PyRes) {
context = runtime.default_context()
err: big.Error
l: int
aa := &big.Int{}
defer big.internal_destroy(aa)
if err = big.atoi(aa, string(a), 16); err != nil { return PyRes{res=":log:atoi(a):", err=err} }
if l, err = #force_inline big.internal_log(aa, base); err != nil { return PyRes{res=":log:log(a, base):", err=err} }
#force_inline big.internal_zero(aa)
aa.digit[0] = big.DIGIT(l) & big._MASK
aa.digit[1] = big.DIGIT(l) >> big._DIGIT_BITS
aa.used = 2
big.clamp(aa)
r := print_to_buffer(aa)
return PyRes{res = r, err = nil}
}
/*
dest = base^power
*/
@export test_pow :: proc "c" (base: cstring, power := int(2)) -> (res: PyRes) {
context = runtime.default_context()
err: big.Error
dest, bb := &big.Int{}, &big.Int{}
defer big.internal_destroy(dest, bb)
if err = big.atoi(bb, string(base), 16); err != nil { return PyRes{res=":pow:atoi(base):", err=err} }
if err = #force_inline big.internal_pow(dest, bb, power); err != nil { return PyRes{res=":pow:pow(dest, base, power):", err=err} }
r := print_to_buffer(dest)
return PyRes{res = r, err = nil}
}
/*
dest = sqrt(src)
*/
@export test_sqrt :: proc "c" (source: cstring) -> (res: PyRes) {
context = runtime.default_context()
err: big.Error
src := &big.Int{}
defer big.internal_destroy(src)
if err = big.atoi(src, string(source), 16); err != nil { return PyRes{res=":sqrt:atoi(src):", err=err} }
if err = #force_inline big.internal_sqrt(src, src); err != nil { return PyRes{res=":sqrt:sqrt(src):", err=err} }
r := print_to_buffer(src)
return PyRes{res = r, err = nil}
}
/*
dest = root_n(src, power)
*/
@export test_root_n :: proc "c" (source: cstring, power: int) -> (res: PyRes) {
context = runtime.default_context()
err: big.Error
src := &big.Int{}
defer big.internal_destroy(src)
if err = big.atoi(src, string(source), 16); err != nil { return PyRes{res=":root_n:atoi(src):", err=err} }
if err = #force_inline big.internal_root_n(src, src, power); err != nil { return PyRes{res=":root_n:root_n(src):", err=err} }
r := print_to_buffer(src)
return PyRes{res = r, err = nil}
}
/*
dest = shr_digit(src, digits)
*/
@export test_shr_leg :: proc "c" (source: cstring, digits: int) -> (res: PyRes) {
context = runtime.default_context()
err: big.Error
src := &big.Int{}
defer big.internal_destroy(src)
if err = big.atoi(src, string(source), 16); err != nil { return PyRes{res=":shr_digit:atoi(src):", err=err} }
if err = #force_inline big._private_int_shr_leg(src, digits); err != nil { return PyRes{res=":shr_digit:shr_digit(src):", err=err} }
r := print_to_buffer(src)
return PyRes{res = r, err = nil}
}
/*
dest = shl_digit(src, digits)
*/
@export test_shl_leg :: proc "c" (source: cstring, digits: int) -> (res: PyRes) {
context = runtime.default_context()
err: big.Error
src := &big.Int{}
defer big.internal_destroy(src)
if err = big.atoi(src, string(source), 16); err != nil { return PyRes{res=":shl_digit:atoi(src):", err=err} }
if err = #force_inline big._private_int_shl_leg(src, digits); err != nil { return PyRes{res=":shl_digit:shr_digit(src):", err=err} }
r := print_to_buffer(src)
return PyRes{res = r, err = nil}
}
/*
dest = shr(src, bits)
*/
@export test_shr :: proc "c" (source: cstring, bits: int) -> (res: PyRes) {
context = runtime.default_context()
err: big.Error
src := &big.Int{}
defer big.internal_destroy(src)
if err = big.atoi(src, string(source), 16); err != nil { return PyRes{res=":shr:atoi(src):", err=err} }
if err = #force_inline big.internal_shr(src, src, bits); err != nil { return PyRes{res=":shr:shr(src, bits):", err=err} }
r := print_to_buffer(src)
return PyRes{res = r, err = nil}
}
/*
dest = shr_signed(src, bits)
*/
@export test_shr_signed :: proc "c" (source: cstring, bits: int) -> (res: PyRes) {
context = runtime.default_context()
err: big.Error
src := &big.Int{}
defer big.internal_destroy(src)
if err = big.atoi(src, string(source), 16); err != nil { return PyRes{res=":shr_signed:atoi(src):", err=err} }
if err = #force_inline big.internal_shr_signed(src, src, bits); err != nil { return PyRes{res=":shr_signed:shr_signed(src, bits):", err=err} }
r := print_to_buffer(src)
return PyRes{res = r, err = nil}
}
/*
dest = shl(src, bits)
*/
@export test_shl :: proc "c" (source: cstring, bits: int) -> (res: PyRes) {
context = runtime.default_context()
err: big.Error
src := &big.Int{}
defer big.internal_destroy(src)
if err = big.atoi(src, string(source), 16); err != nil { return PyRes{res=":shl:atoi(src):", err=err} }
if err = #force_inline big.internal_shl(src, src, bits); err != nil { return PyRes{res=":shl:shl(src, bits):", err=err} }
r := print_to_buffer(src)
return PyRes{res = r, err = nil}
}
/*
dest = factorial(n)
*/
@export test_factorial :: proc "c" (n: int) -> (res: PyRes) {
context = runtime.default_context()
err: big.Error
dest := &big.Int{}
defer big.internal_destroy(dest)
if err = #force_inline big.internal_int_factorial(dest, n); err != nil { return PyRes{res=":factorial:factorial(n):", err=err} }
r := print_to_buffer(dest)
return PyRes{res = r, err = nil}
}
/*
dest = gcd(a, b)
*/
@export test_gcd :: proc "c" (a, b: cstring) -> (res: PyRes) {
context = runtime.default_context()
err: big.Error
ai, bi, dest := &big.Int{}, &big.Int{}, &big.Int{}
defer big.internal_destroy(ai, bi, dest)
if err = big.atoi(ai, string(a), 16); err != nil { return PyRes{res=":gcd:atoi(a):", err=err} }
if err = big.atoi(bi, string(b), 16); err != nil { return PyRes{res=":gcd:atoi(b):", err=err} }
if err = #force_inline big.internal_int_gcd_lcm(dest, nil, ai, bi); err != nil { return PyRes{res=":gcd:gcd(a, b):", err=err} }
r := print_to_buffer(dest)
return PyRes{res = r, err = nil}
}
/*
dest = lcm(a, b)
*/
@export test_lcm :: proc "c" (a, b: cstring) -> (res: PyRes) {
context = runtime.default_context()
err: big.Error
ai, bi, dest := &big.Int{}, &big.Int{}, &big.Int{}
defer big.internal_destroy(ai, bi, dest)
if err = big.atoi(ai, string(a), 16); err != nil { return PyRes{res=":lcm:atoi(a):", err=err} }
if err = big.atoi(bi, string(b), 16); err != nil { return PyRes{res=":lcm:atoi(b):", err=err} }
if err = #force_inline big.internal_int_gcd_lcm(nil, dest, ai, bi); err != nil { return PyRes{res=":lcm:lcm(a, b):", err=err} }
r := print_to_buffer(dest)
return PyRes{res = r, err = nil}
}
/*
dest = lcm(a, b)
*/
@export test_is_square :: proc "c" (a: cstring) -> (res: PyRes) {
context = runtime.default_context()
err: big.Error
square: bool
ai := &big.Int{}
defer big.internal_destroy(ai)
if err = big.atoi(ai, string(a), 16); err != nil { return PyRes{res=":is_square:atoi(a):", err=err} }
if square, err = #force_inline big.internal_int_is_square(ai); err != nil { return PyRes{res=":is_square:is_square(a):", err=err} }
if square {
return PyRes{"True", nil}
}
return PyRes{"False", nil}
}
+184
View File
@@ -1,6 +1,7 @@
package test_core_math_big package test_core_math_big
import "core:math/big" import "core:math/big"
import "core:strconv"
import "core:testing" import "core:testing"
@(test) @(test)
@@ -83,3 +84,186 @@ test_rational_to_float :: proc(t: ^testing.T) {
} }
} }
} }
import "core:log"
@(test)
test_big_math_vectors :: proc(t: ^testing.T) {
for vec in big_test_vectors {
a, b, res, expected := &big.Int{}, &big.Int{}, &big.Int{}, &big.Int{}
defer big.destroy(a, b, res, expected)
atoi(t, a, vec.a) or_continue
atoi(t, b, vec.b) or_continue
atoi(t, expected, vec.exp) or_continue
#partial switch vec.op {
case .Add:
err := big.add(res, a, b)
testing.expect(t, err == vec.err)
expect_ab(t, "Expected add(%v, %v) to be %v, got %v", a, b, expected, res, err)
case .Sub:
err := big.sub(res, a, b)
testing.expect(t, err == vec.err)
expect_ab(t, "Expected sub(%v, %v) to be %v, got %v", a, b, expected, res, err)
case .Mul:
err := big.mul(res, a, b)
testing.expect(t, err == vec.err)
expect_ab(t, "Expected mul(%v, %v) to be %v, got %v", a, b, expected, res, err)
case .Div:
err := big.div(res, a, b)
testing.expect(t, err == vec.err)
expect_ab(t, "Expected div(%v, %v) to be %v, got %v", a, b, expected, res, err)
case .Sqr:
err := big.sqr(res, a)
testing.expect(t, err == vec.err)
expect_a(t, "Expected sqr(%v) to be %v, got %v", a, expected, res, err)
case .Log:
base, base_ok := strconv.parse_i64_of_base(vec.b, 16)
testing.expect(t, base_ok == true)
log_res, err := big.log(a, big.DIGIT(base))
testing.expect(t, err == vec.err)
big.set(res, log_res)
expect_ab(t, "Expected log(%v, %v) to be %v, got %v", a, b, expected, res, err)
case .Sqrt:
err := big.sqrt(res, a)
testing.expect(t, err == vec.err)
expect_a(t, "Expected sqrt(%v) to be %v, got %v", a, expected, res, err)
case .Pow:
power, power_ok := strconv.parse_i64_of_base(vec.b, 16)
testing.expect(t, power_ok == true)
err := big.pow(res, a, int(power))
testing.expect(t, err == vec.err)
expect_ab(t, "Expected pow(%v, %v) to be '%v', got %v", a, b, expected, res, err)
case .Root:
n, n_ok := strconv.parse_i64_of_base(vec.b, 16)
testing.expect(t, n_ok == true)
err := big.root_n(res, a, int(n))
testing.expect(t, err == vec.err)
expect_ab(t, "Expected root_n(%v, %v) to be '%v', got %v", a, b, expected, res, err)
case .Shl:
bits, bits_ok := strconv.parse_i64_of_base(vec.b, 16)
testing.expect(t, bits_ok == true)
err := big.internal_int_shl(res, a, int(bits))
testing.expect(t, err == vec.err)
expect_ab(t, "Expected internal_int_shl(%v, %v) to be '%v', got %v", a, b, expected, res, err)
case .Shr:
bits, bits_ok := strconv.parse_i64_of_base(vec.b, 16)
testing.expect(t, bits_ok == true)
err := big.internal_int_shr(res, a, int(bits))
testing.expect(t, err == vec.err)
expect_ab(t, "Expected internal_int_shr(%v, %v) to be '%v', got %v", a, b, expected, res, err)
case .Shr_Signed:
bits, bits_ok := strconv.parse_i64_of_base(vec.b, 16)
testing.expect(t, bits_ok == true)
big.set(res, a)
err := big.internal_int_shr_signed(res, res, int(bits))
testing.expect(t, err == vec.err)
expect_ab(t, "Expected internal_int_shr_signed(%v, %v) to be '%v', got %v", a, b, expected, res, err)
case .Factorial:
n, n_ok := strconv.parse_i64_of_base(vec.a, 16)
testing.expect(t, n_ok == true)
err := big.factorial(res, int(n))
testing.expect(t, err == vec.err)
expect_a(t, "Expected factorial(%v) to be '%v', got %v", a, expected, res, err)
case .Gcd:
err := big.internal_int_gcd_lcm(res, nil, a, b)
testing.expect(t, err == vec.err)
expect_ab(t, "Expected gcd(%v, %v) to be '%v', got %v", a, b, expected, res, err)
case .Lcm:
err := big.internal_int_gcd_lcm(nil, res, a, b)
testing.expect(t, err == vec.err)
expect_ab(t, "Expected lcm(%v, %v) to be '%v', got %v", a, b, expected, res, err)
case .Is_Square:
square, err := big.internal_int_is_square(a)
testing.expect(t, err == vec.err)
big.set(res, 1 if square else 0)
expect_a(t, "Expected is_square(%v) to be '%v', got %v", a, expected, res, err)
case:
log.assertf(false, "Unhandled op: %v", vec.op)
}
}
}
expect_a :: proc(t: ^testing.T, format: string, a, expected, res: ^big.Int, err: big.Error, loc := #caller_location) {
if err != .Okay { return }
equal, _ := big.equals(res, expected)
if !equal {
as, _ := big.itoa(a)
rs, _ := big.itoa(res)
es, _ := big.itoa(expected)
defer delete(as)
defer delete(rs)
defer delete(es)
testing.expectf(t, equal, format, as, es, rs, loc=loc)
assert(equal)
}
}
expect_ab :: proc(t: ^testing.T, format: string, a, b, expected, res: ^big.Int, err: big.Error, loc := #caller_location) {
if err != .Okay { return }
equal, _ := big.equals(res, expected)
if !equal {
as, _ := big.itoa(a)
bs, _ := big.itoa(b)
rs, _ := big.itoa(res)
es, _ := big.itoa(expected)
defer delete(as)
defer delete(bs)
defer delete(rs)
defer delete(es)
testing.expectf(t, equal, format, as, bs, es, rs, loc=loc)
assert(equal)
}
}
atoi :: proc(t: ^testing.T, i: ^big.Int, a: string, loc := #caller_location) -> bool {
err := big.atoi(i, a, 16)
testing.expect(t, err == .Okay, loc=loc)
return err == .Okay
}
File diff suppressed because one or more lines are too long
+1
View File
@@ -4,3 +4,4 @@ package tests_core
@(require) import "crypto" @(require) import "crypto"
@(require) import "hash" @(require) import "hash"
@(require) import "image" @(require) import "image"
@(require) import "math/big"