use crate::{
Choice, Limb, Odd, U64, U128, Uint, Word,
primitives::{u32_max, u32_min},
word,
};
#[inline(always)]
pub(super) const fn bingcd_step<const LIMBS: usize>(
a: &mut Uint<LIMBS>,
b: &mut Uint<LIMBS>,
) -> (Choice, Choice, Word) {
let a_b_mod_4 = (a.limbs[0].0 & b.limbs[0].0) & 3;
let a_odd = a.is_odd();
let (a_sub_b, borrow) = a.borrowing_sub(&Uint::select(&Uint::ZERO, b, a_odd), Limb::ZERO);
let swap = borrow.is_nonzero();
*b = Uint::select(b, a, swap);
*a = a_sub_b.wrapping_neg_if(swap).shr1();
let mut jacobi_neg = word::select(0, a_b_mod_4 & (a_b_mod_4 >> 1) & 1, swap);
let b_lo = b.limbs[0].0;
jacobi_neg ^= ((b_lo >> 1) ^ (b_lo >> 2)) & 1;
(a_odd, swap, jacobi_neg)
}
impl<const LIMBS: usize> Odd<Uint<LIMBS>> {
pub(crate) const MINIMAL_BINGCD_ITERATIONS: u32 = 2 * Self::BITS - 1;
#[inline]
pub(crate) const fn classic_bingcd(&self, rhs: &Uint<LIMBS>) -> Self {
self.classic_bingcd_(rhs).0
}
#[inline(always)]
pub(crate) const fn classic_bingcd_(&self, rhs: &Uint<LIMBS>) -> (Self, Word) {
let (mut a, mut b) = (*rhs, *self.as_ref());
let mut i = 0;
let mut jacobi_neg = 0;
while i < Self::MINIMAL_BINGCD_ITERATIONS {
jacobi_neg ^= bingcd_step(&mut a, &mut b).2;
i += 1;
}
let gcd = b
.to_odd()
.expect_copied("gcd of an odd value with something else is always odd");
(gcd, jacobi_neg)
}
#[inline]
pub(crate) const fn classic_bingcd_vartime(&self, rhs: &Uint<LIMBS>) -> Self {
self.classic_bingcd_vartime_(rhs).0
}
#[inline(always)]
pub(crate) const fn classic_bingcd_vartime_(&self, rhs: &Uint<LIMBS>) -> (Self, Word) {
let (mut a, mut b) = (*rhs, *self.as_ref());
let mut jacobi_neg = 0;
while !a.is_zero_vartime() {
jacobi_neg ^= bingcd_step(&mut a, &mut b).2;
}
let gcd = b
.to_odd()
.expect_copied("gcd of an odd value with something else is always odd");
(gcd, jacobi_neg)
}
#[inline(always)]
pub(crate) const fn optimized_bingcd(&self, rhs: &Uint<LIMBS>) -> Self {
self.optimized_bingcd_::<{ U64::BITS }, { U64::LIMBS }, { U128::LIMBS }>(rhs, U64::BITS - 1)
.0
}
#[inline(always)]
pub(crate) const fn optimized_bingcd_<
const K: u32,
const LIMBS_K: usize,
const LIMBS_2K: usize,
>(
&self,
rhs: &Uint<LIMBS>,
batch_max: u32,
) -> (Self, Word) {
let (mut a, mut b) = (*self.as_ref(), *rhs);
let mut jacobi_neg = 0;
let mut iterations = Self::MINIMAL_BINGCD_ITERATIONS;
while iterations > 0 {
let batch = u32_min(iterations, batch_max);
iterations -= batch;
let n = u32_max(2 * K, a.bitor(&b).bits());
let a_ = a.compact::<K, LIMBS_2K>(n);
let b_ = b.compact::<K, LIMBS_2K>(n);
let (.., matrix, j_neg) = a_
.to_odd()
.expect_copied("a_ is always odd")
.partial_binxgcd::<LIMBS_K>(&b_, batch, Choice::FALSE);
jacobi_neg ^= j_neg;
let (updated_a, updated_b) = matrix.extended_apply_to_vartime::<LIMBS>((a, b));
(a, _) = updated_a.dropped_abs_sign();
(b, _) = updated_b.dropped_abs_sign();
}
(
a.to_odd()
.expect_copied("gcd of an odd value is always odd"),
jacobi_neg,
)
}
#[inline(always)]
pub(crate) const fn optimized_bingcd_vartime(&self, rhs: &Uint<LIMBS>) -> Self {
self.optimized_bingcd_vartime_::<{ U64::BITS }, { U64::LIMBS }, { U128::LIMBS }>(
rhs,
U64::BITS - 1,
)
.0
}
#[inline(always)]
pub(crate) const fn optimized_bingcd_vartime_<
const K: u32,
const LIMBS_K: usize,
const LIMBS_2K: usize,
>(
&self,
rhs: &Uint<LIMBS>,
batch_max: u32,
) -> (Self, Word) {
let (mut a, mut b) = (*self.as_ref(), *rhs);
let mut jacobi_neg = 0;
while !b.is_zero_vartime() {
let n = u32_max(2 * K, u32_max(a.bits_vartime(), b.bits_vartime()));
let mut a_ = a.compact_vartime::<K, LIMBS_2K>(n);
let mut b_ = b.compact_vartime::<K, LIMBS_2K>(n);
if n <= Uint::<LIMBS_2K>::BITS {
loop {
jacobi_neg ^= bingcd_step(&mut b_, &mut a_).2;
if b_.is_zero_vartime() {
break;
}
}
a = a_.resize();
break;
}
let (.., matrix, j_neg) = a_
.to_odd()
.expect_copied("a_ is always odd")
.partial_binxgcd::<LIMBS_K>(&b_, batch_max, Choice::FALSE);
jacobi_neg ^= j_neg;
let (updated_a, updated_b) = matrix.extended_apply_to_vartime((a, b));
(a, _) = updated_a.dropped_abs_sign();
(b, _) = updated_b.dropped_abs_sign();
}
(
a.to_odd()
.expect_copied("gcd of an odd value with something else is always odd"),
jacobi_neg,
)
}
}
#[cfg(all(test, not(miri)))]
mod tests {
mod test_classic_bingcd {
use crate::{Int, U64, U128, U192, U256, U384, U512, U1024, U2048, U4096, Uint};
fn classic_bingcd_test<const LIMBS: usize>(
lhs: Uint<LIMBS>,
rhs: Uint<LIMBS>,
target: Uint<LIMBS>,
) {
assert_eq!(
lhs.to_odd().unwrap().classic_bingcd(&rhs),
target.to_odd().unwrap()
);
assert_eq!(
lhs.to_odd().unwrap().classic_bingcd_vartime(&rhs),
target.to_odd().unwrap()
);
}
fn classic_bingcd_tests<const LIMBS: usize>() {
classic_bingcd_test(Uint::<LIMBS>::ONE, Uint::ZERO, Uint::ONE);
classic_bingcd_test(Uint::<LIMBS>::ONE, Uint::ONE, Uint::ONE);
classic_bingcd_test(Uint::<LIMBS>::ONE, Int::MAX.abs(), Uint::ONE);
classic_bingcd_test(Uint::<LIMBS>::ONE, Int::MIN.abs(), Uint::ONE);
classic_bingcd_test(Uint::<LIMBS>::ONE, Uint::MAX, Uint::ONE);
classic_bingcd_test(Int::<LIMBS>::MAX.abs(), Uint::ZERO, Int::MAX.abs());
classic_bingcd_test(Int::<LIMBS>::MAX.abs(), Uint::ONE, Uint::ONE);
classic_bingcd_test(Int::<LIMBS>::MAX.abs(), Int::MAX.abs(), Int::MAX.abs());
classic_bingcd_test(Int::<LIMBS>::MAX.abs(), Int::MIN.abs(), Uint::ONE);
classic_bingcd_test(Int::<LIMBS>::MAX.abs(), Uint::MAX, Uint::ONE);
classic_bingcd_test(Uint::<LIMBS>::MAX, Uint::ZERO, Uint::MAX);
classic_bingcd_test(Uint::<LIMBS>::MAX, Uint::ONE, Uint::ONE);
classic_bingcd_test(Uint::<LIMBS>::MAX, Int::MAX.abs(), Uint::ONE);
classic_bingcd_test(Uint::<LIMBS>::MAX, Int::MIN.abs(), Uint::ONE);
classic_bingcd_test(Uint::<LIMBS>::MAX, Uint::MAX, Uint::MAX);
}
#[test]
fn test_classic_bingcd() {
classic_bingcd_tests::<{ U64::LIMBS }>();
classic_bingcd_tests::<{ U128::LIMBS }>();
classic_bingcd_tests::<{ U192::LIMBS }>();
classic_bingcd_tests::<{ U256::LIMBS }>();
classic_bingcd_tests::<{ U384::LIMBS }>();
classic_bingcd_tests::<{ U512::LIMBS }>();
classic_bingcd_tests::<{ U1024::LIMBS }>();
classic_bingcd_tests::<{ U2048::LIMBS }>();
classic_bingcd_tests::<{ U4096::LIMBS }>();
}
}
mod test_optimized_bingcd {
use crate::{Int, U128, U192, U256, U384, U512, U1024, U2048, U4096, Uint};
fn optimized_bingcd_test<const LIMBS: usize>(
lhs: Uint<LIMBS>,
rhs: Uint<LIMBS>,
target: Uint<LIMBS>,
) {
assert_eq!(
lhs.to_odd().unwrap().optimized_bingcd(&rhs),
target.to_odd().unwrap()
);
assert_eq!(
lhs.to_odd().unwrap().optimized_bingcd_vartime(&rhs),
target.to_odd().unwrap()
);
}
fn optimized_bingcd_tests<const LIMBS: usize>() {
optimized_bingcd_test(Uint::<LIMBS>::ONE, Uint::ZERO, Uint::ONE);
optimized_bingcd_test(Uint::<LIMBS>::ONE, Uint::ONE, Uint::ONE);
optimized_bingcd_test(Uint::<LIMBS>::ONE, Int::MAX.abs(), Uint::ONE);
optimized_bingcd_test(Uint::<LIMBS>::ONE, Int::MIN.abs(), Uint::ONE);
optimized_bingcd_test(Uint::<LIMBS>::ONE, Uint::MAX, Uint::ONE);
optimized_bingcd_test(Int::<LIMBS>::MAX.abs(), Uint::ZERO, Int::MAX.abs());
optimized_bingcd_test(Int::<LIMBS>::MAX.abs(), Uint::ONE, Uint::ONE);
optimized_bingcd_test(Int::<LIMBS>::MAX.abs(), Int::MAX.abs(), Int::MAX.abs());
optimized_bingcd_test(Int::<LIMBS>::MAX.abs(), Int::MIN.abs(), Uint::ONE);
optimized_bingcd_test(Int::<LIMBS>::MAX.abs(), Uint::MAX, Uint::ONE);
optimized_bingcd_test(Uint::<LIMBS>::MAX, Uint::ZERO, Uint::MAX);
optimized_bingcd_test(Uint::<LIMBS>::MAX, Uint::ONE, Uint::ONE);
optimized_bingcd_test(Uint::<LIMBS>::MAX, Int::MAX.abs(), Uint::ONE);
optimized_bingcd_test(Uint::<LIMBS>::MAX, Int::MIN.abs(), Uint::ONE);
optimized_bingcd_test(Uint::<LIMBS>::MAX, Uint::MAX, Uint::MAX);
}
#[test]
fn test_optimized_bingcd() {
optimized_bingcd_tests::<{ U128::LIMBS }>();
optimized_bingcd_tests::<{ U192::LIMBS }>();
optimized_bingcd_tests::<{ U256::LIMBS }>();
optimized_bingcd_tests::<{ U384::LIMBS }>();
optimized_bingcd_tests::<{ U512::LIMBS }>();
optimized_bingcd_tests::<{ U1024::LIMBS }>();
optimized_bingcd_tests::<{ U2048::LIMBS }>();
optimized_bingcd_tests::<{ U4096::LIMBS }>();
}
}
}