use crate::types::Value;
use crate::{LimboError, Result};
fn gcd_inner(mut a: i64, mut b: i64) -> Option<i64> {
if a == i64::MIN || b == i64::MIN {
if a == 0 || b == 0 || a == b {
return None;
}
if a == i64::MIN {
a %= b;
} else {
b %= a;
}
}
while b != 0 {
let t = b;
b = a % b;
a = t;
}
Some(a.abs())
}
pub fn exec_gcd(a: &Value, b: &Value) -> Result<Value> {
let (Some(a), Some(b)) = (value_as_i64(a), value_as_i64(b)) else {
return Ok(Value::Null);
};
match gcd_inner(a, b) {
Some(g) => Ok(Value::from_i64(g)),
None => Err(LimboError::IntegerOverflow),
}
}
pub fn exec_lcm(a: &Value, b: &Value) -> Result<Value> {
let (Some(a), Some(b)) = (value_as_i64(a), value_as_i64(b)) else {
return Ok(Value::Null);
};
if a == 0 || b == 0 {
return Ok(Value::from_i64(0));
}
let g = gcd_inner(a, b).ok_or(LimboError::IntegerOverflow)?;
let lcm = (a / g)
.checked_mul(b.checked_abs().ok_or(LimboError::IntegerOverflow)?)
.and_then(i64::checked_abs)
.ok_or(LimboError::IntegerOverflow)?;
Ok(Value::from_i64(lcm))
}
fn value_as_i64(v: &Value) -> Option<i64> {
match v {
Value::Null => None,
Value::Numeric(crate::Numeric::Integer(i)) => Some(*i),
Value::Numeric(crate::Numeric::Float(f)) => {
let f: f64 = (*f).into();
if f.is_finite() {
Some(f as i64)
} else {
None
}
}
Value::Text(t) => t.as_str().parse::<i64>().ok(),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Value;
fn v(i: i64) -> Value {
Value::from_i64(i)
}
#[test]
fn gcd_basic() {
assert_eq!(exec_gcd(&v(12), &v(8)).unwrap(), v(4));
assert_eq!(exec_gcd(&v(0), &v(7)).unwrap(), v(7));
assert_eq!(exec_gcd(&v(7), &v(0)).unwrap(), v(7));
assert_eq!(exec_gcd(&v(0), &v(0)).unwrap(), v(0));
assert_eq!(exec_gcd(&v(-12), &v(8)).unwrap(), v(4));
assert_eq!(exec_gcd(&v(-12), &v(-8)).unwrap(), v(4));
}
#[test]
fn gcd_null_propagates() {
assert!(matches!(
exec_gcd(&Value::Null, &v(7)).unwrap(),
Value::Null
));
assert!(matches!(
exec_gcd(&v(7), &Value::Null).unwrap(),
Value::Null
));
}
#[test]
fn gcd_overflow() {
assert!(matches!(
exec_gcd(&v(i64::MIN), &v(0)),
Err(LimboError::IntegerOverflow)
));
assert!(matches!(
exec_gcd(&v(i64::MIN), &v(i64::MIN)),
Err(LimboError::IntegerOverflow)
));
assert_eq!(exec_gcd(&v(i64::MIN), &v(2)).unwrap(), v(2));
}
#[test]
fn lcm_basic() {
assert_eq!(exec_lcm(&v(4), &v(6)).unwrap(), v(12));
assert_eq!(exec_lcm(&v(0), &v(5)).unwrap(), v(0));
assert_eq!(exec_lcm(&v(5), &v(0)).unwrap(), v(0));
assert_eq!(exec_lcm(&v(-4), &v(6)).unwrap(), v(12));
assert_eq!(exec_lcm(&v(-4), &v(-6)).unwrap(), v(12));
}
#[test]
fn lcm_null_propagates() {
assert!(matches!(
exec_lcm(&Value::Null, &v(7)).unwrap(),
Value::Null
));
assert!(matches!(
exec_lcm(&v(7), &Value::Null).unwrap(),
Value::Null
));
}
#[test]
fn lcm_overflow() {
assert!(matches!(
exec_lcm(&v(i64::MAX), &v(3)),
Err(LimboError::IntegerOverflow)
));
}
}