diff --git a/core/math/big/basic.odin b/core/math/big/basic.odin index 751e2aedf..b449ee543 100644 --- a/core/math/big/basic.odin +++ b/core/math/big/basic.odin @@ -1353,6 +1353,49 @@ int_gcd :: proc(res, a, b: ^Int) -> (err: Error) { gcd :: proc { int_gcd, }; +/* + Least Common Multiple. + Computes least common multiple as `|a*b|/(a, b)` + + TODO(Jeroen): + - Maybe combine with GCD and have an `_int_gcd_lcm` proc that can return both with work shared. +*/ +int_lcm :: proc(res, a, b: ^Int) -> (err: Error) { + if err = clear_if_uninitialized(a, b, res); err != .None { return err; } + + t1, t2 := &Int{}, &Int{}; + defer destroy(t1, t2); + + /* + t1 = get the GCD of the two inputs. + */ + if err = gcd(t1, a, b); err != .None { return err; } + + /* + Divide the smallest by the GCD. + */ + if c, _ := cmp_mag(a, b); c == -1 { + /* + Store quotient in `t2` such that `t2 * b` is the LCM. + */ + if err = div(t2, a, t1); err != .None { return err; } + err = mul(res, t2, b); + } else { + /* + Store quotient in `t2` such that `t2 * a` is the LCM. + */ + if err = div(t2, a, t1); err != .None { return err; } + err = mul(res, t2, b); + } + + /* + Fix the sign to positive and return. + */ + res.sign = .Zero_or_Positive; + return err; +} +lcm :: proc { int_lcm, }; + when size_of(rawptr) == 8 { _factorial_table := [35]_WORD{ /* f(00): */ 1, diff --git a/core/math/big/example.odin b/core/math/big/example.odin index 056cddb90..7e61aeea9 100644 --- a/core/math/big/example.odin +++ b/core/math/big/example.odin @@ -110,19 +110,20 @@ print :: proc(name: string, a: ^Int, base := i8(10), print_extra_info := false, demo :: proc() { - // err: Error; - // a, b, c, d, e, f := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}; - // defer destroy(a, b, c, d, e, f); + err: Error; + a, b, c, d, e, f := &Int{}, &Int{}, &Int{}, &Int{}, &Int{}, &Int{}; + defer destroy(a, b, c, d, e, f); - // set(a, 25); - // set(b, 15); + set(a, 25); + set(b, 15); + + err = gcd(c, a, b); + fmt.printf("gcd("); + print("a =", a, 10, false, true, false); + print(", b =", b, 10, false, true, false); + print(") =", c, 10, false, true, false); + fmt.printf(" (err = %v)\n", err); - // err = gcd(c, a, b); - // fmt.printf("gcd("); - // print("a =", a, 10, false, true, false); - // print(", b =", b, 10, false, true, false); - // print(") =", c, 10, false, true, false); - // fmt.printf(" (err = %v)\n", err); } main :: proc() { @@ -134,7 +135,6 @@ main :: proc() { demo(); print_timings(); - if len(ta.allocation_map) > 0 { for _, v in ta.allocation_map { fmt.printf("Leaked %v bytes @ %v\n", v.size, v.location); diff --git a/core/math/big/test.odin b/core/math/big/test.odin index 0b5cdd7e9..bbe045912 100644 --- a/core/math/big/test.odin +++ b/core/math/big/test.odin @@ -314,3 +314,23 @@ PyRes :: struct { return PyRes{res = r, err = .None}; } +/* + dest = lcm(a, b) +*/ +@export test_lcm :: proc "c" (a, b: cstring) -> (res: PyRes) { + context = runtime.default_context(); + err: Error; + + ai, bi, dest := &Int{}, &Int{}, &Int{}; + defer destroy(ai, bi, dest); + + if err = atoi(ai, string(a), 16); err != .None { return PyRes{res=":gcd:atoi(a):", err=err}; } + if err = atoi(bi, string(b), 16); err != .None { return PyRes{res=":gcd:atoi(b):", err=err}; } + if err = lcm(dest, ai, bi); err != .None { return PyRes{res=":lcm:lcm(a, b):", err=err}; } + + r: cstring; + r, err = int_itoa_cstring(dest, 16, context.temp_allocator); + if err != .None { return PyRes{res=":lcm:itoa(res):", err=err}; } + return PyRes{res = r, err = .None}; +} + diff --git a/core/math/big/test.py b/core/math/big/test.py index efdbab607..d1e40feac 100644 --- a/core/math/big/test.py +++ b/core/math/big/test.py @@ -136,7 +136,7 @@ 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) def test(test_name: "", res: Res, param=[], expected_error = Error.Okay, expected_result = "", radix=16): passed = True @@ -343,6 +343,14 @@ def test_gcd(a = 0, b = 0, expected_error = Error.Okay): return test("test_gcd", res, [a, b], expected_error, expected_result) +def test_lcm(a = 0, b = 0, expected_error = Error.Okay): + args = [arg_to_odin(a), arg_to_odin(b)] + res = int_lcm(*args) + expected_result = None + if expected_error == Error.Okay: + expected_result = math.lcm(a, b) + + return test("test_lcm", res, [a, b], expected_error, expected_result) # TODO(Jeroen): Make sure tests cover edge cases, fast paths, and so on. # @@ -425,6 +433,10 @@ TESTS = { [ 123, 25, ], [ 125, 25, ], ], + test_lcm: [ + [ 123, 25, ], + [ 125, 25, ], + ], } total_passes = 0 @@ -437,7 +449,7 @@ RANDOM_TESTS = [ test_add, test_sub, test_mul, test_div, test_log, test_pow, test_sqrt, test_root_n, test_shl_digit, test_shr_digit, test_shl, test_shr_signed, - test_gcd, + test_gcd, test_lcm, ] SKIP_LARGE = [ test_pow, test_root_n, # test_gcd,