use super::mul::{mul_full_u32, mul_full_u64, mul_low_u32, mul_low_u64};
use super::*;
use num_traits::{CheckedAdd, CheckedMul, CheckedSub, FromPrimitive, One, ToPrimitive};
struct Lcg(u64);
impl Lcg {
#[inline]
fn u(&mut self) -> u64 {
self.0 = self
.0
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
self.0
}
}
macro_rules! rand_fixed {
($rng:expr, $K:literal, $maxbits:expr) => {{
let bits = ($rng.u() as usize) % ($maxbits + 1); let mut limbs = [0u64; $K];
for l in limbs.iter_mut() {
*l = $rng.u();
}
for i in 0..$K {
let lo = i * 64;
if lo >= bits {
limbs[i] = 0;
} else if lo + 64 > bits {
let keep = bits - lo; limbs[i] &= (1u64 << keep) - 1;
}
}
let mut v = FixedInt::<$K>::from_limbs(limbs);
if $rng.u() & 1 == 1 {
v = -v;
}
v
}};
}
macro_rules! diff_fuzz {
($name:ident, $K:literal, $BT:path) => {
#[test]
fn $name() {
const W: usize = $K * 64;
let to_bnum = |x: &FixedInt<$K>| -> $BT {
let mut buf = [0u8; $K * 8];
for i in 0..$K {
buf[i * 8..i * 8 + 8].copy_from_slice(&x.0[i].to_le_bytes());
}
<$BT>::from_le_slice(&buf).unwrap()
};
let mut min_limbs = [0u64; $K];
min_limbs[$K - 1] = 1u64 << 63;
let mut max_limbs = [u64::MAX; $K];
max_limbs[$K - 1] = i64::MAX as u64; let mut half_limbs = [0u64; $K]; half_limbs[$K / 2] = 1;
let zero = FixedInt::<$K>::from_limbs([0u64; $K]);
let one = <FixedInt<$K> as One>::one();
let min = FixedInt::<$K>::from_limbs(min_limbs);
let max = FixedInt::<$K>::from_limbs(max_limbs);
let half = FixedInt::<$K>::from_limbs(half_limbs);
let mut boundaries: Vec<FixedInt<$K>> = vec![
zero,
one,
-one,
min,
max,
min + one, max - one, half,
-half,
half - one, -(half - one),
<FixedInt<$K> as FromPrimitive>::from_i64(i64::MAX).unwrap(),
<FixedInt<$K> as FromPrimitive>::from_i64(i64::MIN).unwrap(),
<FixedInt<$K> as FromPrimitive>::from_u64(u64::MAX).unwrap(),
<FixedInt<$K> as FromPrimitive>::from_i64(-1).unwrap(),
];
let check_pair = |a: &FixedInt<$K>, b: &FixedInt<$K>| {
let (ba, bb) = (to_bnum(a), to_bnum(b));
assert_eq!(
CheckedAdd::checked_add(a, b).map(|r| to_bnum(&r)),
CheckedAdd::checked_add(&ba, &bb),
"checked_add mismatch a={:?} b={:?}",
a.0,
b.0
);
assert_eq!(
CheckedSub::checked_sub(a, b).map(|r| to_bnum(&r)),
CheckedSub::checked_sub(&ba, &bb),
"checked_sub mismatch a={:?} b={:?}",
a.0,
b.0
);
assert_eq!(
CheckedMul::checked_mul(a, b).map(|r| to_bnum(&r)),
CheckedMul::checked_mul(&ba, &bb),
"checked_mul mismatch a={:?} b={:?}",
a.0,
b.0
);
assert_eq!(
a.cmp(b),
ba.cmp(&bb),
"cmp mismatch a={:?} b={:?}",
a.0,
b.0
);
let is_min = *b == min;
let is_neg_one = *b == -one;
if !b.is_zero() && !(*a == min && is_neg_one) && !is_min {
assert_eq!(
to_bnum(&(*a / *b)),
ba / bb,
"div mismatch a={:?} b={:?}",
a.0,
b.0
);
assert_eq!(
to_bnum(&(*a % *b)),
ba % bb,
"rem mismatch a={:?} b={:?}",
a.0,
b.0
);
}
};
let check_unary = |a: &FixedInt<$K>| {
let ba = to_bnum(a);
assert_eq!(to_bnum(&(-*a)), ba.wrapping_neg(), "neg mismatch {:?}", a.0);
assert_eq!(a.to_i64(), ba.to_i64(), "to_i64 mismatch {:?}", a.0);
assert_eq!(a.to_u64(), ba.to_u64(), "to_u64 mismatch {:?}", a.0);
assert_eq!(a.to_f64(), ba.to_f64(), "to_f64 mismatch {:?}", a.0);
assert_eq!(a.is_zero(), ba.is_zero(), "is_zero mismatch {:?}", a.0);
assert_eq!(a.is_one(), ba.is_one(), "is_one mismatch {:?}", a.0);
assert_eq!(a.is_negative(), ba.is_negative(), "is_neg mismatch {:?}", a.0);
assert_eq!(
!a.is_negative() && !a.is_zero(),
ba.is_positive(),
"is_pos mismatch {:?}",
a.0
);
};
let mut seed = Lcg(0x1234_5678_9abc_def0 ^ (W as u64));
for _ in 0..8 {
boundaries.push(rand_fixed!(seed, $K, W - 1));
}
for a in &boundaries {
check_unary(a);
for b in &boundaries {
check_pair(a, b);
}
}
let mut rng = Lcg(0xdead_beef_0000_0000 ^ (W as u64));
for _ in 0..40_000 {
let a = rand_fixed!(rng, $K, W - 1);
let b = rand_fixed!(rng, $K, W - 1);
check_unary(&a);
check_pair(&a, &b);
let n = rng.u() as i64;
assert_eq!(
to_bnum(&<FixedInt<$K> as FromPrimitive>::from_i64(n).unwrap()),
<$BT as FromPrimitive>::from_i64(n).unwrap(),
"from_i64 mismatch n={n}"
);
let m = rng.u();
assert_eq!(
to_bnum(&<FixedInt<$K> as FromPrimitive>::from_u64(m).unwrap()),
<$BT as FromPrimitive>::from_u64(m).unwrap(),
"from_u64 mismatch m={m}"
);
}
let mut rng2 = Lcg(0x0f0f_0f0f_f0f0_f0f0 ^ (W as u64));
for _ in 0..40_000 {
let ba_bits = (rng2.u() as usize) % (W / 2 + 2) + (W / 2 - 1);
let bb_bits = W - (ba_bits.min(W)); let a = rand_fixed!(rng2, $K, ba_bits.min(W - 1));
let b = rand_fixed!(rng2, $K, (bb_bits + 2).min(W - 1));
let ta = to_bnum(&a);
let tb = to_bnum(&b);
assert_eq!(
CheckedMul::checked_mul(&a, &b).map(|r| to_bnum(&r)),
CheckedMul::checked_mul(&ta, &tb),
"checked_mul boundary mismatch a={:?} b={:?}",
a.0,
b.0
);
}
}
};
}
diff_fuzz!(fuzz_i256, 4, bnum::types::I256);
diff_fuzz!(fuzz_i512, 8, bnum::types::I512);
diff_fuzz!(fuzz_i1024, 16, bnum::types::I1024);
diff_fuzz!(fuzz_i2048, 32, bnum::types::I2048);
#[test]
fn checked_mul_min_boundary() {
let mut min_limbs = [0u64; 4];
min_limbs[3] = 1u64 << 63;
let min = FixedInt::<4>::from_limbs(min_limbs);
let one = <FixedInt<4> as One>::one();
let neg_one = -one;
assert_eq!(CheckedMul::checked_mul(&min, &one), Some(min));
assert_eq!(CheckedMul::checked_mul(&min, &neg_one), None);
let mut h = [0u64; 4];
h[1] = 1u64 << 63; let two127 = FixedInt::<4>::from_limbs(h);
let neg = -two127;
let prod = CheckedMul::checked_mul(&neg, &neg).expect("2^254 fits I256");
let mut expect = [0u64; 4];
expect[3] = 1u64 << 62; assert_eq!(prod, FixedInt::<4>::from_limbs(expect));
let mut a = [0u64; 4];
a[2] = 1; let two128 = FixedInt::<4>::from_limbs(a);
let prod2 = CheckedMul::checked_mul(&(-two128), &two127).expect("-2^255 = MIN fits");
assert_eq!(prod2, min);
assert_eq!(CheckedMul::checked_mul(&two128, &two127), None);
}
macro_rules! digit_width_fuzz {
($name:ident, $K:literal) => {
#[test]
fn $name() {
let mut patterns: Vec<[u64; $K]> = vec![[0u64; $K], [u64::MAX; $K]];
for bit in (0..$K * 64).step_by(7) {
let mut l = [0u64; $K];
l[bit / 64] = 1u64 << (bit % 64);
patterns.push(l);
}
for k in 0..$K {
let mut lo = [0u64; $K];
for slot in lo.iter_mut().take(k + 1) {
*slot = u64::MAX;
}
patterns.push(lo);
let mut hi = [0u64; $K];
for slot in hi.iter_mut().skip($K - 1 - k) {
*slot = u64::MAX;
}
patterns.push(hi);
}
let mut rng = Lcg(0xabcd_ef01_2345_6789 ^ ($K as u64));
for _ in 0..1_500 {
let mut l = [0u64; $K];
for slot in l.iter_mut() {
*slot = rng.u();
}
let cut = (rng.u() as usize) % ($K * 64 + 1);
for i in 0..$K {
let lo = i * 64;
if lo >= cut {
l[i] = 0;
} else if lo + 64 > cut {
l[i] &= (1u64 << (cut - lo)) - 1;
}
}
patterns.push(l);
}
for a in &patterns {
for b in patterns.iter().take(48) {
assert_eq!(
mul_low_u32(a, b),
mul_low_u64(a, b),
"mul_low digit-width mismatch a={:?} b={:?}",
a,
b
);
let mut full32 = [0u64; 64];
let mut full64 = [0u64; 64];
mul_full_u32(a, b, &mut full32);
mul_full_u64(a, b, &mut full64);
assert_eq!(
full32, full64,
"mul_full digit-width mismatch a={:?} b={:?}",
a, b
);
}
}
}
};
}
digit_width_fuzz!(digit_width_i256, 4);
digit_width_fuzz!(digit_width_i512, 8);
digit_width_fuzz!(digit_width_i1024, 16);
digit_width_fuzz!(digit_width_i2048, 32);