use core::cmp::Ordering;
use core::ops::{BitAnd, BitOr, BitXor, Shl, ShlAssign, Shr, ShrAssign};
pub const KARATSUBA_THRESHOLD: usize = 32;
pub(crate) const TOOM3_THRESHOLD: usize = 100;
#[derive(Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct BigUint {
pub(crate) limbs: Vec<u64>,
}
impl BigUint {
pub const ZERO: BigUint = BigUint { limbs: Vec::new() };
#[inline]
pub fn zero() -> Self {
Self { limbs: Vec::new() }
}
#[inline]
pub fn one() -> Self {
Self { limbs: vec![1] }
}
#[inline]
pub fn from_u64(value: u64) -> Self {
if value == 0 {
Self::zero()
} else {
Self { limbs: vec![value] }
}
}
#[inline]
pub fn from_u128(value: u128) -> Self {
let lo = value as u64;
let hi = (value >> 64) as u64;
if hi == 0 {
Self::from_u64(lo)
} else {
Self {
limbs: vec![lo, hi],
}
}
}
pub fn from_le_limbs(limbs: &[u64]) -> Self {
let mut v = limbs.to_vec();
normalize(&mut v);
v.shrink_to_fit();
Self { limbs: v }
}
pub fn from_le_limbs_with_capacity(limbs: &[u64], extra_capacity: usize) -> Self {
let sig_len = limbs
.iter()
.rposition(|&x| x != 0)
.map(|p| p + 1)
.unwrap_or(0);
let mut v = Vec::with_capacity(sig_len + extra_capacity);
v.extend_from_slice(&limbs[..sig_len]);
Self { limbs: v }
}
#[inline]
pub fn compact(&mut self) {
self.limbs.shrink_to_fit();
}
#[inline]
pub fn as_limbs(&self) -> &[u64] {
&self.limbs
}
#[inline]
pub fn is_zero(&self) -> bool {
self.limbs.is_empty()
}
#[inline]
pub fn is_one(&self) -> bool {
self.limbs.as_slice() == [1u64]
}
#[inline]
pub fn to_u64(&self) -> Option<u64> {
match self.limbs.len() {
0 => Some(0),
1 => Some(self.limbs[0]),
_ => None,
}
}
pub fn bit_length(&self) -> u64 {
match self.limbs.last() {
None => 0,
Some(&top) => {
let n_limbs = self.limbs.len() as u64;
(n_limbs - 1) * 64 + (64 - top.leading_zeros() as u64)
}
}
}
pub fn trailing_zeros(&self) -> u64 {
for (i, &limb) in self.limbs.iter().enumerate() {
if limb != 0 {
return (i as u64) * 64 + (limb.trailing_zeros() as u64);
}
}
0
}
pub fn count_ones(&self) -> u64 {
self.limbs.iter().map(|l| l.count_ones() as u64).sum()
}
pub fn test_bit(&self, index: u64) -> bool {
let limb_idx = (index / 64) as usize;
let bit_idx = index % 64;
if limb_idx >= self.limbs.len() {
return false;
}
(self.limbs[limb_idx] >> bit_idx) & 1 == 1
}
pub fn set_bit(&mut self, index: u64) {
let limb_idx = (index / 64) as usize;
let bit_idx = index % 64;
if limb_idx >= self.limbs.len() {
self.limbs.resize(limb_idx + 1, 0);
}
self.limbs[limb_idx] |= 1u64 << bit_idx;
normalize(&mut self.limbs);
}
pub fn clear_bit(&mut self, index: u64) {
let limb_idx = (index / 64) as usize;
let bit_idx = index % 64;
if limb_idx >= self.limbs.len() {
return;
}
self.limbs[limb_idx] &= !(1u64 << bit_idx);
normalize(&mut self.limbs);
}
pub fn from_bytes_be(bytes: &[u8]) -> Self {
let mut le = bytes.to_vec();
le.reverse();
Self::from_bytes_le(&le)
}
pub fn from_bytes_le(bytes: &[u8]) -> Self {
let n_limbs = bytes.len().div_ceil(8);
let mut limbs = Vec::with_capacity(n_limbs);
for chunk in bytes.chunks(8) {
let mut buf = [0u8; 8];
buf[..chunk.len()].copy_from_slice(chunk);
limbs.push(u64::from_le_bytes(buf));
}
normalize(&mut limbs);
Self { limbs }
}
pub fn to_bytes_be(&self) -> Vec<u8> {
let mut le = self.to_bytes_le();
le.reverse();
le
}
pub fn to_bytes_le(&self) -> Vec<u8> {
if self.limbs.is_empty() {
return Vec::new();
}
let mut out: Vec<u8> = Vec::with_capacity(self.limbs.len() * 8);
for &limb in &self.limbs {
out.extend_from_slice(&limb.to_le_bytes());
}
while out.last() == Some(&0) {
out.pop();
}
out
}
pub(crate) fn add_ref(a: &BigUint, b: &BigUint) -> BigUint {
let (long, short) = if a.limbs.len() >= b.limbs.len() {
(&a.limbs, &b.limbs)
} else {
(&b.limbs, &a.limbs)
};
let mut out: Vec<u64> = Vec::with_capacity(long.len() + 1);
let mut carry: u64 = 0;
for (&lv, &sv) in long[..short.len()].iter().zip(short.iter()) {
let (s1, c1) = lv.overflowing_add(sv);
let (s2, c2) = s1.overflowing_add(carry);
out.push(s2);
carry = (c1 as u64) | (c2 as u64);
}
for &lv in &long[short.len()..] {
let (s, c) = lv.overflowing_add(carry);
out.push(s);
carry = c as u64;
if carry == 0 {
out.extend_from_slice(&long[out.len()..]);
normalize(&mut out);
return BigUint { limbs: out };
}
}
if carry != 0 {
out.push(carry);
}
normalize(&mut out);
BigUint { limbs: out }
}
pub fn checked_sub(&self, other: &BigUint) -> Option<BigUint> {
if self.cmp(other) == Ordering::Less {
return None;
}
let mut out: Vec<u64> = Vec::with_capacity(self.limbs.len());
let mut borrow: u64 = 0;
for (&av, &bv) in self.limbs[..other.limbs.len()]
.iter()
.zip(other.limbs.iter())
{
let (d1, b1) = av.overflowing_sub(bv);
let (d2, b2) = d1.overflowing_sub(borrow);
out.push(d2);
borrow = (b1 as u64) | (b2 as u64);
}
for &av in &self.limbs[other.limbs.len()..] {
let (d, b) = av.overflowing_sub(borrow);
out.push(d);
borrow = b as u64;
if borrow == 0 {
out.extend_from_slice(&self.limbs[out.len()..]);
normalize(&mut out);
return Some(BigUint { limbs: out });
}
}
debug_assert_eq!(borrow, 0, "checked_sub underflow despite cmp guard");
normalize(&mut out);
Some(BigUint { limbs: out })
}
pub fn shl_bits(&self, n: u64) -> BigUint {
if self.is_zero() || n == 0 {
return self.clone();
}
let limb_offset = (n / 64) as usize;
let bit_offset = (n % 64) as u32;
let capacity = limb_offset + self.limbs.len() + if bit_offset != 0 { 1 } else { 0 };
let mut out: Vec<u64> = Vec::with_capacity(capacity);
out.resize(limb_offset, 0u64);
if bit_offset == 0 {
out.extend_from_slice(&self.limbs);
} else {
out.extend(super::simd_ops::shl_within(&self.limbs, bit_offset));
}
normalize(&mut out);
BigUint { limbs: out }
}
pub fn pow(&self, exp: u32) -> BigUint {
if exp == 0 {
return BigUint::one();
}
if self.is_zero() {
return BigUint::zero();
}
if self.is_one() {
return BigUint::one();
}
let mut base = self.clone();
let mut result = BigUint::one();
let mut e = exp;
while e > 0 {
if e & 1 == 1 {
result = &result * &base;
}
e >>= 1;
if e > 0 {
base = &base * &base;
}
}
result
}
pub fn shr_bits(&self, n: u64) -> BigUint {
if self.is_zero() || n == 0 {
return self.clone();
}
let limb_offset = (n / 64) as usize;
let bit_offset = (n % 64) as u32;
if limb_offset >= self.limbs.len() {
return BigUint::zero();
}
let remaining = &self.limbs[limb_offset..];
let mut out: Vec<u64> = Vec::with_capacity(remaining.len());
if bit_offset == 0 {
out.extend_from_slice(remaining);
} else {
out.extend(super::simd_ops::shr_within(remaining, bit_offset));
}
normalize(&mut out);
BigUint { limbs: out }
}
}
#[inline]
pub(crate) fn normalize(limbs: &mut Vec<u64>) {
let new_len = limbs
.iter()
.rposition(|&x| x != 0)
.map(|p| p + 1)
.unwrap_or(0);
limbs.truncate(new_len);
}
impl PartialEq for BigUint {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.limbs == other.limbs
}
}
impl Eq for BigUint {}
impl std::hash::Hash for BigUint {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.limbs.hash(state);
}
}
impl Ord for BigUint {
fn cmp(&self, other: &Self) -> Ordering {
match self.limbs.len().cmp(&other.limbs.len()) {
Ordering::Equal => {
for (a, b) in self.limbs.iter().rev().zip(other.limbs.iter().rev()) {
match a.cmp(b) {
Ordering::Equal => continue,
non_eq => return non_eq,
}
}
Ordering::Equal
}
non_eq => non_eq,
}
}
}
impl PartialOrd for BigUint {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Shl<u64> for BigUint {
type Output = BigUint;
#[inline]
fn shl(self, n: u64) -> BigUint {
self.shl_bits(n)
}
}
impl Shl<u64> for &BigUint {
type Output = BigUint;
#[inline]
fn shl(self, n: u64) -> BigUint {
self.shl_bits(n)
}
}
impl Shr<u64> for BigUint {
type Output = BigUint;
#[inline]
fn shr(self, n: u64) -> BigUint {
self.shr_bits(n)
}
}
impl Shr<u64> for &BigUint {
type Output = BigUint;
#[inline]
fn shr(self, n: u64) -> BigUint {
self.shr_bits(n)
}
}
impl ShlAssign<u64> for BigUint {
#[inline]
fn shl_assign(&mut self, n: u64) {
*self = self.shl_bits(n);
}
}
impl ShrAssign<u64> for BigUint {
#[inline]
fn shr_assign(&mut self, n: u64) {
*self = self.shr_bits(n);
}
}
impl BitAnd<&BigUint> for &BigUint {
type Output = BigUint;
#[inline]
fn bitand(self, rhs: &BigUint) -> BigUint {
BigUint {
limbs: super::simd_ops::and_limbs(&self.limbs, &rhs.limbs),
}
}
}
impl BitAnd<BigUint> for BigUint {
type Output = BigUint;
#[inline]
fn bitand(self, rhs: BigUint) -> BigUint {
(&self).bitand(&rhs)
}
}
impl BitAnd<&BigUint> for BigUint {
type Output = BigUint;
#[inline]
fn bitand(self, rhs: &BigUint) -> BigUint {
(&self).bitand(rhs)
}
}
impl BitAnd<BigUint> for &BigUint {
type Output = BigUint;
#[inline]
fn bitand(self, rhs: BigUint) -> BigUint {
self.bitand(&rhs)
}
}
impl BitOr<&BigUint> for &BigUint {
type Output = BigUint;
#[inline]
fn bitor(self, rhs: &BigUint) -> BigUint {
BigUint {
limbs: super::simd_ops::or_limbs(&self.limbs, &rhs.limbs),
}
}
}
impl BitOr<BigUint> for BigUint {
type Output = BigUint;
#[inline]
fn bitor(self, rhs: BigUint) -> BigUint {
(&self).bitor(&rhs)
}
}
impl BitOr<&BigUint> for BigUint {
type Output = BigUint;
#[inline]
fn bitor(self, rhs: &BigUint) -> BigUint {
(&self).bitor(rhs)
}
}
impl BitOr<BigUint> for &BigUint {
type Output = BigUint;
#[inline]
fn bitor(self, rhs: BigUint) -> BigUint {
self.bitor(&rhs)
}
}
impl BitXor<&BigUint> for &BigUint {
type Output = BigUint;
#[inline]
fn bitxor(self, rhs: &BigUint) -> BigUint {
BigUint {
limbs: super::simd_ops::xor_limbs(&self.limbs, &rhs.limbs),
}
}
}
impl BitXor<BigUint> for BigUint {
type Output = BigUint;
#[inline]
fn bitxor(self, rhs: BigUint) -> BigUint {
(&self).bitxor(&rhs)
}
}
impl BitXor<&BigUint> for BigUint {
type Output = BigUint;
#[inline]
fn bitxor(self, rhs: &BigUint) -> BigUint {
(&self).bitxor(rhs)
}
}
impl BitXor<BigUint> for &BigUint {
type Output = BigUint;
#[inline]
fn bitxor(self, rhs: BigUint) -> BigUint {
self.bitxor(&rhs)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_is_canonical() {
let z = BigUint::zero();
assert!(z.is_zero());
assert_eq!(z.limbs.len(), 0);
assert_eq!(z.bit_length(), 0);
}
#[test]
fn normalize_strips_trailing_zeros() {
let n = BigUint::from_le_limbs(&[5, 0, 0]);
assert_eq!(n.as_limbs(), &[5u64]);
}
#[test]
fn from_u128_high_limb() {
let n = BigUint::from_u128(u128::MAX);
assert_eq!(n.as_limbs(), &[u64::MAX, u64::MAX]);
assert_eq!(n.bit_length(), 128);
}
#[test]
fn add_with_carry_chain() {
let a = BigUint::from_u64(u64::MAX);
let b = BigUint::from_u64(1);
let s = &a + &b;
assert_eq!(s.as_limbs(), &[0u64, 1u64]);
}
#[test]
fn checked_sub_underflow_returns_none() {
let a = BigUint::from_u64(5);
let b = BigUint::from_u64(10);
assert!(a.checked_sub(&b).is_none());
}
#[test]
fn checked_sub_basic() {
let a = BigUint::from_u64(100);
let b = BigUint::from_u64(40);
assert_eq!(a.checked_sub(&b), Some(BigUint::from_u64(60)));
}
#[test]
fn checked_sub_borrow_chain() {
let a = BigUint::from_le_limbs(&[0, 1]); let b = BigUint::from_u64(1);
let d = a.checked_sub(&b).expect("non-underflow");
assert_eq!(d.as_limbs(), &[u64::MAX]);
}
#[test]
fn shl_within_limb() {
let n = BigUint::from_u64(1);
assert_eq!(n.shl_bits(5), BigUint::from_u64(32));
}
#[test]
fn shl_crosses_limb_boundary() {
let n = BigUint::from_u64(1);
let s = n.shl_bits(64);
assert_eq!(s.as_limbs(), &[0u64, 1u64]);
}
#[test]
fn shl_by_zero_is_identity() {
let n = BigUint::from_u64(42);
assert_eq!(n.shl_bits(0), n);
}
#[test]
fn shr_within_limb() {
let n = BigUint::from_u64(0b1010_0000);
assert_eq!(n.shr_bits(4), BigUint::from_u64(0b1010));
}
#[test]
fn shr_crosses_limb_boundary() {
let n = BigUint::from_le_limbs(&[0, 1]); let s = n.shr_bits(64);
assert_eq!(s, BigUint::from_u64(1));
}
#[test]
fn shl_then_shr_is_identity() {
let n = BigUint::from_u64(0xDEAD_BEEF_CAFE_BABE);
for k in [0u64, 1, 32, 63, 64, 65, 100, 256] {
let r = n.shl_bits(k).shr_bits(k);
assert_eq!(r, n, "shl/shr identity failed for k={k}");
}
}
#[test]
fn cmp_by_length() {
let small = BigUint::from_u64(u64::MAX);
let big = BigUint::from_le_limbs(&[0, 1]); assert!(big > small);
}
#[test]
fn cmp_same_length_msb_first() {
let a = BigUint::from_le_limbs(&[1, 2]);
let b = BigUint::from_le_limbs(&[100, 1]);
assert!(a > b);
}
#[test]
fn bit_length_basic() {
assert_eq!(BigUint::zero().bit_length(), 0);
assert_eq!(BigUint::from_u64(1).bit_length(), 1);
assert_eq!(BigUint::from_u64(0xFF).bit_length(), 8);
assert_eq!(BigUint::from_u64(u64::MAX).bit_length(), 64);
}
#[test]
fn trailing_zeros_basic() {
assert_eq!(BigUint::from_u64(0b1000).trailing_zeros(), 3);
assert_eq!(BigUint::from_le_limbs(&[0, 1]).trailing_zeros(), 64);
assert_eq!(BigUint::zero().trailing_zeros(), 0);
}
#[test]
fn count_ones_basic() {
assert_eq!(BigUint::zero().count_ones(), 0);
assert_eq!(BigUint::from_u64(0b1011).count_ones(), 3);
let n = BigUint::from_le_limbs(&[1, 0xF]);
assert_eq!(n.count_ones(), 5);
}
#[test]
fn test_set_clear_bit() {
let mut n = BigUint::zero();
n.set_bit(100);
assert!(n.test_bit(100));
assert_eq!(n.bit_length(), 101);
n.clear_bit(100);
assert!(n.is_zero());
}
#[test]
fn bytes_roundtrip_le() {
let n = BigUint::from_u64(0xDEAD_BEEF_CAFE_BABE);
let bytes = n.to_bytes_le();
let m = BigUint::from_bytes_le(&bytes);
assert_eq!(m, n);
}
#[test]
fn bytes_roundtrip_be() {
let n = BigUint::from_u64(0xDEAD_BEEF_CAFE_BABE);
let bytes = n.to_bytes_be();
let m = BigUint::from_bytes_be(&bytes);
assert_eq!(m, n);
}
#[test]
fn bitand_or_xor() {
let a = BigUint::from_u64(0b1100);
let b = BigUint::from_u64(0b1010);
assert_eq!(&a & &b, BigUint::from_u64(0b1000));
assert_eq!(&a | &b, BigUint::from_u64(0b1110));
assert_eq!(&a ^ &b, BigUint::from_u64(0b0110));
}
#[test]
fn normalize_bulk_truncate_correctness() {
let mut v: Vec<u64> = vec![1, 2, 3, 0, 0, 0];
normalize(&mut v);
assert_eq!(v, vec![1u64, 2, 3]);
let mut z: Vec<u64> = vec![0, 0, 0];
normalize(&mut z);
assert!(z.is_empty());
let mut s: Vec<u64> = vec![42];
normalize(&mut s);
assert_eq!(s, vec![42u64]);
}
#[test]
fn add_two_pass_carries_correctly() {
let a = BigUint::from_le_limbs(&[u64::MAX, u64::MAX]);
let b = BigUint::from_u64(1);
let sum = &a + &b;
assert_eq!(sum.as_limbs(), &[0u64, 0, 1]);
}
#[test]
fn add_fast_path_tail_copy() {
let a = BigUint::from_le_limbs(&[1, 2, 3, 4, 5]);
let b = BigUint::from_le_limbs(&[u64::MAX - 1, 0]);
let expected = BigUint::from_le_limbs(&[u64::MAX, 2, 3, 4, 5]);
assert_eq!(&a + &b, expected);
}
#[test]
fn checked_sub_two_pass_borrows_correctly() {
let a = BigUint::from_le_limbs(&[0, 0, 1]); let b = BigUint::from_u64(1);
let diff = a.checked_sub(&b).expect("no underflow");
assert_eq!(diff.as_limbs(), &[u64::MAX, u64::MAX]);
}
#[test]
fn checked_sub_fast_path_tail_copy() {
let a = BigUint::from_le_limbs(&[5, 100, 100, 100]);
let b = BigUint::from_le_limbs(&[3, 100]);
let expected = BigUint::from_le_limbs(&[2, 0, 100, 100]);
assert_eq!(a.checked_sub(&b).expect("no underflow"), expected);
}
#[test]
fn from_le_limbs_with_capacity_value_correct() {
let n = BigUint::from_le_limbs_with_capacity(&[42, 0, 0], 4);
assert_eq!(n, BigUint::from_u64(42));
}
#[test]
fn from_le_limbs_with_capacity_zero_input() {
let n = BigUint::from_le_limbs_with_capacity(&[0, 0], 8);
assert!(n.is_zero());
}
#[test]
fn from_le_limbs_with_capacity_multidigit() {
let n = BigUint::from_le_limbs_with_capacity(&[1, 2, 3], 2);
assert_eq!(n.as_limbs(), &[1u64, 2, 3]);
}
#[test]
fn compact_preserves_value() {
let mut n = BigUint::from_le_limbs_with_capacity(&[0xDEAD, 0xBEEF], 100);
let before = n.clone();
n.compact();
assert_eq!(n, before, "compact() must not change value");
}
#[test]
fn shl_bits_pre_alloc_no_realloc() {
let n = BigUint::from_u64(1);
let s = n.shl_bits(127);
assert!(s.test_bit(127));
assert_eq!(s.bit_length(), 128);
}
}