use super::div::divrem;
use super::uint::BigUint;
use oxinum_core::{OxiNumError, OxiNumResult};
pub fn mod_mul(a: &BigUint, b: &BigUint, m: &BigUint) -> OxiNumResult<BigUint> {
if m.is_zero() {
return Err(OxiNumError::DivByZero);
}
let product = a.clone() * b.clone();
let (_q, rem) = divrem(&product, m);
Ok(rem)
}
pub fn mod_pow(base: &BigUint, exp: &BigUint, modulus: &BigUint) -> OxiNumResult<BigUint> {
if modulus.is_zero() {
return Err(OxiNumError::DivByZero);
}
if modulus.is_one() {
return Ok(BigUint::zero());
}
if exp.is_zero() {
return Ok(BigUint::one());
}
let base_reduced = if base >= modulus {
let (_q, r) = divrem(base, modulus);
r
} else {
base.clone()
};
if base_reduced.is_zero() {
return Ok(BigUint::zero());
}
let mut result = BigUint::one();
let mut current = base_reduced;
let bits = exp.bit_length();
for i in 0..bits {
if exp.test_bit(i) {
let (_q, r) = divrem(&(result.clone() * current.clone()), modulus);
result = r;
}
if i + 1 < bits {
let (_q, r) = divrem(&(current.clone() * current.clone()), modulus);
current = r;
}
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
fn bu(n: u64) -> BigUint {
BigUint::from_u64(n)
}
#[test]
fn mod_mul_basic() {
assert_eq!(mod_mul(&bu(7), &bu(8), &bu(5)).expect(""), bu(1));
}
#[test]
fn mod_mul_identity() {
for (a, m) in [(13u64, 7u64), (100, 13), (65536, 65537)] {
let expected = a % m;
assert_eq!(mod_mul(&bu(a), &bu(1), &bu(m)).expect(""), bu(expected));
}
}
#[test]
fn mod_mul_zero_modulus() {
assert!(mod_mul(&bu(3), &bu(4), &bu(0)).is_err());
}
#[test]
fn mod_mul_result_in_range() {
let m = bu(17);
for a in 0u64..17 {
for b in 0u64..17 {
let r = mod_mul(&bu(a), &bu(b), &m).expect("");
assert!(r < m, "result {r:?} >= m=17 for a={a}, b={b}");
}
}
}
#[test]
fn mod_pow_basic() {
assert_eq!(mod_pow(&bu(2), &bu(10), &bu(1000)).expect(""), bu(24));
}
#[test]
fn mod_pow_modulus_one() {
assert_eq!(mod_pow(&bu(999), &bu(999), &bu(1)).expect(""), bu(0));
}
#[test]
fn mod_pow_zero_exponent() {
assert_eq!(mod_pow(&bu(0), &bu(0), &bu(7)).expect(""), bu(1));
assert_eq!(mod_pow(&bu(5), &bu(0), &bu(7)).expect(""), bu(1));
}
#[test]
fn mod_pow_zero_modulus() {
assert!(mod_pow(&bu(2), &bu(10), &bu(0)).is_err());
}
#[test]
fn mod_pow_base_zero() {
assert_eq!(mod_pow(&bu(0), &bu(5), &bu(7)).expect(""), bu(0));
}
#[test]
fn mod_pow_fermat_little_theorem() {
for &p in &[7u64, 13, 101, 65537] {
for a in 2u64..p.min(10) {
let result = mod_pow(&bu(a), &bu(p - 1), &bu(p)).expect("mod_pow Fermat");
assert_eq!(result, bu(1), "Fermat failed for a={a}, p={p}");
}
}
}
#[test]
fn mod_pow_large_exp() {
let result = mod_pow(&bu(3), &bu(1000), &bu(1000)).expect("mod_pow large");
assert!(result < bu(1000));
}
}