From 1e934def566832c2722c63d5b48df631996c6e88 Mon Sep 17 00:00:00 2001 From: HarmoGlace <23212967+HarmoGlace@users.noreply.github.com> Date: Wed, 19 Apr 2023 16:18:31 +0200 Subject: [PATCH] Add `gcd` and `lcm` calculation methods (#789) --- library/src/compute/calc.rs | 68 +++++++++++++++++++++++++++++++++++++ tests/typ/compute/calc.typ | 24 +++++++++++++ 2 files changed, 92 insertions(+) diff --git a/library/src/compute/calc.rs b/library/src/compute/calc.rs index 9ab18dbfc..62c529deb 100644 --- a/library/src/compute/calc.rs +++ b/library/src/compute/calc.rs @@ -27,6 +27,8 @@ pub fn module() -> Module { scope.define("fact", fact); scope.define("perm", perm); scope.define("binom", binom); + scope.define("gcd", gcd); + scope.define("lcm", lcm); scope.define("floor", floor); scope.define("ceil", ceil); scope.define("round", round); @@ -522,6 +524,72 @@ fn binomial(n: u64, k: u64) -> Option { i64::try_from(result).ok() } +/// Calculate the greatest common divisor of two integers. +/// +/// ## Example +/// ```example +/// #calc.gcd(7, 42) +/// ``` +/// +/// Display: Greatest Common Divisor +/// Category: calculate +/// Returns: integer +#[func] +pub fn gcd( + /// The first integer. + a: i64, + /// The second integer. + b: i64, +) -> Value { + Value::Int(calculate_gcd(a, b).into()) +} + +/// Calculates the greatest common divisor of two integers +/// It is always non-negative. +fn calculate_gcd(mut a: i64, mut b: i64) -> i64 { + while b != 0 { + let temp = b; + b = a % b; + a = temp; + } + + a.abs() +} + +/// Calculate the least common multiple of two integers. +/// +/// ## Example +/// ```example +/// #calc.lcm(96, 13) +/// ``` +/// +/// Display: Least Common Multiple +/// Category: calculate +/// Returns: integer +#[func] +pub fn lcm( + /// The first integer. + a: i64, + /// The second integer. + b: i64, +) -> Value { + calculate_lcm(a, b) + .map(Value::Int) + .ok_or("the return value is too large") + .at(args.span)? +} + +/// Calculates the least common multiple between two non-zero integers +/// Returns None if the value cannot be computed. +/// It is always non-negative. +fn calculate_lcm(a: i64, b: i64) -> Option { + if a == b { + return Some(a.abs()); + } + + a.checked_div(calculate_gcd(a, b))?.checked_mul(b).map(|v| v.abs()) +} + /// Round a number down to the nearest integer. /// /// If the number is already an integer, it is returned unchanged. diff --git a/tests/typ/compute/calc.typ b/tests/typ/compute/calc.typ index c9e235426..6af5189b4 100644 --- a/tests/typ/compute/calc.typ +++ b/tests/typ/compute/calc.typ @@ -146,6 +146,30 @@ #test(calc.binom(5, 6), 0) #test(calc.binom(6, 2), 15) +--- +// Test the `gcd` function. +#test(calc.gcd(112, 77), 7) +#test(calc.gcd(12, 96), 12) +#test(calc.gcd(13, 9), 1) +#test(calc.gcd(13, -9), 1) +#test(calc.gcd(272557, 272557), 272557) +#test(calc.gcd(0, 0), 0) +#test(calc.gcd(7, 0), 7) + +--- +// Test the `lcm` function. +#test(calc.lcm(112, 77), 1232) +#test(calc.lcm(12, 96), 96) +#test(calc.lcm(13, 9), 117) +#test(calc.lcm(13, -9), 117) +#test(calc.lcm(272557, 272557), 272557) +#test(calc.lcm(0, 0), 0) +#test(calc.lcm(8, 0), 0) + +--- +// Error: 10-41 the return value is too large +#calc.lcm(15486487489457, 4874879896543) + --- // Error: 10-12 expected at least one value #calc.min()