diff --git a/Cargo.toml b/Cargo.toml index fe15f05e..f651aa78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" build = "build.rs" [package.metadata.docs.rs] -features = ["std", "serde", "rand"] +features = ["std", "serde", "rand", "quickcheck"] [[bench]] name = "bigint" @@ -53,6 +53,16 @@ version = "1.0" default-features = false features = ["std"] +[dependencies.quickcheck] +optional = true +version = "0.8" +default-features = false + +[dependencies.quickcheck_macros] +optional = true +version = "0.8" +default-features = false + [dev-dependencies.serde_test] version = "1.0" diff --git a/ci/test_full.sh b/ci/test_full.sh index 3021b040..4e1b60e9 100755 --- a/ci/test_full.sh +++ b/ci/test_full.sh @@ -5,12 +5,15 @@ set -ex echo Testing num-bigint on rustc ${TRAVIS_RUST_VERSION} FEATURES="serde" -if [[ "$TRAVIS_RUST_VERSION" =~ ^(nightly|beta|stable|1.26.0|1.22.0)$ ]]; then +if [[ "$TRAVIS_RUST_VERSION" =~ ^(nightly|beta|stable|1.31.0|1.26.0|1.22.0)$ ]]; then FEATURES="$FEATURES rand" fi -if [[ "$TRAVIS_RUST_VERSION" =~ ^(nightly|beta|stable|1.26.0)$ ]]; then +if [[ "$TRAVIS_RUST_VERSION" =~ ^(nightly|beta|stable|1.31.0|1.26.0)$ ]]; then FEATURES="$FEATURES i128" fi +if [[ "$TRAVIS_RUST_VERSION" =~ ^(nightly|beta|stable|1.31.0)$ ]]; then + FEATURES="$FEATURES quickcheck quickcheck_macros" +fi # num-bigint should build and test everywhere. cargo build --verbose diff --git a/src/bigint.rs b/src/bigint.rs index 43bdfeed..19705bc1 100644 --- a/src/bigint.rs +++ b/src/bigint.rs @@ -34,6 +34,9 @@ use biguint::{BigUint, IntDigits}; use IsizePromotion; use UsizePromotion; +#[cfg(feature = "quickcheck")] +use quickcheck::{Arbitrary, Gen}; + /// A Sign is a `BigInt`'s composing element. #[derive(PartialEq, PartialOrd, Eq, Ord, Copy, Clone, Debug, Hash)] pub enum Sign { @@ -114,6 +117,21 @@ pub struct BigInt { data: BigUint, } +#[cfg(feature = "quickcheck")] +impl Arbitrary for BigInt { + fn arbitrary(g: &mut G) -> Self { + let positive = bool::arbitrary(g); + let sign = if positive { Sign::Plus } else { Sign::Minus }; + Self::from_biguint(sign, BigUint::arbitrary(g)) + } + + fn shrink(&self) -> Box> { + let sign = self.sign(); + let unsigned_shrink = self.data.shrink(); + Box::new(unsigned_shrink.map(move |x| BigInt::from_biguint(sign, x))) + } +} + /// Return the magnitude of a `BigInt`. /// /// This is in a private module, pseudo pub(crate) diff --git a/src/biguint.rs b/src/biguint.rs index f1306fef..da7fb1d1 100644 --- a/src/biguint.rs +++ b/src/biguint.rs @@ -41,12 +41,28 @@ use UsizePromotion; use ParseBigIntError; +#[cfg(feature = "quickcheck")] +use quickcheck::{Arbitrary, Gen}; + /// A big unsigned integer type. #[derive(Clone, Debug, Hash)] pub struct BigUint { data: Vec, } +#[cfg(feature = "quickcheck")] +impl Arbitrary for BigUint { + fn arbitrary(g: &mut G) -> Self { + // Use arbitrary from Vec + Self::new(Vec::::arbitrary(g)) + } + + fn shrink(&self) -> Box> { + // Use shrinker from Vec + Box::new(self.data.shrink().map(|x| BigUint::new(x))) + } +} + impl PartialEq for BigUint { #[inline] fn eq(&self, other: &BigUint) -> bool { diff --git a/src/lib.rs b/src/lib.rs index 5503bfcc..2408c8eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,6 +87,8 @@ extern crate serde; extern crate num_integer as integer; extern crate num_traits as traits; +#[cfg(feature = "quickcheck")] +extern crate quickcheck; use std::error::Error; use std::fmt; diff --git a/tests/quickcheck.rs b/tests/quickcheck.rs new file mode 100644 index 00000000..6bb251fa --- /dev/null +++ b/tests/quickcheck.rs @@ -0,0 +1,317 @@ +#![cfg(feature = "quickcheck")] +#![cfg(feature = "quickcheck_macros")] + +extern crate num_bigint; +extern crate num_integer; +extern crate num_traits; + +extern crate quickcheck; +#[macro_use] +extern crate quickcheck_macros; + +use num_bigint::{BigInt, BigUint}; +use num_traits::{Num, One, Pow, Zero}; +use quickcheck::{QuickCheck, StdThreadGen, TestResult}; + +#[quickcheck] +fn quickcheck_unsigned_eq_reflexive(a: BigUint) -> bool { + a == a +} + +#[quickcheck] +fn quickcheck_signed_eq_reflexive(a: BigInt) -> bool { + a == a +} + +#[quickcheck] +fn quickcheck_unsigned_eq_symmetric(a: BigUint, b: BigUint) -> bool { + if a == b { + b == a + } else { + b != a + } +} + +#[quickcheck] +fn quickcheck_signed_eq_symmetric(a: BigInt, b: BigInt) -> bool { + if a == b { + b == a + } else { + b != a + } +} + +#[test] +fn quickcheck_arith_primitive() { + let gen = StdThreadGen::new(usize::max_value()); + let mut qc = QuickCheck::with_gen(gen); + + fn test_unsigned_add_primitive(a: usize, b: usize) -> TestResult { + let actual = BigUint::from(a) + BigUint::from(b); + match a.checked_add(b) { + None => TestResult::discard(), + Some(expected) => TestResult::from_bool(BigUint::from(expected) == actual), + } + } + + fn test_signed_add_primitive(a: isize, b: isize) -> TestResult { + let actual = BigInt::from(a) + BigInt::from(b); + match a.checked_add(b) { + None => TestResult::discard(), + Some(expected) => TestResult::from_bool(BigInt::from(expected) == actual), + } + } + + fn test_unsigned_mul_primitive(a: u64, b: u64) -> bool { + //maximum value of u64 means no overflow + BigUint::from(a as u128 * b as u128) == BigUint::from(a) * BigUint::from(b) + } + + fn test_signed_mul_primitive(a: i64, b: i64) -> bool { + //maximum value of i64 means no overflow + BigInt::from(a as i128 * b as i128) == BigInt::from(a) * BigInt::from(b) + } + + fn test_unsigned_sub_primitive(a: u128, b: u128) -> bool { + if b < a { + BigUint::from(a - b) == BigUint::from(a) - BigUint::from(b) + } else { + BigUint::from(b - a) == BigUint::from(b) - BigUint::from(a) + } + } + + fn test_signed_sub_primitive(a: i128, b: i128) -> bool { + if b < a { + BigInt::from(a - b) == BigInt::from(a) - BigInt::from(b) + } else { + BigInt::from(b - a) == BigInt::from(b) - BigInt::from(a) + } + } + + fn test_unsigned_div_primitive(a: u128, b: u128) -> TestResult { + if b == 0 { + TestResult::discard() + } else { + TestResult::from_bool(BigUint::from(a / b) == BigUint::from(a) / BigUint::from(b)) + } + } + + fn test_signed_div_primitive(a: i128, b: i128) -> TestResult { + if b == 0 { + TestResult::discard() + } else { + TestResult::from_bool(BigInt::from(a / b) == BigInt::from(a) / BigInt::from(b)) + } + } + + qc.quickcheck(test_unsigned_add_primitive as fn(usize, usize) -> TestResult); + qc.quickcheck(test_signed_add_primitive as fn(isize, isize) -> TestResult); + qc.quickcheck(test_unsigned_mul_primitive as fn(u64, u64) -> bool); + qc.quickcheck(test_signed_mul_primitive as fn(i64, i64) -> bool); + qc.quickcheck(test_unsigned_sub_primitive as fn(u128, u128) -> bool); + qc.quickcheck(test_signed_sub_primitive as fn(i128, i128) -> bool); + qc.quickcheck(test_unsigned_div_primitive as fn(u128, u128) -> TestResult); + qc.quickcheck(test_signed_div_primitive as fn(i128, i128) -> TestResult); +} + +#[quickcheck] +fn quickcheck_unsigned_add_commutative(a: BigUint, b: BigUint) -> bool { + &a + &b == b + a +} + +#[quickcheck] +fn quickcheck_signed_add_commutative(a: BigInt, b: BigInt) -> bool { + &a + &b == b + a +} + +#[quickcheck] +fn quickcheck_unsigned_add_zero(a: BigUint) -> bool { + a == &a + BigUint::zero() +} + +#[quickcheck] +fn quickcheck_signed_add_zero(a: BigInt) -> bool { + a == &a + BigInt::zero() +} + +#[quickcheck] +fn quickcheck_unsigned_add_associative(a: BigUint, b: BigUint, c: BigUint) -> bool { + (&a + &b) + &c == a + (b + c) +} + +#[quickcheck] +fn quickcheck_signed_add_associative(a: BigInt, b: BigInt, c: BigInt) -> bool { + (&a + &b) + &c == a + (b + c) +} + +#[quickcheck] +fn quickcheck_unsigned_mul_zero(a: BigUint) -> bool { + a * BigUint::zero() == BigUint::zero() +} + +#[quickcheck] +fn quickcheck_signed_mul_zero(a: BigInt) -> bool { + a * BigInt::zero() == BigInt::zero() +} + +#[quickcheck] +fn quickcheck_unsigned_mul_one(a: BigUint) -> bool { + &a * BigUint::one() == a +} + +#[quickcheck] +fn quickcheck_signed_mul_one(a: BigInt) -> bool { + &a * BigInt::one() == a +} + +#[quickcheck] +fn quickcheck_unsigned_mul_commutative(a: BigUint, b: BigUint) -> bool { + &a * &b == b * a +} + +#[quickcheck] +fn quickcheck_signed_mul_commutative(a: BigInt, b: BigInt) -> bool { + &a * &b == b * a +} + +#[quickcheck] +fn quickcheck_unsigned_mul_associative(a: BigUint, b: BigUint, c: BigUint) -> bool { + (&a * &b) * &c == a * (b * c) +} + +#[quickcheck] +fn quickcheck_signed_mul_associative(a: BigInt, b: BigInt, c: BigInt) -> bool { + (&a * &b) * &c == a * (b * c) +} + +#[quickcheck] +fn quickcheck_unsigned_distributive(a: BigUint, b: BigUint, c: BigUint) -> bool { + &a * (&b + &c) == &a * b + a * c +} + +#[quickcheck] +fn quickcheck_signed_distributive(a: BigInt, b: BigInt, c: BigInt) -> bool { + &a * (&b + &c) == &a * b + a * c +} + +#[quickcheck] +///Tests that exactly one of ab a=b is true +fn quickcheck_unsigned_ge_le_eq_mut_exclusive(a: BigUint, b: BigUint) -> bool { + let gt_lt_eq = vec![a > b, a < b, a == b]; + gt_lt_eq + .iter() + .fold(0, |acc, e| if *e { acc + 1 } else { acc }) + == 1 +} + +#[quickcheck] +///Tests that exactly one of ab a=b is true +fn quickcheck_signed_ge_le_eq_mut_exclusive(a: BigInt, b: BigInt) -> bool { + let gt_lt_eq = vec![a > b, a < b, a == b]; + gt_lt_eq + .iter() + .fold(0, |acc, e| if *e { acc + 1 } else { acc }) + == 1 +} + +#[quickcheck] +/// Tests correctness of subtraction assuming addition is correct +fn quickcheck_unsigned_sub(a: BigUint, b: BigUint) -> bool { + if b < a { + &a - &b + b == a + } else { + &b - &a + a == b + } +} + +#[quickcheck] +/// Tests correctness of subtraction assuming addition is correct +fn quickcheck_signed_sub(a: BigInt, b: BigInt) -> bool { + if b < a { + &a - &b + b == a + } else { + &b - &a + a == b + } +} + +#[quickcheck] +fn quickcheck_unsigned_pow_zero(a: BigUint) -> bool { + a.pow(0_u32) == BigUint::one() +} + +#[quickcheck] +fn quickcheck_unsigned_pow_one(a: BigUint) -> bool { + a.pow(1_u32) == a +} + +#[quickcheck] +fn quickcheck_unsigned_sqrt(a: BigUint) -> bool { + (&a * &a).sqrt() == a +} + +#[quickcheck] +fn quickcheck_unsigned_cbrt(a: BigUint) -> bool { + (&a * &a * &a).cbrt() == a +} + +#[quickcheck] +fn quickcheck_signed_cbrt(a: BigInt) -> bool { + (&a * &a * &a).cbrt() == a +} + +#[quickcheck] +fn quickcheck_unsigned_conversion(a: BigUint, radix: u8) -> TestResult { + let radix = radix as u32; + if radix > 36 || radix < 2 { + return TestResult::discard(); + } + let string = a.to_str_radix(radix); + TestResult::from_bool(a == BigUint::from_str_radix(&string, radix).unwrap()) +} + +#[quickcheck] +fn quickcheck_signed_conversion(a: BigInt, radix: u8) -> TestResult { + let radix = radix as u32; + if radix > 36 || radix < 2 { + return TestResult::discard(); + } + let string = a.to_str_radix(radix); + TestResult::from_bool(a == BigInt::from_str_radix(&string, radix).unwrap()) +} + +#[test] +fn quicktest_shift() { + let gen = StdThreadGen::new(usize::max_value()); + let mut qc = QuickCheck::with_gen(gen); + + fn test_shr_unsigned(a: u64, shift: u8) -> TestResult { + let shift = (shift % 64) as usize; //shift at most 64 bits + let big_a = BigUint::from(a); + TestResult::from_bool(BigUint::from(a >> shift) == big_a >> shift) + } + + fn test_shr_signed(a: i64, shift: u8) -> TestResult { + let shift = (shift % 64) as usize; //shift at most 64 bits + let big_a = BigInt::from(a); + TestResult::from_bool(BigInt::from(a >> shift) == big_a >> shift) + } + + fn test_shl_unsigned(a: u32, shift: u8) -> TestResult { + let shift = (shift % 32) as usize; //shift at most 32 bits + let a = a as u64; //leave room for the shifted bits + let big_a = BigUint::from(a); + TestResult::from_bool(BigUint::from(a >> shift) == big_a >> shift) + } + + fn test_shl_signed(a: i32, shift: u8) -> TestResult { + let shift = (shift % 32) as usize; + let a = a as u64; //leave room for the shifted bits + let big_a = BigInt::from(a); + TestResult::from_bool(BigInt::from(a >> shift) == big_a >> shift) + } + + qc.quickcheck(test_shr_unsigned as fn(u64, u8) -> TestResult); + qc.quickcheck(test_shr_signed as fn(i64, u8) -> TestResult); + qc.quickcheck(test_shl_unsigned as fn(u32, u8) -> TestResult); + qc.quickcheck(test_shl_signed as fn(i32, u8) -> TestResult); +}