use super::div::divrem;
use super::mod_arith::mod_mul;
use super::uint::BigUint;
use oxinum_core::{OxiNumError, OxiNumResult};
pub fn lucas_uv(n: &BigUint, p: i64, q: i64, m: &BigUint) -> OxiNumResult<(BigUint, BigUint)> {
if m.is_zero() {
return Err(OxiNumError::DivByZero);
}
if !is_odd(m) {
return Err(OxiNumError::Domain("lucas_uv: modulus must be odd".into()));
}
lucas_uv_mod(n, p, q, m)
}
fn lucas_uv_mod(n: &BigUint, p: i64, q: i64, m: &BigUint) -> OxiNumResult<(BigUint, BigUint)> {
if n.is_zero() {
return Ok((BigUint::zero(), mod_reduce_u64(2, m)));
}
let bits = n.bit_length();
if bits == 1 && n.test_bit(0) {
return Ok((BigUint::one(), mod_reduce_signed(p, m)));
}
let d_val: i128 = (p as i128) * (p as i128) - 4 * (q as i128);
let mut u = BigUint::one(); let mut v = mod_reduce_signed(p, m); let mut qk = mod_reduce_signed(q, m);
for i in (0..bits - 1).rev() {
let new_u = mod_mul(&u, &v, m)?;
let v_sq = mod_mul(&v, &v, m)?;
let two_qk = mod_mul(&BigUint::from(2u64), &qk, m)?;
let new_v = modsub(&v_sq, &two_qk, m);
let new_qk = mod_mul(&qk, &qk, m)?;
u = new_u;
v = new_v;
qk = new_qk;
if n.test_bit(i) {
let p_mod = mod_reduce_signed(p, m);
let d_mod = mod_reduce_i128(d_val, m);
let pu = mod_mul(&p_mod, &u, m)?;
let raw_u = mod_add(&pu, &v, m);
let new_u2 = moddiv2(raw_u, m);
let du = mod_mul(&d_mod, &u, m)?;
let pv = mod_mul(&p_mod, &v, m)?;
let raw_v = mod_add(&du, &pv, m);
let new_v2 = moddiv2(raw_v, m);
let q_mod = mod_reduce_signed(q, m);
let new_qk2 = mod_mul(&qk, &q_mod, m)?;
u = new_u2;
v = new_v2;
qk = new_qk2;
}
}
Ok((u, v))
}
fn mod_reduce_signed(x: i64, m: &BigUint) -> BigUint {
if x >= 0 {
let bx = BigUint::from(x as u64);
let (_, rem) = divrem(&bx, m);
rem
} else {
let bx = BigUint::from(x.unsigned_abs());
let (_, rem) = divrem(&bx, m);
if rem.is_zero() {
BigUint::zero()
} else {
m.checked_sub(&rem).unwrap_or_else(BigUint::zero)
}
}
}
fn mod_reduce_i128(x: i128, m: &BigUint) -> BigUint {
if x >= 0 {
let bx = BigUint::from(x as u128);
let (_, rem) = divrem(&bx, m);
rem
} else {
let bx = BigUint::from(x.unsigned_abs());
let (_, rem) = divrem(&bx, m);
if rem.is_zero() {
BigUint::zero()
} else {
m.checked_sub(&rem).unwrap_or_else(BigUint::zero)
}
}
}
fn mod_reduce_u64(x: u64, m: &BigUint) -> BigUint {
let bx = BigUint::from(x);
let (_, rem) = divrem(&bx, m);
rem
}
fn modsub(a: &BigUint, b: &BigUint, m: &BigUint) -> BigUint {
if a >= b {
let r = a.checked_sub(b).unwrap_or_else(BigUint::zero);
let (_, rem) = divrem(&r, m);
rem
} else {
let diff = b.checked_sub(a).unwrap_or_else(BigUint::zero);
if diff.is_zero() {
return BigUint::zero();
}
m.checked_sub(&diff).unwrap_or_else(BigUint::zero)
}
}
fn mod_add(a: &BigUint, b: &BigUint, m: &BigUint) -> BigUint {
let sum = BigUint::add_ref(a, b);
if &sum >= m {
sum.checked_sub(m).unwrap_or_else(BigUint::zero)
} else {
sum
}
}
fn moddiv2(a: BigUint, m: &BigUint) -> BigUint {
let a_mod = {
let (_, r) = divrem(&a, m);
r
};
if !is_odd(&a_mod) {
a_mod.shr_bits(1)
} else {
let sum = BigUint::add_ref(&a_mod, m);
sum.shr_bits(1)
}
}
#[inline]
pub(crate) fn is_odd(n: &BigUint) -> bool {
n.as_limbs().first().copied().unwrap_or(0) & 1 == 1
}
#[cfg(test)]
mod tests {
use super::*;
fn bu(n: u64) -> BigUint {
BigUint::from(n)
}
#[test]
fn lucas_base_cases() {
let m = bu(1_000_000_007);
let (u, v) = lucas_uv(&bu(0), 1, -1, &m).expect("lucas 0");
assert_eq!(u, bu(0));
assert_eq!(v, bu(2));
let (u, v) = lucas_uv(&bu(1), 1, -1, &m).expect("lucas 1");
assert_eq!(u, bu(1));
assert_eq!(v, bu(1));
}
#[test]
fn lucas_fibonacci_u_sequence() {
let expected_u = [0u64, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55];
let m = bu(1_000_000_007);
for (n, &eu) in expected_u.iter().enumerate() {
let (u, _v) = lucas_uv(&bu(n as u64), 1, -1, &m).expect("lucas Fib");
assert_eq!(u, bu(eu), "U_{} should be Fib_{}", n, n);
}
}
#[test]
fn lucas_fibonacci_v_sequence() {
let expected_v = [2u64, 1, 3, 4, 7, 11, 18, 29, 47, 76];
let m = bu(1_000_000_007);
for (n, &ev) in expected_v.iter().enumerate() {
let (_u, v) = lucas_uv(&bu(n as u64), 1, -1, &m).expect("lucas V");
assert_eq!(v, bu(ev), "V_{} should be Lucas_{}", n, n);
}
}
#[test]
fn lucas_modular_reduction() {
let m = bu(101);
let (u, _) = lucas_uv(&bu(12), 1, -1, &m).expect("lucas mod");
assert_eq!(u, bu(144 % 101)); }
#[test]
fn lucas_rejects_even_modulus() {
let m = bu(100);
assert!(lucas_uv(&bu(5), 1, -1, &m).is_err());
}
#[test]
fn lucas_rejects_zero_modulus() {
assert!(lucas_uv(&bu(5), 1, -1, &BigUint::zero()).is_err());
}
}