math: add gcd and lcm

Add gcd and lcm functions to calculate the greatest common divisor (gcd)
and the least common multiple (lcm) to the math module. This will also
work for BigInts that implements its own gcd/lcm.
This commit is contained in:
Koni Marti
2024-12-08 19:30:46 +01:00
committed by Christoffer Lerno
parent 061c02306f
commit 62dca4f1c5
4 changed files with 110 additions and 4 deletions

View File

@@ -328,7 +328,7 @@ fn BigInt BigInt.unary_minus(&self)
}
macro void BigInt.div(self, BigInt other)
macro BigInt BigInt.div(self, BigInt other)
{
self.div_this(other);
return self;
@@ -831,6 +831,14 @@ fn BigInt BigInt.gcd(&self, BigInt other)
return g;
}
fn BigInt BigInt.lcm(&self, BigInt other)
{
BigInt x = self.abs();
BigInt y = other.abs();
BigInt g = y.mult(x);
return g.div(x.gcd(y));
}
<*
@require bits >> 5 < MAX_LEN "Required bits > maxlength"
*>

View File

@@ -1183,4 +1183,62 @@ macro long[<*>] long[<*>].muldiv(self, mul, div) => mul_div_helper(self, mul, di
@require @is_same_vector_or_scalar(self, mul) `mul must be a vector of the same type as self, or be an integer scalar`
@require @is_same_vector_or_scalar(self, div) `div must be a vector of the same type as self, or be an integer scalar`
*>
macro ulong[<*>] ulong[<*>].muldiv(self, mul, div) => mul_div_helper(self, mul, div);
macro ulong[<*>] ulong[<*>].muldiv(self, mul, div) => mul_div_helper(self, mul, div);
<*
@require types::is_int($typeof(a)) `The input must be an integer`
@require types::is_int($typeof(b)) `The input must be an integer`
*>
macro _gcd(a, b) @private
{
if (a == 0) return b;
if (b == 0) return a;
var $Type = $typeof(a);
$Type r, aa, ab;
aa = abs(a);
ab = abs(b);
while (ab != 0)
{
r = aa % ab;
aa = ab;
ab = r;
}
return aa;
}
<*
Calculate the least common multiple for the provided arguments.
@require $vacount >= 2 `At least two arguments are required.`
*>
macro lcm(...)
{
$typeof($vaarg[0]) result = $vaarg[0];
$for (var $i = 1; $i < $vacount; $i++)
$if $defined(result.lcm):
result = result.lcm($vaarg[$i]);
$else
result = (abs($vaarg[$i]) * abs(result)) / (_gcd($vaarg[$i], result));
$endif
$endfor
return result;
}
<*
Calculate the greatest common divisor for the provided arguments.
@require $vacount >= 2 `At least two arguments are required.`
*>
macro gcd(...)
{
$typeof($vaarg[0]) result = $vaarg[0];
$for (var $i = 1; $i < $vacount; $i++)
$if $defined(result.gcd):
result = result.gcd($vaarg[$i]);
$else
result = _gcd($vaarg[$i], result);
$endif
$endfor
return result;
}

View File

@@ -42,4 +42,20 @@ fn void test_init_string_radix()
assert(a.equals(bigint::from_int(0o123)));
a.init_string_radix("123", 16)!!;
assert(a.equals(bigint::from_int(0x123)));
}
}
fn void test_gcd()
{
BigInt a = bigint::from_int(15);
BigInt b = bigint::from_int(20);
assert(a.gcd(b).equals(bigint::from_int(5)));
assert(math::gcd(a,b).equals(bigint::from_int(5)));
}
fn void test_lcm()
{
BigInt a = bigint::from_int(11);
BigInt b = bigint::from_int(17);
assert(a.lcm(b).equals(bigint::from_int(11*17)));
assert(math::lcm(a,b).equals(bigint::from_int(11*17)));
}

View File

@@ -194,4 +194,28 @@ fn void! test_muldiv()
ichar[<4>] k = {20, 30, 40, 50};
assert(k.muldiv(20,-10) == ichar[<4>]{-40,-60,-80,-100});
}
}
fn void! test_gcd() @test
{
assert(math::gcd(20,15) == 5);
assert(math::gcd(15,20) == 5);
assert(math::gcd(-15,20) == 5);
assert(math::gcd(15,-20) == 5);
assert(math::gcd(-15,-20) == 5);
assert(math::gcd(5,15,20) == 5);
assert(math::gcd(1,2,3) == 1);
assert(math::gcd(2,4,6,8) == 2);
}
fn void! test_lcm() @test
{
assert(math::lcm(4,5) == 20);
assert(math::lcm(6,10) == 30);
assert(math::lcm(-8,20) == 40);
assert(math::lcm(8,-20) == 40);
assert(math::lcm(-8,-20) == 40);
assert(math::lcm(11,17) == 11*17);
assert(math::lcm(11,17,227,263) == 11*17*227*263);
}