use crate::parity::Parity;
#[cfg(feature = "nightly")]
use const_num_traits::{One, OverflowingAdd, OverflowingSub, Zero};
#[cfg(feature = "nightly")]
c0nst::c0nst! {
pub c0nst fn const_mod_mul<T>(a: T, b: T, m: T) -> T
where
T: [c0nst] core::cmp::PartialOrd
+ [c0nst] core::cmp::PartialEq
+ Copy
+ [c0nst] Zero
+ [c0nst] One
+ [c0nst] core::ops::BitAnd<Output = T>
+ [c0nst] OverflowingAdd
+ [c0nst] core::ops::Add<Output = T>
+ [c0nst] OverflowingSub
+ [c0nst] core::ops::Sub<Output = T>
+ [c0nst] core::ops::Shr<usize, Output = T>
+ [c0nst] core::ops::Rem<Output = T>,
{
let mut a = a % m;
let mut b = b % m;
let mut result = T::zero();
while b > T::zero() {
if b & T::one() == T::one() {
let (sum, overflow) = result.overflowing_add(a);
result = if sum >= m || overflow {
sum.overflowing_sub(m).0
} else {
sum
};
}
b = b >> 1;
if b > T::zero() {
let (doubled, overflow) = a.overflowing_add(a);
a = if doubled >= m || overflow {
doubled.overflowing_sub(m).0
} else {
doubled
};
}
}
result
}
}
pub fn basic_mod_mul<T>(a: T, b: T, m: T) -> T
where
T: core::cmp::PartialOrd
+ Copy
+ const_num_traits::Zero
+ const_num_traits::One
+ crate::parity::Parity
+ const_num_traits::ops::wrapping::WrappingAdd
+ const_num_traits::ops::wrapping::WrappingSub
+ core::ops::Add<Output = T>
+ core::ops::Sub<Output = T>
+ core::ops::Shr<usize, Output = T>
+ core::ops::Rem<Output = T>
+ crate::NonCt,
{
basic_mod_mul_pr(a % m, b % m, m)
}
pub fn basic_mod_mul_nz<T>(a: T, b: T, m: T::NonZero) -> T
where
T: core::cmp::PartialOrd
+ Copy
+ const_num_traits::Zero
+ const_num_traits::One
+ crate::parity::Parity
+ const_num_traits::HasNonZero
+ const_num_traits::DivNonZero<Output = T>
+ const_num_traits::ops::wrapping::WrappingAdd
+ const_num_traits::ops::wrapping::WrappingSub
+ core::ops::Add<Output = T>
+ core::ops::Sub<Output = T>
+ core::ops::Shr<usize, Output = T>
+ crate::NonCt,
{
let m_raw = T::nonzero_get(m);
basic_mod_mul_pr(a.rem_nonzero(m), b.rem_nonzero(m), m_raw)
}
pub fn basic_mod_mul_pr<T>(mut a: T, mut b: T, m: T) -> T
where
T: core::cmp::PartialOrd
+ Copy
+ const_num_traits::Zero
+ const_num_traits::One
+ crate::parity::Parity
+ const_num_traits::ops::wrapping::WrappingAdd
+ const_num_traits::ops::wrapping::WrappingSub
+ core::ops::Add<Output = T>
+ core::ops::Sub<Output = T>
+ core::ops::Shr<usize, Output = T>
+ crate::NonCt,
{
let m1 = m;
let mut result = T::zero();
while b > T::zero() {
if b.is_odd() {
let sum = result.wrapping_add(a);
result = if sum >= m1 || sum < result {
sum.wrapping_sub(m1)
} else {
sum
};
}
b = b >> 1;
if b > T::zero() {
let sum = a.wrapping_add(a);
a = if sum >= m1 || sum < a {
sum.wrapping_sub(m1)
} else {
sum
};
}
}
result
}
pub fn constrained_mod_mul<T>(mut a: T, b: &T, m: &T) -> T
where
T: Clone
+ const_num_traits::Zero
+ const_num_traits::One
+ PartialOrd
+ const_num_traits::ops::wrapping::WrappingAdd
+ const_num_traits::ops::wrapping::WrappingSub
+ core::ops::Add<Output = T>
+ core::ops::Sub<Output = T>
+ core::ops::Shr<usize, Output = T>
+ crate::NonCt,
for<'a> T: core::ops::RemAssign<&'a T>,
for<'a> &'a T: core::ops::Rem<&'a T, Output = T>,
for<'a> &'a T: crate::parity::Parity,
{
a.rem_assign(m);
let b = b % m;
constrained_mod_mul_pr(a, &b, m)
}
pub fn constrained_mod_mul_nz<T>(a: T, b: &T, m: T::NonZero) -> T
where
T: Clone
+ const_num_traits::Zero
+ const_num_traits::One
+ PartialOrd
+ const_num_traits::HasNonZero
+ const_num_traits::DivNonZero<Output = T>
+ const_num_traits::ops::wrapping::WrappingAdd
+ const_num_traits::ops::wrapping::WrappingSub
+ core::ops::Add<Output = T>
+ core::ops::Sub<Output = T>
+ core::ops::Shr<usize, Output = T>
+ crate::NonCt,
for<'a> &'a T: crate::parity::Parity,
{
let m_raw = T::nonzero_get(m);
let b_mod = b.clone().rem_nonzero(m);
constrained_mod_mul_pr(a.rem_nonzero(m), &b_mod, &m_raw)
}
pub fn constrained_mod_mul_pr<T>(mut a: T, b: &T, m: &T) -> T
where
T: Clone
+ const_num_traits::Zero
+ const_num_traits::One
+ PartialOrd
+ const_num_traits::ops::wrapping::WrappingAdd
+ const_num_traits::ops::wrapping::WrappingSub
+ core::ops::Add<Output = T>
+ core::ops::Sub<Output = T>
+ core::ops::Shr<usize, Output = T>
+ crate::NonCt,
for<'a> &'a T: crate::parity::Parity,
{
let mut b = b.clone();
let mut result = T::zero();
while b > T::zero() {
if (&b).is_odd() {
let sum = result.clone().wrapping_add(a.clone());
result = if &sum >= m || sum < result {
sum.wrapping_sub(m.clone())
} else {
sum
};
}
b = b >> 1;
if b > T::zero() {
let sum = a.clone().wrapping_add(a.clone());
a = if &sum >= m || sum < a {
sum.wrapping_sub(m.clone())
} else {
sum
};
}
}
result
}
pub fn strict_mod_mul_nz<T>(a: T, b: &T, m: T::NonZero) -> T
where
T: Clone
+ const_num_traits::Zero
+ const_num_traits::One
+ PartialOrd
+ const_num_traits::HasNonZero
+ const_num_traits::DivNonZero<Output = T>
+ const_num_traits::ops::overflowing::OverflowingAdd
+ const_num_traits::ops::overflowing::OverflowingSub
+ core::ops::Add<Output = T>
+ core::ops::Sub<Output = T>
+ core::ops::Shr<usize, Output = T>
+ crate::NonCt,
for<'a> &'a T: crate::parity::Parity,
{
let m_raw = T::nonzero_get(m);
let b_mod = b.clone().rem_nonzero(m);
strict_mod_mul_pr(a.rem_nonzero(m), &b_mod, &m_raw)
}
pub fn strict_mod_mul<T>(mut a: T, b: &T, m: &T) -> T
where
T: Clone
+ const_num_traits::Zero
+ const_num_traits::One
+ PartialOrd
+ const_num_traits::ops::overflowing::OverflowingAdd
+ const_num_traits::ops::overflowing::OverflowingSub
+ core::ops::Add<Output = T>
+ core::ops::Sub<Output = T>
+ core::ops::Shr<usize, Output = T>
+ crate::NonCt,
for<'a> T: core::ops::RemAssign<&'a T>,
for<'a> &'a T: core::ops::Rem<&'a T, Output = T>,
for<'a> &'a T: crate::parity::Parity,
{
a.rem_assign(m);
let b = b % m;
strict_mod_mul_pr(a, &b, m)
}
pub fn strict_mod_mul_pr<T>(mut a: T, b: &T, m: &T) -> T
where
T: Clone
+ const_num_traits::Zero
+ const_num_traits::One
+ PartialOrd
+ const_num_traits::ops::overflowing::OverflowingAdd
+ const_num_traits::ops::overflowing::OverflowingSub
+ core::ops::Add<Output = T>
+ core::ops::Sub<Output = T>
+ core::ops::Shr<usize, Output = T>
+ crate::NonCt,
for<'a> &'a T: crate::parity::Parity,
{
let mut b = b.clone();
let mut result = T::zero();
while b > T::zero() {
if (&b).is_odd() {
let (sum, overflow) = result.overflowing_add(a.clone());
result = if &sum >= m || overflow {
sum.overflowing_sub(m.clone()).0
} else {
sum
};
}
b = b >> 1;
if b > T::zero() {
let (doubled, overflow) = a.clone().overflowing_add(a);
a = if &doubled >= m || overflow {
doubled.overflowing_sub(m.clone()).0
} else {
doubled
};
}
}
result
}
#[cfg(test)]
macro_rules! select_mod_mul {
($mod_mul:path, $t:ty, by_ref) => {
fn mod_mul(a: $t, b: &$t, m: &$t) -> $t {
$mod_mul(a, b, m)
}
};
($mod_mul:path, $t:ty, by_val) => {
fn mod_mul(a: $t, b: &$t, m: &$t) -> $t {
$mod_mul(a, *b, *m)
}
};
}
#[cfg(test)]
macro_rules! generate_mod_mul_tests_block_1 {
($mod_add:path, $t:ty, $by_ref:tt) => {
select_mod_mul!($mod_add, $t, $by_ref);
#[test]
fn test_basic_cases() {
assert_eq!(mod_mul(7u8, &13u8, &19u8), 15); assert_eq!(mod_mul(6u8, &9u8, &7u8), 5); assert_eq!(mod_mul(5u8, &5u8, &11u8), 3); }
#[test]
fn test_a_is_zero() {
assert_eq!(mod_mul(0u8, &5u8, &7u8), 0); assert_eq!(mod_mul(0u8, &255u8, &19u8), 0); }
#[test]
fn test_b_is_zero() {
assert_eq!(mod_mul(5u8, &0u8, &7u8), 0); assert_eq!(mod_mul(255u8, &0u8, &19u8), 0); }
#[test]
fn test_modulus_is_one() {
assert_eq!(mod_mul(7u8, &13u8, &1u8), 0); assert_eq!(mod_mul(255u8, &255u8, &1u8), 0); }
#[test]
#[should_panic]
fn test_modulus_is_zero() {
mod_mul(7u8, &13u8, &0u8); }
#[test]
fn test_max_values() {
assert_eq!(mod_mul(255u8, &255u8, &19u8), 7); assert_eq!(mod_mul(255u8, &255u8, &255u8), 0); }
#[test]
fn test_multiplication_by_one() {
assert_eq!(mod_mul(1u8, &5u8, &7u8), 5 % 7); assert_eq!(mod_mul(7u8, &1u8, &19u8), 7 % 19); }
#[test]
fn test_equal_values() {
assert_eq!(mod_mul(7u8, &7u8, &19u8), (7 * 7) % 19); assert_eq!(mod_mul(13u8, &13u8, &19u8), (13 * 13) % 19); }
#[test]
fn test_prime_moduli() {
assert_eq!(mod_mul(7u8, &13u8, &19u8), (7 * 13) % 19); assert_eq!(mod_mul(8u8, &9u8, &17u8), (8 * 9) % 17); assert_eq!(mod_mul(5u8, &11u8, &23u8), (5 * 11) % 23); }
#[test]
fn test_large_values_small_modulus() {
assert_eq!(mod_mul(200u8, &200u8, &7u8), 2); assert_eq!(mod_mul(255u8, &255u8, &3u8), 0); }
#[test]
fn test_small_modulus() {
assert_eq!(mod_mul(7u8, &8u8, &2u8), (7 * 8) % 2); assert_eq!(mod_mul(5u8, &6u8, &4u8), (5 * 6) % 4); }
#[test]
fn test_powers_of_two_modulus() {
assert_eq!(mod_mul(7u8, &13u8, &8u8), 3); assert_eq!(mod_mul(16u8, &16u8, &16u8), 0); }
#[test]
fn test_modulus_greater_than_a_or_b() {
assert_eq!(mod_mul(10u8, &12u8, &20u8), (10 * 12) % 20); assert_eq!(mod_mul(15u8, &14u8, &30u8), (15 * 14) % 30); }
#[test]
fn test_a_or_b_equals_m_minus_1() {
assert_eq!(mod_mul(18u8, &13u8, &19u8), (18 * 13) % 19); assert_eq!(mod_mul(7u8, &16u8, &17u8), (7 * 16) % 17); }
#[test]
fn test_binary_modulus() {
assert_eq!(mod_mul(5u8, &6u8, &2u8), (5 * 6) % 2); }
#[test]
fn test_small_moduli_explicit() {
assert_eq!(mod_mul(10u8, &9u8, &2u8), (10 * 9) % 2); assert_eq!(mod_mul(10u8, &9u8, &3u8), (10 * 9) % 3); }
#[test]
fn test_a_and_b_equals_m_minus_1() {
assert_eq!(mod_mul(18u8, &18u8, &19u8), 1); assert_eq!(mod_mul(254u8, &254u8, &255u8), 1); }
#[test]
fn test_a_or_b_equals_modulus() {
assert_eq!(mod_mul(7u8, &8u8, &8u8), 0); assert_eq!(mod_mul(8u8, &8u8, &8u8), 0); }
#[test]
fn test_large_product_small_modulus() {
assert_eq!(mod_mul(250u8, &240u8, &13u8), 5); assert_eq!(mod_mul(200u8, &200u8, &5u8), 0); }
};
}
#[cfg(test)]
macro_rules! generate_mod_mul_tests_block_2 {
($mod_add:path, $t:ty, $by_ref:tt) => {
select_mod_mul!($mod_add, $t, $by_ref);
#[test]
fn test_64bit_large_values() {
assert_eq!(mod_mul(u64::MAX, &u64::MAX, &u64::MAX), 0);
assert_eq!(
mod_mul(u64::MAX, &(u64::MAX - 1), &1_000_000_007_u64),
532600269
);
assert_eq!(
mod_mul(
12345678901234567890_u64,
&9876543210987654321_u64,
&1_000_000_007_u64
),
77470638
);
assert_eq!(mod_mul(10_u64, &20_u64, &u64::MAX), (10 * 20) % u64::MAX);
}
#[test]
fn test_64bit_overflows() {
assert_eq!(mod_mul(2_u64.pow(32), &2_u64.pow(32), &u64::MAX), 1);
}
#[test]
fn test_64bit_specific_patterns() {
assert_eq!(mod_mul(2_u64.pow(63), &2_u64, &u64::MAX), 1);
assert_eq!(
mod_mul(u64::MAX - 1, &1_u64, &u64::MAX),
(u64::MAX - 1) % u64::MAX
); let large_prime = 18_446_744_073_709_551_557_u64;
assert_eq!(mod_mul(u64::MAX, &(u64::MAX - 1), &large_prime), 3306_u64);
assert_eq!(mod_mul(u64::MAX - 1, &(u64::MAX - 1), &2_u64), 0);
}
};
}
#[cfg(test)]
mod strict_mod_mul_tests {
use super::strict_mod_mul;
mod u8_tests {
use super::strict_mod_mul;
generate_mod_mul_tests_block_1!(strict_mod_mul, u8, by_ref);
}
mod u64_tests {
use super::strict_mod_mul;
generate_mod_mul_tests_block_2!(strict_mod_mul, u64, by_ref);
}
}
#[cfg(test)]
mod constrained_mod_mul_tests {
use super::constrained_mod_mul;
mod u8_tests {
use super::constrained_mod_mul;
generate_mod_mul_tests_block_1!(constrained_mod_mul, u8, by_ref);
}
mod u64_tests {
use super::constrained_mod_mul;
generate_mod_mul_tests_block_2!(constrained_mod_mul, u64, by_ref);
}
}
#[cfg(test)]
mod basic_mod_mul_tests {
use super::basic_mod_mul;
mod u8_tests {
use super::basic_mod_mul;
generate_mod_mul_tests_block_1!(basic_mod_mul, u8, by_val);
}
mod u64_tests {
use super::basic_mod_mul;
generate_mod_mul_tests_block_2!(basic_mod_mul, u64, by_val);
}
}
#[cfg(test)]
#[cfg(feature = "nightly")]
const _: () = {
let result = const_mod_mul(7u8, 13u8, 19u8);
assert!(result == 15u8);
};
#[cfg(test)]
#[cfg(feature = "nightly")]
mod const_mod_mul_tests {
use super::const_mod_mul;
mod u8_tests {
use super::const_mod_mul;
generate_mod_mul_tests_block_1!(const_mod_mul, u8, by_val);
}
mod u64_tests {
use super::const_mod_mul;
generate_mod_mul_tests_block_2!(const_mod_mul, u64, by_val);
}
}
#[cfg(test)]
macro_rules! mul_test_module {
(
$stem:ident, // Base name (e.g., "bnum")
$type_path:path, $(type $type_def:ty = $type_expr:ty;)? strict: $strict:expr,
constrained: $constrained:expr,
basic: $basic:expr,
) => {
paste::paste! {
mod [<$stem _tests>] { #[allow(unused_imports)]
use $type_path;
$( type $type_def = $type_expr; )?
#[test]
#[allow(unused_variables)]
fn test_mod_mul_basic() {
let a_val = 5u8;
let a = U256::from(a_val);
let b = U256::from(10u8);
let m = U256::from(20u8);
let result = U256::from(10u8);
crate::maybe_test!($strict, assert_eq!(super::strict_mod_mul(a, &b, &m), result));
let a = U256::from(a_val);
crate::maybe_test!($constrained, assert_eq!(super::constrained_mod_mul(a, &b, &m), result));
let a = U256::from(a_val);
crate::maybe_test!($basic, assert_eq!(super::basic_mod_mul(a, b, m), result));
let a_val = 12345u32;
let a = U256::from(a_val);
let b = U256::from(6789u32);
let m = U256::from(10000u32);
let result = U256::from(205u32);
crate::maybe_test!($strict, assert_eq!(super::strict_mod_mul(a, &b, &m), result));
let a = U256::from(a_val);
crate::maybe_test!($constrained, assert_eq!(super::constrained_mod_mul(a, &b, &m), result));
let a = U256::from(a_val);
crate::maybe_test!($basic, assert_eq!(super::basic_mod_mul(a, b, m), result));
}
}
}
};
}
#[cfg(test)]
mod bnum_mul_tests {
use super::basic_mod_mul;
use super::constrained_mod_mul;
use super::strict_mod_mul;
mul_test_module!(
fixed_bigint,
fixed_bigint::FixedUInt,
type U256 = fixed_bigint::FixedUInt<u32, 4>;
strict: on,
constrained: on,
basic: on,
);
}
#[cfg(test)]
mod fixed_bigint_pr_tests {
use super::{basic_mod_mul_pr, constrained_mod_mul_pr, strict_mod_mul_pr};
type U256 = fixed_bigint::FixedUInt<u32, 4>;
#[test]
fn test_mod_mul_basic_pr() {
let m = U256::from(19u8);
let a = U256::from(7u8);
let b = U256::from(13u8);
let expected = U256::from(15u8);
assert_eq!(strict_mod_mul_pr(a, &b, &m), expected);
let a = U256::from(7u8);
assert_eq!(constrained_mod_mul_pr(a, &b, &m), expected);
let a = U256::from(7u8);
assert_eq!(basic_mod_mul_pr(a, b, m), expected);
}
#[test]
fn test_mod_mul_zero_pr() {
let m = U256::from(19u8);
let zero = U256::from(0u8);
let b = U256::from(13u8);
assert_eq!(strict_mod_mul_pr(zero, &b, &m), zero);
assert_eq!(constrained_mod_mul_pr(zero, &b, &m), zero);
assert_eq!(basic_mod_mul_pr(zero, b, m), zero);
}
}
#[cfg(test)]
mod nz_tests {
use super::*;
use const_num_traits::HasNonZero;
#[test]
fn basic_mod_mul_nz_matches_basic_mod_mul() {
let m: u32 = 97;
let m_nz = m.into_nonzero().unwrap();
for a in [0u32, 1, 5, 96, 200, 0xFFFF] {
for b in [0u32, 1, 50, 96, 300, 0xABCD] {
assert_eq!(basic_mod_mul_nz(a, b, m_nz), basic_mod_mul(a, b, m));
}
}
}
#[test]
fn constrained_mod_mul_nz_matches_constrained_mod_mul() {
let m: u32 = 97;
let m_nz = m.into_nonzero().unwrap();
for a in [0u32, 1, 5, 96, 200, 0xFFFF] {
for b in [0u32, 1, 50, 96, 300, 0xABCD] {
assert_eq!(
constrained_mod_mul_nz(a, &b, m_nz),
constrained_mod_mul(a, &b, &m)
);
}
}
}
#[test]
fn strict_mod_mul_nz_matches_strict_mod_mul() {
let m: u32 = 97;
let m_nz = m.into_nonzero().unwrap();
for a in [0u32, 1, 5, 96, 200, 0xFFFF] {
for b in [0u32, 1, 50, 96, 300, 0xABCD] {
assert_eq!(strict_mod_mul_nz(a, &b, m_nz), strict_mod_mul(a, &b, &m));
}
}
}
#[test]
fn basic_mod_mul_nz_with_fixed_bigint() {
use fixed_bigint::FixedUInt;
type U128 = FixedUInt<u32, 4>;
let m = U128::from(97u32);
let m_nz = m.into_nonzero().expect("97 != 0");
let a = U128::from(50u32);
let b = U128::from(50u32);
let via_nz = basic_mod_mul_nz(a, b, m_nz);
let via_old = basic_mod_mul(a, b, m);
assert_eq!(via_nz, via_old);
assert_eq!(via_nz, U128::from(75u32));
}
}