Browse Source

big: Add `lcm` and its test.

Jeroen van Rijn 4 years ago
parent
commit
8b1d8c8453
4 changed files with 91 additions and 16 deletions
  1. 43 0
      core/math/big/basic.odin
  2. 14 14
      core/math/big/example.odin
  3. 20 0
      core/math/big/test.odin
  4. 14 2
      core/math/big/test.py

+ 43 - 0
core/math/big/basic.odin

@@ -1353,6 +1353,49 @@ int_gcd :: proc(res, a, b: ^Int) -> (err: Error) {
 gcd :: proc { int_gcd, };
 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 {
 when size_of(rawptr) == 8 {
 	_factorial_table := [35]_WORD{
 	_factorial_table := [35]_WORD{
 /* f(00): */                                                   1,
 /* f(00): */                                                   1,

+ 14 - 14
core/math/big/example.odin

@@ -110,19 +110,20 @@ print :: proc(name: string, a: ^Int, base := i8(10), print_extra_info := false,
 
 
 demo :: proc() {
 demo :: proc() {
 
 
-	// 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);
-
-	// 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: 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);
+
+	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() {
 main :: proc() {
@@ -134,7 +135,6 @@ main :: proc() {
 	demo();
 	demo();
 	print_timings();
 	print_timings();
 
 
-
 	if len(ta.allocation_map) > 0 {
 	if len(ta.allocation_map) > 0 {
 		for _, v in ta.allocation_map {
 		for _, v in ta.allocation_map {
 			fmt.printf("Leaked %v bytes @ %v\n", v.size, v.location);
 			fmt.printf("Leaked %v bytes @ %v\n", v.size, v.location);

+ 20 - 0
core/math/big/test.odin

@@ -314,3 +314,23 @@ PyRes :: struct {
 	return PyRes{res = r, err = .None};
 	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};
+}
+

+ 14 - 2
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_factorial  = load(l.test_factorial, [c_uint64], Res)
 int_gcd        = load(l.test_gcd, [c_char_p, c_char_p], 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):
 def test(test_name: "", res: Res, param=[], expected_error = Error.Okay, expected_result = "", radix=16):
 	passed = True
 	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)
 	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.
 # TODO(Jeroen): Make sure tests cover edge cases, fast paths, and so on.
 #
 #
@@ -425,6 +433,10 @@ TESTS = {
 		[ 123, 25, ],
 		[ 123, 25, ],
 		[ 125, 25, ],
 		[ 125, 25, ],
 	],
 	],
+	test_lcm: [
+		[ 123, 25, ],
+		[ 125, 25, ],
+	],
 }
 }
 
 
 total_passes   = 0
 total_passes   = 0
@@ -437,7 +449,7 @@ RANDOM_TESTS = [
 	test_add, test_sub, test_mul, test_div,
 	test_add, test_sub, test_mul, test_div,
 	test_log, test_pow, test_sqrt, test_root_n,
 	test_log, test_pow, test_sqrt, test_root_n,
 	test_shl_digit, test_shr_digit, test_shl, test_shr_signed,
 	test_shl_digit, test_shr_digit, test_shl, test_shr_signed,
-	test_gcd,
+	test_gcd, test_lcm,
 ]
 ]
 SKIP_LARGE   = [
 SKIP_LARGE   = [
 	test_pow, test_root_n, # test_gcd,
 	test_pow, test_root_n, # test_gcd,