mod hgcd;
#[cfg(all(
target_arch = "aarch64",
target_endian = "little",
target_pointer_width = "64",
any(
target_vendor = "apple",
target_os = "linux",
target_os = "android",
target_os = "windows",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "fuchsia"
),
not(miri)
))]
#[path = "gcd/aarch64.rs"]
mod kernels;
#[cfg(not(all(
target_arch = "aarch64",
target_endian = "little",
target_pointer_width = "64",
any(
target_vendor = "apple",
target_os = "linux",
target_os = "android",
target_os = "windows",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "fuchsia"
),
not(miri)
)))]
#[path = "gcd/portable.rs"]
mod kernels;
mod limbs;
use super::add::{read_small, significant_len, small, write_small};
use super::mul::limbs::mul;
use super::mul::{Operand, Product, STACK_LIMBS, on_heap, on_stack, to_limbs};
use super::{ByteOrder, Number};
use core::mem::MaybeUninit;
use core::ptr;
use hgcd::{GCD_DC_THRESHOLD, GCDEXT_DC_THRESHOLD, gcd_dc, gcd_dc_itch, gcdext, gcdext_dc_itch};
use limbs::{
Row, Step, add_to, divexact, divexact_itch, gcd_11, gcd_lehmer, gcd_u128, gcdext_1, hgcd2,
lehmer_ext_itch, lshift, mul_bound, normalized, rem_1, rshift, sub_from, tdiv_itch, tdiv_qr,
with_scratch,
};
impl Number {
pub fn gcd(&self, other: &Number) -> Number {
if small(self, other) {
let (x, y) = unsafe {
(
read_small(&self.value, self.byte_order),
read_small(&other.value, other.byte_order),
)
};
return small_number(self.byte_order, gcd_u128(x, y), false);
}
gcd_general(self, other)
}
pub fn lcm(&self, other: &Number) -> Number {
if small(self, other) {
let (x, y) = unsafe {
(
read_small(&self.value, self.byte_order),
read_small(&other.value, other.byte_order),
)
};
return small_lcm(self.byte_order, x, y);
}
lcm_general(self, other)
}
pub fn gcd_ext(&self, other: &Number) -> (Number, Number, Number) {
if small(self, other) {
let (x, y) = unsafe {
(
read_small(&self.value, self.byte_order),
read_small(&other.value, other.byte_order),
)
};
return small_gcd_ext(self.byte_order, x, self.negative, y, other.negative);
}
gcd_ext_general(self, other)
}
#[track_caller]
pub fn mod_inverse(&self, modulus: &Number) -> Option<Number> {
if small(self, modulus) {
let (x, n) = unsafe {
(
read_small(&self.value, self.byte_order),
read_small(&modulus.value, modulus.byte_order),
)
};
if n == 0 {
by_zero();
}
return small_inverse(x, self.negative, n)
.map(|r| small_number(self.byte_order, r, false));
}
inverse_general(self, modulus)
}
}
#[cold]
#[inline(never)]
#[track_caller]
fn by_zero() -> ! {
panic!("attempt to calculate the remainder with a divisor of zero")
}
#[inline]
fn small_number(order: ByteOrder, m: u128, negative: bool) -> Number {
let len = 16 - (m | 1).leading_zeros() as usize / 8;
let mut v = Vec::with_capacity(len);
unsafe {
write_small(v.as_mut_ptr(), order, m as u64, (m >> 64) as u64, len);
v.set_len(len);
}
Number {
value: v,
negative: negative && m != 0,
byte_order: order,
}
}
fn small_lcm(order: ByteOrder, x: u128, y: u128) -> Number {
let g = gcd_u128(x, y);
if g == 0 {
return small_number(order, 0, false);
}
let q = if (x | g) >> 64 == 0 {
u128::from(x as u64 / g as u64)
} else {
x / g
};
let (q0, q1, y0, y1) = (q as u64, (q >> 64) as u64, y as u64, (y >> 64) as u64);
let mul = |a: u64, b: u64| u128::from(a) * u128::from(b);
let (p00, p01, p10, p11) = (mul(q0, y0), mul(q0, y1), mul(q1, y0), mul(q1, y1));
let c1 = (p00 >> 64) + u128::from(p01 as u64) + u128::from(p10 as u64);
let c2 = (c1 >> 64) + (p01 >> 64) + (p10 >> 64) + u128::from(p11 as u64);
let c3 = (c2 >> 64) + (p11 >> 64);
let limbs = [p00 as u64, c1 as u64, c2 as u64, c3 as u64];
unsafe { limbs_number(limbs.as_ptr(), 4, false, order) }
}
type Signed = (u128, bool);
#[inline(always)]
fn less_times(x: i128, q: u128, y: i128) -> i128 {
x.wrapping_sub((q as i128).wrapping_mul(y))
}
fn gcdext_u128(x: u128, y: u128) -> (u128, i128, i128) {
let (mut a, mut b) = (x, y);
let (mut sa, mut ta, mut sb, mut tb) = (1i128, 0i128, 0i128, 1i128);
while (a | b) >> 64 != 0 {
let s = (a | b).leading_zeros();
let (ah, bh) = (a << s, b << s);
if let Some(m) = hgcd2((ah >> 64) as u64, ah as u64, (bh >> 64) as u64, bh as u64) {
let (u00, u01, u10, u11) = (
u128::from(m.u00),
u128::from(m.u01),
u128::from(m.u10),
u128::from(m.u11),
);
(a, b) = (
u11.wrapping_mul(a).wrapping_sub(u01.wrapping_mul(b)),
u00.wrapping_mul(b).wrapping_sub(u10.wrapping_mul(a)),
);
let (i00, i01, i10, i11) = (u00 as i128, u01 as i128, u10 as i128, u11 as i128);
(sa, sb) = (
i11.wrapping_mul(sa).wrapping_sub(i01.wrapping_mul(sb)),
i00.wrapping_mul(sb).wrapping_sub(i10.wrapping_mul(sa)),
);
(ta, tb) = (
i11.wrapping_mul(ta).wrapping_sub(i01.wrapping_mul(tb)),
i00.wrapping_mul(tb).wrapping_sub(i10.wrapping_mul(ta)),
);
continue;
}
if a == b {
return smaller_pair(a, (sa, ta), (sb, tb));
}
if a > b {
let q = a / b;
a -= q * b;
if a == 0 {
return (b, sb, tb);
}
sa = less_times(sa, q, sb);
ta = less_times(ta, q, tb);
} else {
let q = b / a;
b -= q * a;
if b == 0 {
return (a, sa, ta);
}
sb = less_times(sb, q, sa);
tb = less_times(tb, q, ta);
}
}
if a == b {
return smaller_pair(a, (sa, ta), (sb, tb));
}
let (g, u, v) = gcdext_1(a as u64, b as u64);
let (u, v) = (i128::from(u), i128::from(v));
(
u128::from(g),
u.wrapping_mul(sa).wrapping_add(v.wrapping_mul(sb)),
u.wrapping_mul(ta).wrapping_add(v.wrapping_mul(tb)),
)
}
#[inline]
fn smaller_pair(g: u128, a: (i128, i128), b: (i128, i128)) -> (u128, i128, i128) {
let (ua, ub) = (a.0.unsigned_abs(), b.0.unsigned_abs());
let pick_b = ub < ua || (ub == ua && b.0 > 0);
let (s, t) = if pick_b { b } else { a };
(g, s, t)
}
fn small_gcd_ext(
order: ByteOrder,
x: u128,
negative_x: bool,
y: u128,
negative_y: bool,
) -> (Number, Number, Number) {
let sgn = |m: u128, negative: bool| -> Signed { (u128::from(m != 0), negative) };
let (g, s, t) = if y == 0 {
(x, sgn(x, negative_x), (0, false))
} else if x == 0 {
(y, (0, false), sgn(y, negative_y))
} else if x == y {
(x, (0, false), (1, negative_y))
} else {
let (g, s, t) = gcdext_u128(x, y);
(
g,
(s.unsigned_abs(), (s < 0) != negative_x),
(t.unsigned_abs(), (t < 0) != negative_y),
)
};
(
small_number(order, g, false),
small_number(order, s.0, s.1),
small_number(order, t.0, t.1),
)
}
fn small_inverse(x: u128, negative: bool, n: u128) -> Option<u128> {
if n == 1 {
return Some(0);
}
let x = x % n;
if x == 0 {
return None;
}
let (g, s, _) = gcdext_u128(x, n);
if g != 1 {
return None;
}
let r = if s < 0 {
n - s.unsigned_abs()
} else {
s as u128
};
Some(if negative && r != 0 { n - r } else { r })
}
unsafe fn limbs_number(r: *const u64, n: usize, negative: bool, order: ByteOrder) -> Number {
unsafe {
let top = normalized(r, n);
let p = if top == 0 {
Product {
r: ptr::null(),
len: 1,
negative: false,
}
} else {
Product {
r,
len: 8 * top - (*r.add(top - 1)).leading_zeros() as usize / 8,
negative,
}
};
let mut v = Vec::with_capacity(p.len);
p.write(v.as_mut_ptr(), order);
v.set_len(p.len);
Number {
value: v,
negative: p.negative,
byte_order: order,
}
}
}
fn magnitude(n: &Number, negative: bool, order: ByteOrder) -> Number {
let be = n.byte_order == ByteOrder::BigEndian;
let len = if be {
significant_len::<true>(&n.value)
} else {
significant_len::<false>(&n.value)
};
let bytes = if be {
&n.value[n.value.len() - len..]
} else {
&n.value[..len]
};
let value = if len == 0 {
vec![0]
} else if n.byte_order == order {
bytes.to_vec()
} else {
bytes.iter().rev().copied().collect()
};
Number {
value,
negative: negative && len != 0,
byte_order: order,
}
}
fn sign(nonzero: bool, negative: bool, order: ByteOrder) -> Number {
small_number(order, u128::from(nonzero), negative)
}
#[inline(always)]
fn working<R>(need: usize, f: impl FnOnce(*mut u64) -> R) -> R {
let run = |s: *mut MaybeUninit<u64>| f(s.cast());
if need <= STACK_LIMBS {
on_stack(run)
} else {
on_heap(need, run)
}
}
unsafe fn strip(p: *mut u64, n: usize) -> (usize, usize) {
unsafe {
let mut skip = 0;
while *p.add(skip) == 0 {
skip += 1;
}
let bits = (*p.add(skip)).trailing_zeros();
let m = n - skip;
if skip > 0 {
ptr::copy(p.add(skip), p, m);
}
if bits > 0 {
rshift(p, p, m, bits);
}
(normalized(p, m), 64 * skip + bits as usize)
}
}
#[inline(never)]
fn gcd_general(a: &Number, b: &Number) -> Number {
let order = a.byte_order;
let (x, y) = (Operand::of(a), Operand::of(b));
if x.significant == 0 {
return magnitude(b, false, order);
}
if y.significant == 0 {
return magnitude(a, false, order);
}
let (nx, ny) = (x.significant.div_ceil(8), y.significant.div_ceil(8));
let (long, short) = (nx.max(ny), nx.min(ny));
let need = (nx + 1) + (ny + 1) + (long + 2) + gcd_itch(long, short);
working(need, |s| {
unsafe {
let (pa, pb) = (s, s.add(nx + 1));
let r = pb.add(ny + 1);
let t = r.add(long + 2);
to_limbs(x, pa);
to_limbs(y, pb);
let (na, za) = strip(pa, nx);
let (nb, zb) = strip(pb, ny);
let k = za.min(zb);
let (pa, na, pb, nb) = if na >= nb {
(pa, na, pb, nb)
} else {
(pb, nb, pa, na)
};
let (zl, zbits) = (k / 64, (k % 64) as u32);
let g = r.add(zl);
let gn = gcd_limbs(g, pa, na, pb, nb, t, gcd_itch(long, short));
ptr::write_bytes(r, 0, zl);
*g.add(gn) = lshift(g, g, gn, zbits);
limbs_number(r, zl + gn + 1, false, order)
}
})
}
fn gcd_itch(na: usize, nb: usize) -> usize {
let lehmer = (na - nb + 1) + tdiv_itch(na, nb).max(nb + 1 + tdiv_itch(nb, 1));
if nb >= GCD_DC_THRESHOLD {
lehmer.max(gcd_dc_itch(nb))
} else {
lehmer
}
}
unsafe fn gcd_limbs(
g: *mut u64,
a: *mut u64,
na: usize,
b: *mut u64,
nb: usize,
t: *mut u64,
avail: usize,
) -> usize {
unsafe {
if nb == 1 {
let d = *b;
let r = if na == 1 && *a < d {
*a
} else {
rem_1(a, na, d, t)
};
*g = if r == 0 {
d
} else {
gcd_11(r >> r.trailing_zeros(), d)
};
return 1;
}
if na > nb {
let qn = na - nb + 1;
with_scratch(t, avail, qn + tdiv_itch(na, nb), |t| {
tdiv_qr(t, a, na, b, nb, t.add(qn));
});
if normalized(a, nb) == 0 {
ptr::copy_nonoverlapping(b, g, nb);
return nb;
}
}
let (mut a, mut b, mut n) = (a, b, nb);
if n >= GCD_DC_THRESHOLD {
match gcd_dc(&mut a, &mut b, n, t, avail) {
Step::Size(k) => n = k,
Step::Gcd(p, k) => {
ptr::copy(p, g, k);
return k;
}
}
}
gcd_lehmer(g, a, b, n, t, avail)
}
}
#[inline(never)]
fn lcm_general(a: &Number, b: &Number) -> Number {
let order = a.byte_order;
let (x, y) = (Operand::of(a), Operand::of(b));
if x.significant == 0 || y.significant == 0 {
return small_number(order, 0, false);
}
let g = a.gcd(b);
let one = g.value == [1];
let mut l = if x.significant <= y.significant {
let q = if one {
magnitude(a, false, order)
} else {
a.div_exact(&g)
};
q * b
} else {
let q = if one {
magnitude(b, false, b.byte_order)
} else {
b.div_exact(&g)
};
a * q
};
l.negative = false;
l
}
fn ext_dc_itch(n: usize) -> usize {
if n >= GCDEXT_DC_THRESHOLD {
gcdext_dc_itch(n)
} else {
0
}
}
#[inline(never)]
fn gcd_ext_general(a: &Number, b: &Number) -> (Number, Number, Number) {
let order = a.byte_order;
let (x, y) = (Operand::of(a), Operand::of(b));
if y.significant == 0 {
return (
magnitude(a, false, order),
sign(x.significant != 0, a.negative, order),
sign(false, false, order),
);
}
if x.significant == 0 {
return (
magnitude(b, false, order),
sign(false, false, order),
sign(true, b.negative, order),
);
}
let (nx, ny) = (x.significant.div_ceil(8), y.significant.div_ceil(8));
let swap = nx < ny;
let (p, q, negative_p, negative_q) = if swap {
(y, x, b.negative, a.negative)
} else {
(x, y, a.negative, b.negative)
};
let (np, nq) = if swap { (ny, nx) } else { (nx, ny) };
let (g, cp, cq) = cofactors(p, np, q, nq, order);
let cp = Number {
negative: cp.negative != negative_p && cp.value != [0],
..cp
};
let cq = Number {
negative: cq.negative != negative_q && cq.value != [0],
..cq
};
if swap { (g, cq, cp) } else { (g, cp, cq) }
}
fn cofactors(
p: Operand,
np: usize,
q: Operand,
nq: usize,
order: ByteOrder,
) -> (Number, Number, Number) {
let ualloc = nq + 4;
let scratch = lehmer_ext_itch(nq)
.max(ext_dc_itch(nq))
.max((np - nq + 1) + tdiv_itch(np, nq))
.max(mul_bound(np, ualloc))
.max(divexact_itch(np + ualloc + 1, nq));
let need = (np + 1)
+ (nq + 1)
+ 2 * ualloc
+ nq
+ (ualloc + 1)
+ (np + ualloc + 1)
+ (np + 2)
+ scratch;
working(need, |s| {
unsafe {
let pa = s;
let pb = pa.add(np + 1);
let u0 = pb.add(nq + 1);
let u1 = u0.add(ualloc);
let g = u1.add(ualloc);
let c = g.add(nq);
let prod = c.add(ualloc + 1);
let v = prod.add(np + ualloc + 1);
let t = v.add(np + 2);
to_limbs(p, pa);
to_limbs(q, pb);
if np > nq {
let qn = np - nq + 1;
tdiv_qr(t, pa, np, pb, nq, t.add(qn));
if normalized(pa, nq) == 0 {
let gq = limbs_number(pb, nq, false, order);
return (gq, sign(false, false, order), sign(true, false, order));
}
}
ptr::write_bytes(u0, 0, 2 * ualloc);
*u1 = 1;
let mut row = Row { u0, u1, un: 1 };
let (gn, cn, negative) = gcdext(g, c, pa, pb, nq, &mut row, t, scratch);
let gq = limbs_number(g, gn, false, order);
let cp = limbs_number(c, cn, negative, order);
if cn == 0 {
return (gq, cp, sign(true, false, order));
}
to_limbs(p, pa);
to_limbs(q, pb);
let (long, short) = if np >= cn {
((pa, np), (c, cn))
} else {
((c, cn), (pa, np))
};
let pn = np + cn;
mul(prod, long.0, long.1, short.0, short.1, t);
if negative {
add_to(prod, prod, pn, g, gn);
} else {
sub_from(prod, prod, pn, g, gn);
}
let pn = normalized(prod, pn);
if pn == 0 {
return (gq, cp, sign(false, false, order));
}
divexact(v, prod, pn, pb, nq, t, scratch);
let cq = limbs_number(v, pn - nq + 1, !negative, order);
(gq, cp, cq)
}
})
}
#[inline(never)]
#[track_caller]
fn inverse_general(a: &Number, modulus: &Number) -> Option<Number> {
let order = a.byte_order;
let (x, m) = (Operand::of(a), Operand::of(modulus));
if m.significant == 0 {
by_zero();
}
let nm = m.significant.div_ceil(8);
let nx = x.significant.div_ceil(8);
let ualloc = nm + 4;
let scratch = lehmer_ext_itch(nm)
.max(ext_dc_itch(nm))
.max((nx.max(nm) - nm + 1) + tdiv_itch(nx.max(nm), nm));
let need = (nx.max(nm) + 1) + (nm + 1) + 2 * ualloc + nm + (ualloc + 1) + scratch;
working(need, |s| {
unsafe {
let pa = s;
let pm = pa.add(nx.max(nm) + 1);
let u0 = pm.add(nm + 1);
let u1 = u0.add(ualloc);
let g = u1.add(ualloc);
let c = g.add(nm);
let t = c.add(ualloc + 1);
to_limbs(m, pm);
if nm == 1 && *pm == 1 {
return Some(small_number(order, 0, false));
}
if x.significant == 0 {
return None;
}
to_limbs(x, pa);
let mut na = nx;
if nx >= nm {
let qn = nx - nm + 1;
tdiv_qr(t, pa, nx, pm, nm, t.add(qn));
na = normalized(pa, nm);
if na == 0 {
return None;
}
}
ptr::write_bytes(pa.add(na), 0, nm - na);
ptr::write_bytes(u0, 0, 2 * ualloc);
*u1 = 1;
let mut row = Row { u0, u1, un: 1 };
let (gn, cn, negative) = gcdext(g, c, pa, pm, nm, &mut row, t, scratch);
if gn != 1 || *g != 1 {
return None;
}
let negative = negative != a.negative;
if negative {
to_limbs(m, pm);
sub_from(pm, pm, nm, c, cn);
return Some(limbs_number(pm, nm, false, order));
}
Some(limbs_number(c, cn, false, order))
}
})
}