use crate::int::algos::div::div_knuth::div_knuth_into;
use crate::int::algos::div::div_knuth_u128_limb::div_knuth_u128_limb_into;
use crate::int::policy::div_rem::{select_for_limbs, Algorithm};
use crate::int::types::compute_limbs::{ComputeLimbs, Limbs};
use crate::int::types::Int;
#[inline]
pub(crate) fn rem_int_layer<const N: usize>(a: Int<N>, b: Int<N>) -> Int<N>
where
Limbs<N>: ComputeLimbs,
{
if a == Int::<N>::MIN && b == -Int::<N>::ONE {
panic!("attempt to calculate the remainder with overflow");
}
assert!(
!b.is_zero(),
"attempt to calculate the remainder with a divisor of zero"
);
let neg_r = a.is_negative();
let a_abs = a.unsigned_abs();
let b_abs = b.unsigned_abs();
if a_abs < b_abs {
return a;
}
let al = a_abs.as_limbs();
let bl = b_abs.as_limbs();
let mut fits = true;
let mut i = 2;
while i < N {
if al[i] != 0 || bl[i] != 0 {
fits = false;
break;
}
i += 1;
}
if fits {
let a_hi = if N >= 2 { al[1] as u128 } else { 0 };
let b_hi = if N >= 2 { bl[1] as u128 } else { 0 };
let a_u = (al[0] as u128) | (a_hi << 64);
let b_u = (bl[0] as u128) | (b_hi << 64);
let r = a_u % b_u;
let mut rem = [0u64; N];
rem[0] = r as u64;
if N >= 2 {
rem[1] = (r >> 64) as u64;
}
return Int::<N>::from_mag_limbs(&rem, neg_r);
}
divmod_mags::<N>(&a_abs, &b_abs, neg_r)
}
#[inline]
fn divmod_mags<const N: usize>(
a_abs: &crate::int::types::Uint<N>,
b_abs: &crate::int::types::Uint<N>,
neg_r: bool,
) -> Int<N>
where
Limbs<N>: ComputeLimbs,
{
let mut quot = [0u64; N];
let mut rem = [0u64; N];
let mut u = Limbs::<N>::single_buffered_u64();
let mut v = Limbs::<N>::single_buffered_u64();
match select_for_limbs(a_abs.as_limbs(), b_abs.as_limbs()) {
Algorithm::KnuthU128Limb => {
let mut u128_u = Limbs::<N>::double_u128();
let mut u128_v = Limbs::<N>::single_u128();
div_knuth_u128_limb_into(
a_abs.as_limbs(),
b_abs.as_limbs(),
&mut quot,
&mut rem,
u.as_mut(),
v.as_mut(),
u128_u.as_mut(),
u128_v.as_mut(),
);
}
Algorithm::Rem
| Algorithm::Knuth
| Algorithm::BurnikelZieglerWithKnuth
| Algorithm::Schoolbook => div_knuth_into(
a_abs.as_limbs(),
b_abs.as_limbs(),
&mut quot,
&mut rem,
u.as_mut(),
v.as_mut(),
),
}
Int::<N>::from_mag_limbs(&rem, neg_r)
}
#[inline]
pub(crate) fn rem_int_layer_divmod<const N: usize>(a: Int<N>, b: Int<N>) -> Int<N>
where
Limbs<N>: ComputeLimbs,
{
if a == Int::<N>::MIN && b == -Int::<N>::ONE {
panic!("attempt to calculate the remainder with overflow");
}
assert!(
!b.is_zero(),
"attempt to calculate the remainder with a divisor of zero"
);
let neg_r = a.is_negative();
let a_abs = a.unsigned_abs();
let b_abs = b.unsigned_abs();
divmod_mags::<N>(&a_abs, &b_abs, neg_r)
}
#[cfg(test)]
mod tests {
use super::{rem_int_layer, rem_int_layer_divmod};
use crate::int::types::Int;
#[test]
fn fast_path_matches_divmod_only() {
let small: &[(i128, i128)] = &[
(2, 1),
(100, 7),
(-100, 7),
(100, -7),
(-100, -7),
(0, 5),
(5, 5),
(i128::MAX, 3),
(i128::MIN + 1, 7),
];
for &(a, b) in small {
let ia = Int::<3>::from_i128(a);
let ib = Int::<3>::from_i128(b);
assert_eq!(
rem_int_layer::<3>(ia, ib),
rem_int_layer_divmod::<3>(ia, ib),
"fast path ({a} % {b}) at N=3"
);
let ja = Int::<16>::from_i128(a);
let jb = Int::<16>::from_i128(b);
assert_eq!(
rem_int_layer::<16>(ja, jb),
rem_int_layer_divmod::<16>(ja, jb),
"fast path ({a} % {b}) at N=16"
);
}
let mut a_lim = [0u64; 8];
let mut b_lim = [0u64; 8];
for i in 0..8 {
a_lim[i] = 0x9E37_79B9_7F4A_7C15u64.wrapping_mul(i as u64 + 1);
b_lim[i] = 0xD1B5_4A32_D192_ED03u64.wrapping_mul(i as u64 + 3);
}
let ia = Int::<8>::from_mag_limbs(&a_lim, false);
let ib = Int::<8>::from_mag_limbs(&b_lim, true); assert_eq!(
rem_int_layer::<8>(ia, ib),
rem_int_layer_divmod::<8>(ia, ib),
"full-width fall-through"
);
}
#[cfg(feature = "exact-scratch")]
#[test]
fn u128_verdict_shape_matches_knuth_reference() {
use crate::int::algos::div::div_knuth::div_knuth_into;
use crate::int::policy::div_rem::{select_for_limbs, Algorithm};
const N: usize = 64;
let mut state: u64 = 0xA076_1D64_78BD_642F;
let mut next = || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
let shapes: &[(usize, usize)] = &[(64, 24), (64, 32), (48, 24), (56, 28), (50, 24)];
for (case, &(num_n, den_n)) in shapes.iter().enumerate() {
for round in 0..8 {
let mut a_lim = [0u64; N];
let mut b_lim = [0u64; N];
for x in a_lim[..num_n].iter_mut() {
*x = next();
}
for x in b_lim[..den_n].iter_mut() {
*x = next();
}
if num_n == N {
a_lim[N - 1] &= !(1u64 << 63);
}
a_lim[num_n - 1] |= 1;
b_lim[den_n - 1] |= 1;
assert!(
select_for_limbs(&a_lim, &b_lim) == Algorithm::KnuthU128Limb,
"case {case}: ({num_n},{den_n}) sig limbs must route to KnuthU128Limb"
);
let mut quot = [0u64; N];
let mut rem = [0u64; N];
let mut u = [0u64; N + 2];
let mut v = [0u64; N + 2];
div_knuth_into(&a_lim, &b_lim, &mut quot, &mut rem, &mut u, &mut v);
let a_neg = round & 1 == 1;
let b_neg = round & 2 == 2;
let ia = Int::<N>::from_mag_limbs(&a_lim, a_neg);
let ib = Int::<N>::from_mag_limbs(&b_lim, b_neg);
let expected = Int::<N>::from_mag_limbs(&rem, a_neg);
assert_eq!(
rem_int_layer::<N>(ia, ib),
expected,
"case {case} round {round}: ({num_n},{den_n}) a_neg={a_neg} b_neg={b_neg}"
);
}
}
}
}