diff --git a/core/math/big/basic.odin b/core/math/big/basic.odin index c2f983bc4..c501dd72e 100644 --- a/core/math/big/basic.odin +++ b/core/math/big/basic.odin @@ -1266,31 +1266,110 @@ _int_div_digit :: proc(quotient, numerator: ^Int, denominator: DIGIT) -> (remain return remainder, .None; } +/* + Function computing both GCD and (if target isn't `nil`) also LCM. +*/ +int_gcd_lcm :: proc(res_gcd, res_lcm, a, b: ^Int) -> (err: Error) { + if err = clear_if_uninitialized(res_gcd, res_lcm, a, b); err != .None { return err; } + return #force_inline _int_gcd_lcm(res_gcd, res_lcm, a, b); +} +gcd_lcm :: proc { int_gcd_lcm, }; /* Greatest Common Divisor using the binary method. - - TODO(Jeroen): - - Maybe combine with LCM and have an `_int_gcd_lcm` proc that can return both with work shared. */ int_gcd :: proc(res, a, b: ^Int) -> (err: Error) { - if err = clear_if_uninitialized(a, b, res); err != .None { return err; } + if err = clear_if_uninitialized(res, a, b); err != .None { return err; } /* If both `a` and `b` are zero, return zero. If either `a` or `b`, return the other one. */ - az, _ := is_zero(a); - bz, _ := is_zero(b); + az, _ := is_zero(a); bz, _ := is_zero(b); + if az && bz { return zero(res); } + else if az { return abs(res, b); } + else if bz { return abs(res, a); } + + return #force_inline _int_gcd_lcm(res, nil, a, b); +} +gcd :: proc { int_gcd, }; + +/* + Least Common Multiple. +*/ +int_lcm :: proc(res, a, b: ^Int) -> (err: Error) { + if err = clear_if_uninitialized(res, a, b); err != .None { return err; } + + /* + If both `a` and `b` are zero, return zero. + */ + az, _ := is_zero(a); bz, _ := is_zero(b); + if az || bz { return zero(res); } + + return #force_inline _int_gcd_lcm(nil, res, a, b); +} +lcm :: proc { int_lcm, }; + +/* + Internal function computing both GCD and (if target isn't `nil`) also LCM. + Expects the arguments to have been initialized. +*/ +_int_gcd_lcm :: proc(res_gcd, res_lcm, a, b: ^Int) -> (err: Error) { + /* + If both `a` and `b` are zero, return zero. + If either `a` or `b`, return the other one. + + The `gcd` and `lcm` wrappers have already done this test, + but `gcd_lcm` wouldn't have, so we still need to perform it. + + If neither result is wanted, we have nothing to do. + */ + if res_gcd == nil && res_lcm == nil { return .None; } + + /* + We need a temporary because `res_gcd` is allowed to be `nil`. + */ + az, _ := is_zero(a); bz, _ := is_zero(b); if az && bz { - return zero(res); + /* + GCD(0, 0) and LCM(0, 0) are both 0. + */ + if res_gcd != nil { + if err = zero(res_gcd); err != .None { return err; } + } + if res_lcm != nil { + if err = zero(res_lcm); err != .None { return err; } + } + return .None; } else if az { - return abs(res, b); + /* + We can early out with GCD = B and LCM = 0 + */ + if res_gcd != nil { + if err = abs(res_gcd, b); err != .None { return err; } + } + if res_lcm != nil { + if err = zero(res_lcm); err != .None { return err; } + } + return .None; } else if bz { - return abs(res, a); + /* + We can early out with GCD = A and LCM = 0 + */ + if res_gcd != nil { + if err = abs(res_gcd, a); err != .None { return err; } + } + if res_lcm != nil { + if err = zero(res_lcm); err != .None { return err; } + } + return .None; } - /* + temp_gcd_res := &Int{}; + defer destroy(temp_gcd_res); + + /* + If neither `a` or `b` was zero, we need to compute `gcd`. Get copies of `a` and `b` we can modify. */ u, v := &Int{}, &Int{}; @@ -1354,62 +1433,47 @@ int_gcd :: proc(res, a, b: ^Int) -> (err: Error) { /* Multiply by 2**k which we divided out at the beginning. */ - if err = shl(res, u, k); err != .None { return err; } - res.sign = .Zero_or_Positive; - return err; -} -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); + if err = shl(temp_gcd_res, u, k); err != .None { return err; } + temp_gcd_res.sign = .Zero_or_Positive; /* - Special case: lcm(0, 0) is defined as zero. + We've computed `gcd`, either the long way, or because one of the inputs was zero. + If we don't want `lcm`, we're done. */ - az, _ := is_zero(a); - bz, _ := is_zero(b); - if az && bz { return zero(res); } - - /* - t1 = get the GCD of the two inputs. - */ - if err = gcd(t1, a, b); err != .None { return err; } + if res_lcm == nil { + swap(temp_gcd_res, res_gcd); + return .None; + } /* + Computes least common multiple as `|a*b|/gcd(a,b)` 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); + if err = div(res_lcm, a, temp_gcd_res); err != .None { return err; } + err = mul(res_lcm, res_lcm, 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); + if err = div(res_lcm, a, temp_gcd_res); err != .None { return err; } + err = mul(res_lcm, res_lcm, b); + } + + if res_gcd != nil { + swap(temp_gcd_res, res_gcd); } /* Fix the sign to positive and return. */ - res.sign = .Zero_or_Positive; + res_lcm.sign = .Zero_or_Positive; return err; } -lcm :: proc { int_lcm, }; + when size_of(rawptr) == 8 { _factorial_table := [35]_WORD{ diff --git a/core/math/big/example.odin b/core/math/big/example.odin index 7e61aeea9..ba7190c59 100644 --- a/core/math/big/example.odin +++ b/core/math/big/example.odin @@ -114,8 +114,16 @@ demo :: proc() { 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, 125); + set(b, 0); + + err = gcd_lcm(c, d, a, b); + fmt.printf("gcd_lcm("); + print("a =", a, 10, false, true, false); + print(", b =", b, 10, false, true, false); + print("), gcd =", c, 10, false, true, false); + print(", lcm =", d, 10, false, true, false); + fmt.printf(" (err = %v)\n", err); err = gcd(c, a, b); fmt.printf("gcd("); @@ -124,6 +132,14 @@ demo :: proc() { print(") =", c, 10, false, true, false); fmt.printf(" (err = %v)\n", err); + err = lcm(c, a, b); + fmt.printf("lcm("); + 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() { diff --git a/core/math/big/test.py b/core/math/big/test.py index acb6df197..b8e91c003 100644 --- a/core/math/big/test.py +++ b/core/math/big/test.py @@ -430,16 +430,18 @@ TESTS = { [ 12_345 ], ], test_gcd: [ - [ 123, 25, ], + [ 23, 25, ], [ 125, 25, ], [ 125, 0, ], [ 0, 0, ], + [ 0, 125,], ], test_lcm: [ - [ 123, 25, ], + [ 23, 25,], [ 125, 25, ], [ 125, 0, ], [ 0, 0, ], + [ 0, 125,], ], }