diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 47973222..fb274922 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -10,6 +10,8 @@ jobs: matrix: rust: [ 1.60.0, # MSRV + 1.63.0, # rand v0.9 + 1.85.0, # rand v0.10 stable, beta, nightly @@ -69,7 +71,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: target: thumbv6m-none-eabi - - run: cargo build --target thumbv6m-none-eabi --no-default-features --features "serde rand" + - run: cargo build --target thumbv6m-none-eabi --no-default-features --features "serde rand_0_9 rand_0_10" fmt: name: Format @@ -87,9 +89,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly - - run: cargo doc --features std,serde,rand,quickcheck,arbitrary - env: - RUSTDOCFLAGS: --cfg docsrs + - run: cargo rustdoc --features std,serde,rand_0_9,rand_0_10,quickcheck,arbitrary -- --cfg docsrs # One job that "summarizes" the success state of this pipeline. This can then be added to branch # protection, rather than having to add each job separately. diff --git a/Cargo.toml b/Cargo.toml index 720512a6..aa835425 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ categories = [ "algorithms", "data-structures", "science" ] license = "MIT OR Apache-2.0" name = "num-bigint" repository = "https://github.com/rust-num/num-bigint" -version = "0.4.6" +version = "0.5.0" readme = "README.md" exclude = ["/ci/*", "/.github/*"] edition = "2021" @@ -19,24 +19,30 @@ default = ["std"] std = ["num-integer/std", "num-traits/std"] arbitrary = ["dep:arbitrary"] quickcheck = ["dep:quickcheck"] -rand = ["dep:rand"] +rand_0_9 = ["rand_core_0_9", "dep:rand_0_9"] +rand_core_0_9 = ["dep:rand_core_0_9"] +rand_0_10 = ["rand_core_0_10", "dep:rand_0_10"] +rand_core_0_10 = ["dep:rand_core_0_10"] serde = ["dep:serde"] [package.metadata.docs.rs] -features = ["std", "serde", "rand", "quickcheck", "arbitrary"] +features = ["std", "serde", "rand_0_10", "rand_0_9", "quickcheck", "arbitrary"] rustdoc-args = ["--cfg", "docsrs"] [[bench]] name = "bigint" +required-features = ["rand_0_10"] [[bench]] name = "factorial" [[bench]] name = "gcd" +required-features = ["rand_0_10"] [[bench]] name = "roots" +required-features = ["rand_0_10"] [[bench]] harness = false @@ -54,9 +60,28 @@ version = "0.2.18" default-features = false features = ["i128"] -[dependencies.rand] +[dependencies.rand_0_9] +package = "rand" optional = true -version = "0.8" +version = "0.9" +default-features = false + +[dependencies.rand_core_0_9] +package = "rand_core" +optional = true +version = "0.9" +default-features = false + +[dependencies.rand_0_10] +package = "rand" +optional = true +version = "0.10" +default-features = false + +[dependencies.rand_core_0_10] +package = "rand_core" +optional = true +version = "0.10" default-features = false [dependencies.serde] diff --git a/README.md b/README.md index cce185b9..e7ec6474 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Add this to your `Cargo.toml`: ```toml [dependencies] -num-bigint = "0.4" +num-bigint = "0.5" ``` ## Features @@ -25,16 +25,18 @@ if your compiler is not new enough. ### Random Generation -`num-bigint` supports the generation of random big integers when the `rand` -feature is enabled. To enable it include rand as +`num-bigint` supports the generation of random big integers when either of the +`rand_0_9` or `rand_0_10` features are enabled. For example: ```toml -rand = "0.8" -num-bigint = { version = "0.4", features = ["rand"] } +rand = "0.10" +num-bigint = { version = "0.5", features = ["rand_0_10"] } ``` -Note that you must use the version of `rand` that `num-bigint` is compatible -with: `0.8`. +Note that you must use the same version of `rand` as the feature you enable. + +You can instead use `rand_core_0_9` or `rand_core_0_10` for a more restricted +subset. ## Releases diff --git a/benches/bigint.rs b/benches/bigint.rs index 22795274..65db2de2 100644 --- a/benches/bigint.rs +++ b/benches/bigint.rs @@ -1,9 +1,8 @@ #![feature(test)] -#![cfg(feature = "rand")] extern crate test; -use num_bigint::{BigInt, BigUint, RandBigInt}; +use num_bigint::{BigInt, BigRng010, BigUint}; use num_traits::{FromPrimitive, Num, One, Zero}; use std::mem::replace; use test::Bencher; @@ -13,24 +12,24 @@ use rng::get_rng; fn multiply_bench(b: &mut Bencher, xbits: u64, ybits: u64) { let mut rng = get_rng(); - let x = rng.gen_bigint(xbits); - let y = rng.gen_bigint(ybits); + let x = rng.random_bigint(xbits); + let y = rng.random_bigint(ybits); b.iter(|| &x * &y); } fn divide_bench(b: &mut Bencher, xbits: u64, ybits: u64) { let mut rng = get_rng(); - let x = rng.gen_bigint(xbits); - let y = rng.gen_bigint(ybits); + let x = rng.random_bigint(xbits); + let y = rng.random_bigint(ybits); b.iter(|| &x / &y); } fn remainder_bench(b: &mut Bencher, xbits: u64, ybits: u64) { let mut rng = get_rng(); - let x = rng.gen_bigint(xbits); - let y = rng.gen_bigint(ybits); + let x = rng.random_bigint(xbits); + let y = rng.random_bigint(ybits); b.iter(|| &x % &y); } @@ -186,7 +185,7 @@ fn fib_to_string(b: &mut Bencher) { fn to_str_radix_bench(b: &mut Bencher, radix: u32, bits: u64) { let mut rng = get_rng(); - let x = rng.gen_bigint(bits); + let x = rng.random_bigint(bits); b.iter(|| x.to_str_radix(radix)); } @@ -222,7 +221,7 @@ fn to_str_radix_36(b: &mut Bencher) { fn from_str_radix_bench(b: &mut Bencher, radix: u32) { let mut rng = get_rng(); - let x = rng.gen_bigint(1009); + let x = rng.random_bigint(1009); let s = x.to_str_radix(radix); assert_eq!(x, BigInt::from_str_radix(&s, radix).unwrap()); b.iter(|| BigInt::from_str_radix(&s, radix)); @@ -256,7 +255,7 @@ fn from_str_radix_36(b: &mut Bencher) { fn rand_bench(b: &mut Bencher, bits: u64) { let mut rng = get_rng(); - b.iter(|| rng.gen_bigint(bits)); + b.iter(|| rng.random_bigint(bits)); } #[bench] @@ -327,7 +326,7 @@ fn shr(b: &mut Bencher) { fn hash(b: &mut Bencher) { use std::collections::HashSet; let mut rng = get_rng(); - let v: Vec = (1000..2000).map(|bits| rng.gen_bigint(bits)).collect(); + let v: Vec = (1000..2000).map(|bits| rng.random_bigint(bits)).collect(); b.iter(|| { let h: HashSet<&BigInt> = v.iter().collect(); assert_eq!(h.len(), v.len()); @@ -399,8 +398,8 @@ const RFC3526_2048BIT_MODP_GROUP: &str = "\ #[bench] fn modpow(b: &mut Bencher) { let mut rng = get_rng(); - let base = rng.gen_biguint(2048); - let e = rng.gen_biguint(2048); + let base = rng.random_biguint(2048); + let e = rng.random_biguint(2048); let m = BigUint::from_str_radix(RFC3526_2048BIT_MODP_GROUP, 16).unwrap(); b.iter(|| base.modpow(&e, &m)); @@ -409,8 +408,8 @@ fn modpow(b: &mut Bencher) { #[bench] fn modpow_even(b: &mut Bencher) { let mut rng = get_rng(); - let base = rng.gen_biguint(2048); - let e = rng.gen_biguint(2048); + let base = rng.random_biguint(2048); + let e = rng.random_biguint(2048); // Make the modulus even, so monty (base-2^32) doesn't apply. let m = BigUint::from_str_radix(RFC3526_2048BIT_MODP_GROUP, 16).unwrap() - 1u32; @@ -420,7 +419,7 @@ fn modpow_even(b: &mut Bencher) { #[bench] fn to_u32_digits(b: &mut Bencher) { let mut rng = get_rng(); - let n = rng.gen_biguint(2048); + let n = rng.random_biguint(2048); b.iter(|| n.to_u32_digits()); } @@ -428,7 +427,7 @@ fn to_u32_digits(b: &mut Bencher) { #[bench] fn iter_u32_digits(b: &mut Bencher) { let mut rng = get_rng(); - let n = rng.gen_biguint(2048); + let n = rng.random_biguint(2048); b.iter(|| n.iter_u32_digits().max()); } @@ -436,7 +435,7 @@ fn iter_u32_digits(b: &mut Bencher) { #[bench] fn to_u64_digits(b: &mut Bencher) { let mut rng = get_rng(); - let n = rng.gen_biguint(2048); + let n = rng.random_biguint(2048); b.iter(|| n.to_u64_digits()); } @@ -444,7 +443,7 @@ fn to_u64_digits(b: &mut Bencher) { #[bench] fn iter_u64_digits(b: &mut Bencher) { let mut rng = get_rng(); - let n = rng.gen_biguint(2048); + let n = rng.random_biguint(2048); b.iter(|| n.iter_u64_digits().max()); } diff --git a/benches/gcd.rs b/benches/gcd.rs index c211b6ef..aab87ac2 100644 --- a/benches/gcd.rs +++ b/benches/gcd.rs @@ -1,9 +1,8 @@ #![feature(test)] -#![cfg(feature = "rand")] extern crate test; -use num_bigint::{BigUint, RandBigInt}; +use num_bigint::{BigRng010, BigUint}; use num_integer::Integer; use num_traits::Zero; use test::Bencher; @@ -13,8 +12,8 @@ use rng::get_rng; fn bench(b: &mut Bencher, bits: u64, gcd: fn(&BigUint, &BigUint) -> BigUint) { let mut rng = get_rng(); - let x = rng.gen_biguint(bits); - let y = rng.gen_biguint(bits); + let x = rng.random_biguint(bits); + let y = rng.random_biguint(bits); assert_eq!(euclid(&x, &y), x.gcd(&y)); diff --git a/benches/rng/mod.rs b/benches/rng/mod.rs index 33e4f0fa..50668104 100644 --- a/benches/rng/mod.rs +++ b/benches/rng/mod.rs @@ -1,38 +1,6 @@ -use rand::RngCore; +use rand_0_10::rngs::Xoshiro128PlusPlus; +use rand_0_10::{Rng, SeedableRng}; -pub(crate) fn get_rng() -> impl RngCore { - XorShiftStar { - a: 0x0123_4567_89AB_CDEF, - } -} - -/// Simple `Rng` for benchmarking without additional dependencies -struct XorShiftStar { - a: u64, -} - -impl RngCore for XorShiftStar { - fn next_u32(&mut self) -> u32 { - self.next_u64() as u32 - } - - fn next_u64(&mut self) -> u64 { - // https://en.wikipedia.org/wiki/Xorshift#xorshift* - self.a ^= self.a >> 12; - self.a ^= self.a << 25; - self.a ^= self.a >> 27; - self.a.wrapping_mul(0x2545_F491_4F6C_DD1D) - } - - fn fill_bytes(&mut self, dest: &mut [u8]) { - for chunk in dest.chunks_mut(8) { - let bytes = self.next_u64().to_le_bytes(); - let slice = &bytes[..chunk.len()]; - chunk.copy_from_slice(slice) - } - } - - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { - Ok(self.fill_bytes(dest)) - } +pub(crate) fn get_rng() -> impl Rng { + Xoshiro128PlusPlus::from_seed(*b"benching bigints") } diff --git a/benches/roots.rs b/benches/roots.rs index 7afc4f76..f5bb0b91 100644 --- a/benches/roots.rs +++ b/benches/roots.rs @@ -1,9 +1,8 @@ #![feature(test)] -#![cfg(feature = "rand")] extern crate test; -use num_bigint::{BigUint, RandBigInt}; +use num_bigint::{BigRng010, BigUint}; use test::Bencher; mod rng; @@ -37,7 +36,7 @@ fn check(x: &BigUint, n: u32) { } fn bench_sqrt(b: &mut Bencher, bits: u64) { - let x = get_rng().gen_biguint(bits); + let x = get_rng().random_biguint(bits); eprintln!("bench_sqrt({})", x); check(&x, 2); @@ -65,7 +64,7 @@ fn big4k_sqrt(b: &mut Bencher) { } fn bench_cbrt(b: &mut Bencher, bits: u64) { - let x = get_rng().gen_biguint(bits); + let x = get_rng().random_biguint(bits); eprintln!("bench_cbrt({})", x); check(&x, 3); @@ -93,7 +92,7 @@ fn big4k_cbrt(b: &mut Bencher) { } fn bench_nth_root(b: &mut Bencher, bits: u64, n: u32) { - let x = get_rng().gen_biguint(bits); + let x = get_rng().random_biguint(bits); eprintln!("bench_{}th_root({})", n, x); check(&x, n); diff --git a/ci/big_quickcheck/Cargo.toml b/ci/big_quickcheck/Cargo.toml index 6002eedb..5507b0bf 100644 --- a/ci/big_quickcheck/Cargo.toml +++ b/ci/big_quickcheck/Cargo.toml @@ -3,6 +3,7 @@ name = "big_quickcheck" version = "0.1.0" authors = ["Josh Stone "] edition = "2018" +rust-version = "1.60" [dependencies] num-integer = "0.1.42" diff --git a/ci/big_rand/Cargo.toml b/ci/big_rand/Cargo.toml deleted file mode 100644 index d2bfe353..00000000 --- a/ci/big_rand/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "big_rand" -version = "0.1.0" -authors = ["Josh Stone "] -edition = "2018" - -[dependencies] -num-traits = "0.2.11" -rand = "0.8" -rand_chacha = "0.3" -rand_isaac = "0.3" -rand_xorshift = "0.3" - -[dependencies.num-bigint] -features = ["rand"] -path = "../.." diff --git a/ci/big_rand/src/lib.rs b/ci/big_rand/src/lib.rs deleted file mode 100644 index 69728777..00000000 --- a/ci/big_rand/src/lib.rs +++ /dev/null @@ -1,384 +0,0 @@ -//! Test randomization of `BigUint` and `BigInt` -//! -//! This test is in a completely separate crate so `rand::thread_rng()` -//! can be available without "infecting" the rest of the build with -//! `rand`'s default features, especially not `rand/std`. - -#![cfg(test)] - -mod torture; - -mod biguint { - use num_bigint::{BigUint, RandBigInt, RandomBits}; - use num_traits::Zero; - use rand::distributions::{Distribution, Uniform}; - use rand::thread_rng; - use rand::{Rng, SeedableRng}; - - #[test] - fn test_rand() { - let mut rng = thread_rng(); - let n: BigUint = rng.gen_biguint(137); - assert!(n.bits() <= 137); - assert!(rng.gen_biguint(0).is_zero()); - } - - #[test] - fn test_rand_bits() { - let mut rng = thread_rng(); - let n: BigUint = rng.sample(&RandomBits::new(137)); - assert!(n.bits() <= 137); - let z: BigUint = rng.sample(&RandomBits::new(0)); - assert!(z.is_zero()); - } - - #[test] - fn test_rand_range() { - let mut rng = thread_rng(); - - for _ in 0..10 { - assert_eq!( - rng.gen_biguint_range(&BigUint::from(236u32), &BigUint::from(237u32)), - BigUint::from(236u32) - ); - } - - let l = BigUint::from(403469000u32 + 2352); - let u = BigUint::from(403469000u32 + 3513); - for _ in 0..1000 { - let n: BigUint = rng.gen_biguint_below(&u); - assert!(n < u); - - let n: BigUint = rng.gen_biguint_range(&l, &u); - assert!(n >= l); - assert!(n < u); - } - } - - #[test] - #[should_panic] - fn test_zero_rand_range() { - thread_rng().gen_biguint_range(&BigUint::from(54u32), &BigUint::from(54u32)); - } - - #[test] - #[should_panic] - fn test_negative_rand_range() { - let mut rng = thread_rng(); - let l = BigUint::from(2352u32); - let u = BigUint::from(3513u32); - // Switching u and l should fail: - let _n: BigUint = rng.gen_biguint_range(&u, &l); - } - - #[test] - fn test_rand_uniform() { - let mut rng = thread_rng(); - - let tiny = Uniform::new(BigUint::from(236u32), BigUint::from(237u32)); - for _ in 0..10 { - assert_eq!(rng.sample(&tiny), BigUint::from(236u32)); - } - - let l = BigUint::from(403469000u32 + 2352); - let u = BigUint::from(403469000u32 + 3513); - let below = Uniform::new(BigUint::zero(), u.clone()); - let range = Uniform::new(l.clone(), u.clone()); - for _ in 0..1000 { - let n: BigUint = rng.sample(&below); - assert!(n < u); - - let n: BigUint = rng.sample(&range); - assert!(n >= l); - assert!(n < u); - } - } - - fn seeded_value_stability(expected: &[&str]) { - let mut seed = ::default(); - for (i, x) in seed.as_mut().iter_mut().enumerate() { - *x = (i as u8).wrapping_mul(191); - } - let mut rng = R::from_seed(seed); - for (i, &s) in expected.iter().enumerate() { - let n: BigUint = s.parse().unwrap(); - let r = rng.gen_biguint((1 << i) + i as u64); - assert_eq!(n, r); - } - } - - #[test] - fn test_chacha_value_stability() { - const EXPECTED: &[&str] = &[ - "0", - "0", - "52", - "84", - "23780", - "86502865016", - "187057847319509867386", - "34045731223080904464438757488196244981910", - "23813754422987836414755953516143692594193066497413249270287126597896871975915808", - "57401636903146945411652549098818446911814352529449356393690984105383482703074355\ - 67088360974672291353736011718191813678720755501317478656550386324355699624671", - ]; - use rand_chacha::ChaChaRng; - seeded_value_stability::(EXPECTED); - } - - #[test] - fn test_isaac_value_stability() { - const EXPECTED: &[&str] = &[ - "1", - "4", - "3", - "649", - "89116", - "7730042024", - "20773149082453254949", - "35999009049239918667571895439206839620281", - "10191757312714088681302309313551624007714035309632506837271600807524767413673006", - "37805949268912387809989378008822038725134260145886913321084097194957861133272558\ - 43458183365174899239251448892645546322463253898288141861183340823194379722556", - ]; - use rand_isaac::IsaacRng; - seeded_value_stability::(EXPECTED); - } - - #[test] - fn test_xorshift_value_stability() { - const EXPECTED: &[&str] = &[ - "1", - "0", - "37", - "395", - "181116", - "122718231117", - "1068467172329355695001", - "28246925743544411614293300167064395633287", - "12750053187017853048648861493745244146555950255549630854523304068318587267293038", - "53041498719137109355568081064978196049094604705283682101683207799515709404788873\ - 53417136457745727045473194367732849819278740266658219147356315674940229288531", - ]; - use rand_xorshift::XorShiftRng; - seeded_value_stability::(EXPECTED); - } - - #[test] - fn test_roots_rand() { - fn check>(x: T, n: u32) { - let x: BigUint = x.into(); - let root = x.nth_root(n); - println!("check {}.nth_root({}) = {}", x, n, root); - - if n == 2 { - assert_eq!(root, x.sqrt()) - } else if n == 3 { - assert_eq!(root, x.cbrt()) - } - - let lo = root.pow(n); - assert!(lo <= x); - assert_eq!(lo.nth_root(n), root); - if !lo.is_zero() { - assert_eq!((&lo - 1u32).nth_root(n), &root - 1u32); - } - - let hi = (&root + 1u32).pow(n); - assert!(hi > x); - assert_eq!(hi.nth_root(n), &root + 1u32); - assert_eq!((&hi - 1u32).nth_root(n), root); - } - - let mut rng = thread_rng(); - let bit_range = Uniform::new(0, 2048); - let sample_bits: Vec<_> = bit_range.sample_iter(&mut rng).take(100).collect(); - for bits in sample_bits { - let x = rng.gen_biguint(bits); - for n in 2..11 { - check(x.clone(), n); - } - check(x.clone(), 100); - } - } -} - -mod bigint { - use num_bigint::{BigInt, RandBigInt, RandomBits}; - use num_traits::Zero; - use rand::distributions::Uniform; - use rand::thread_rng; - use rand::{Rng, SeedableRng}; - - #[test] - fn test_rand() { - let mut rng = thread_rng(); - let n: BigInt = rng.gen_bigint(137); - assert!(n.bits() <= 137); - assert!(rng.gen_bigint(0).is_zero()); - } - - #[test] - fn test_rand_bits() { - let mut rng = thread_rng(); - let n: BigInt = rng.sample(&RandomBits::new(137)); - assert!(n.bits() <= 137); - let z: BigInt = rng.sample(&RandomBits::new(0)); - assert!(z.is_zero()); - } - - #[test] - fn test_rand_range() { - let mut rng = thread_rng(); - - for _ in 0..10 { - assert_eq!( - rng.gen_bigint_range(&BigInt::from(236), &BigInt::from(237)), - BigInt::from(236) - ); - } - - fn check(l: BigInt, u: BigInt) { - let mut rng = thread_rng(); - for _ in 0..1000 { - let n: BigInt = rng.gen_bigint_range(&l, &u); - assert!(n >= l); - assert!(n < u); - } - } - let l: BigInt = BigInt::from(403469000 + 2352); - let u: BigInt = BigInt::from(403469000 + 3513); - check(l.clone(), u.clone()); - check(-l.clone(), u.clone()); - check(-u, -l); - } - - #[test] - #[should_panic] - fn test_zero_rand_range() { - thread_rng().gen_bigint_range(&BigInt::from(54), &BigInt::from(54)); - } - - #[test] - #[should_panic] - fn test_negative_rand_range() { - let mut rng = thread_rng(); - let l = BigInt::from(2352); - let u = BigInt::from(3513); - // Switching u and l should fail: - let _n: BigInt = rng.gen_bigint_range(&u, &l); - } - - #[test] - fn test_rand_uniform() { - let mut rng = thread_rng(); - - let tiny = Uniform::new(BigInt::from(236u32), BigInt::from(237u32)); - for _ in 0..10 { - assert_eq!(rng.sample(&tiny), BigInt::from(236u32)); - } - - fn check(l: BigInt, u: BigInt) { - let mut rng = thread_rng(); - let range = Uniform::new(l.clone(), u.clone()); - for _ in 0..1000 { - let n: BigInt = rng.sample(&range); - assert!(n >= l); - assert!(n < u); - } - } - let l: BigInt = BigInt::from(403469000 + 2352); - let u: BigInt = BigInt::from(403469000 + 3513); - check(l.clone(), u.clone()); - check(-l.clone(), u.clone()); - check(-u, -l); - } - - fn seeded_value_stability(expected: &[&str]) { - let mut seed = ::default(); - for (i, x) in seed.as_mut().iter_mut().enumerate() { - *x = (i as u8).wrapping_mul(191); - } - let mut rng = R::from_seed(seed); - for (i, &s) in expected.iter().enumerate() { - let n: BigInt = s.parse().unwrap(); - let r = rng.gen_bigint((1 << i) + i as u64); - assert_eq!(n, r); - } - } - - #[test] - fn test_chacha_value_stability() { - const EXPECTED: &[&str] = &[ - "0", - "-6", - "-1", - "1321", - "-147247", - "8486373526", - "-272736656290199720696", - "2731152629387534140535423510744221288522", - "-28820024790651190394679732038637785320661450462089347915910979466834461433196572", - "501454570554170484799723603981439288209930393334472085317977614690773821680884844\ - 8530978478667288338327570972869032358120588620346111979053742269317702532328", - ]; - use rand_chacha::ChaChaRng; - seeded_value_stability::(EXPECTED); - } - - #[test] - fn test_isaac_value_stability() { - const EXPECTED: &[&str] = &[ - "1", - "0", - "5", - "113", - "-132240", - "-36348760761", - "-365690596708430705434", - "-14090753008246284277803606722552430292432", - "-26313941628626248579319341019368550803676255307056857978955881718727601479436059", - "-14563174552421101848999036239003801073335703811160945137332228646111920972691151\ - 88341090358094331641182310792892459091016794928947242043358702692294695845817", - ]; - use rand_isaac::IsaacRng; - seeded_value_stability::(EXPECTED); - } - - #[test] - fn test_xorshift_value_stability() { - const EXPECTED: &[&str] = &[ - "-1", - "-4", - "11", - "-1802", - "966495", - "-62592045703", - "-602281783447192077116", - "-34335811410223060575607987996861632509125", - "29156580925282215857325937227200350542000244609280383263289720243118706105351199", - "49920038676141573457451407325930326489996232208489690499754573826911037849083623\ - 24546142615325187412887314466195222441945661833644117700809693098722026764846", - ]; - use rand_xorshift::XorShiftRng; - seeded_value_stability::(EXPECTED); - } - - #[test] - fn test_random_shr() { - use rand::distributions::Standard; - use rand::Rng; - let rng = rand::thread_rng(); - - for p in rng.sample_iter::(&Standard).take(1000) { - let big = BigInt::from(p); - let bigger = &big << 1000; - assert_eq!(&bigger >> 1000, big); - for i in 0..64 { - let answer = BigInt::from(p >> i); - assert_eq!(&big >> i, answer); - assert_eq!(&bigger >> (1000 + i), answer); - } - } - } -} diff --git a/ci/big_rand_0_10/Cargo.toml b/ci/big_rand_0_10/Cargo.toml new file mode 100644 index 00000000..0c6cbffb --- /dev/null +++ b/ci/big_rand_0_10/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "big_rand" +edition = "2024" +rust-version = "1.85" +publish = false + +[dependencies] +num-traits = "0.2.11" +rand = "0.10" +rand_chacha = "0.10" +rand_isaac = "0.5" +rand_xorshift = "0.5" + +[dependencies.num-bigint] +features = ["rand_0_10"] +path = "../.." diff --git a/ci/big_rand_0_10/src/bigint.rs b/ci/big_rand_0_10/src/bigint.rs new file mode 100644 index 00000000..781dbf59 --- /dev/null +++ b/ci/big_rand_0_10/src/bigint.rs @@ -0,0 +1,178 @@ +use crate::BigRng; + +use num_bigint::{BigInt, RandomBits}; +use num_traits::Zero; +use rand::distr::Uniform; +use rand::prelude::*; +use rand::SeedableRng; + +#[test] +fn test_rand() { + let mut rng = rand::rng(); + let n: BigInt = rng.random_bigint(137); + assert!(n.bits() <= 137); + assert!(rng.random_bigint(0).is_zero()); +} + +#[test] +fn test_rand_bits() { + let mut rng = rand::rng(); + let n: BigInt = rng.sample(&RandomBits::new(137)); + assert!(n.bits() <= 137); + let z: BigInt = rng.sample(&RandomBits::new(0)); + assert!(z.is_zero()); +} + +#[test] +fn test_rand_range() { + let mut rng = rand::rng(); + + for _ in 0..10 { + assert_eq!( + rng.random_bigint_range(&BigInt::from(236), &BigInt::from(237)), + BigInt::from(236) + ); + } + + fn check(l: BigInt, u: BigInt) { + let mut rng = rand::rng(); + for _ in 0..1000 { + let n: BigInt = rng.random_bigint_range(&l, &u); + assert!(n >= l); + assert!(n < u); + } + } + let l: BigInt = BigInt::from(403469000 + 2352); + let u: BigInt = BigInt::from(403469000 + 3513); + check(l.clone(), u.clone()); + check(-l.clone(), u.clone()); + check(-u, -l); +} + +#[test] +#[should_panic] +fn test_zero_rand_range() { + rand::rng().random_bigint_range(&BigInt::from(54), &BigInt::from(54)); +} + +#[test] +#[should_panic] +fn test_negative_rand_range() { + let mut rng = rand::rng(); + let l = BigInt::from(2352); + let u = BigInt::from(3513); + // Switching u and l should fail: + let _n: BigInt = rng.random_bigint_range(&u, &l); +} + +#[test] +fn test_rand_uniform() { + let mut rng = rand::rng(); + + let tiny = Uniform::new(BigInt::from(236u32), BigInt::from(237u32)).unwrap(); + for _ in 0..10 { + assert_eq!(rng.sample(&tiny), BigInt::from(236u32)); + } + + fn check(l: BigInt, u: BigInt) { + let mut rng = rand::rng(); + let range = Uniform::new(l.clone(), u.clone()).unwrap(); + for _ in 0..1000 { + let n: BigInt = rng.sample(&range); + assert!(n >= l); + assert!(n < u); + } + } + let l: BigInt = BigInt::from(403469000 + 2352); + let u: BigInt = BigInt::from(403469000 + 3513); + check(l.clone(), u.clone()); + check(-l.clone(), u.clone()); + check(-u, -l); +} + +fn seeded_value_stability(expected: &[&str]) { + let mut seed = ::default(); + for (i, x) in seed.as_mut().iter_mut().enumerate() { + *x = (i as u8).wrapping_mul(191); + } + let mut rng = R::from_seed(seed); + for (i, &s) in expected.iter().enumerate() { + let n: BigInt = s.parse().unwrap(); + let r = rng.random_bigint((1 << i) + i as u64); + assert_eq!(n, r); + } +} + +#[test] +fn test_chacha_value_stability() { + const EXPECTED: &[&str] = &[ + "0", + "1", + "27", + "-2031", + "194831", + "-107270621334", + "604778049616971649784", + "-13279906003936626507900036341129035843658", + "9598537977256701954366948527195592617018633007593014285361020521520881912967196", + "-264136370194186516137263361513195811753545618309986722532270577136898598348583\ + 0633637046727891746431626435333970024327119474183088397436173223513443625647336", + ]; + use rand_chacha::ChaChaRng; + seeded_value_stability::(EXPECTED); +} + +#[test] +fn test_isaac_value_stability() { + const EXPECTED: &[&str] = &[ + "-1", + "1", + "-43", + "-1167", + "593159", + "87888368313", + "863752686698588599066", + "25660353483558192035558343375232549481936", + "28977159681084521074061473674568372684301464954366590951863406150909603461154587", + "87978171425457850961822054897752872385195765083062159849110972253115624397595266\ + 8338609763273154793791639422990440190830083511986328340851006045387434229689", + ]; + use rand_isaac::IsaacRng; + seeded_value_stability::(EXPECTED); +} + +#[test] +fn test_xorshift_value_stability() { + const EXPECTED: &[&str] = &[ + "1", + "4", + "-43", + "810", + "-389184", + "127016555143", + "860536200479125799740", + "9835480991915491206245016261774321284293", + "-14103609324431110452261709176070922521075146602747109938160234322089999252159519", + "-22299954340459823432328915829626283467388738618069360141644037252844203947132116\ + 47324956509902805306783167903408806315469788533784856707400727767026773441070", + ]; + use rand_xorshift::XorShiftRng; + seeded_value_stability::(EXPECTED); +} + +#[test] +fn test_random_shr() { + use rand::distr::StandardUniform; + let rng = rand::rng(); + + for p in rng.sample_iter::(&StandardUniform).take(1000) { + let big = BigInt::from(p); + let bigger = &big << 1000; + assert_eq!(&bigger >> 1000, big); + for i in 0..64 { + let answer = BigInt::from(p >> i); + assert_eq!(&big >> i, answer); + assert_eq!(&bigger >> (1000 + i), answer); + } + } +} diff --git a/ci/big_rand_0_10/src/biguint.rs b/ci/big_rand_0_10/src/biguint.rs new file mode 100644 index 00000000..1a8fec1b --- /dev/null +++ b/ci/big_rand_0_10/src/biguint.rs @@ -0,0 +1,194 @@ +use crate::BigRng; + +use num_bigint::{BigUint, RandomBits}; +use num_traits::Zero; +use rand::distr::{Distribution, Uniform}; +use rand::prelude::*; +use rand::SeedableRng; + +#[test] +fn test_rand() { + let mut rng = rand::rng(); + let n: BigUint = rng.random_biguint(137); + assert!(n.bits() <= 137); + assert!(rng.random_biguint(0).is_zero()); +} + +#[test] +fn test_rand_bits() { + let mut rng = rand::rng(); + let n: BigUint = rng.sample(&RandomBits::new(137)); + assert!(n.bits() <= 137); + let z: BigUint = rng.sample(&RandomBits::new(0)); + assert!(z.is_zero()); +} + +#[test] +fn test_rand_range() { + let mut rng = rand::rng(); + + for _ in 0..10 { + assert_eq!( + rng.random_biguint_range(&BigUint::from(236u32), &BigUint::from(237u32)), + BigUint::from(236u32) + ); + } + + let l = BigUint::from(403469000u32 + 2352); + let u = BigUint::from(403469000u32 + 3513); + for _ in 0..1000 { + let n: BigUint = rng.random_biguint_below(&u); + assert!(n < u); + + let n: BigUint = rng.random_biguint_range(&l, &u); + assert!(n >= l); + assert!(n < u); + } +} + +#[test] +#[should_panic] +fn test_zero_rand_range() { + rand::rng().random_biguint_range(&BigUint::from(54u32), &BigUint::from(54u32)); +} + +#[test] +#[should_panic] +fn test_negative_rand_range() { + let mut rng = rand::rng(); + let l = BigUint::from(2352u32); + let u = BigUint::from(3513u32); + // Switching u and l should fail: + let _n: BigUint = rng.random_biguint_range(&u, &l); +} + +#[test] +fn test_rand_uniform() { + let mut rng = rand::rng(); + + let tiny = Uniform::new(BigUint::from(236u32), BigUint::from(237u32)).unwrap(); + for _ in 0..10 { + assert_eq!(rng.sample(&tiny), BigUint::from(236u32)); + } + + let l = BigUint::from(403469000u32 + 2352); + let u = BigUint::from(403469000u32 + 3513); + let below = Uniform::new(BigUint::zero(), u.clone()).unwrap(); + let range = Uniform::new(l.clone(), u.clone()).unwrap(); + for _ in 0..1000 { + let n: BigUint = rng.sample(&below); + assert!(n < u); + + let n: BigUint = rng.sample(&range); + assert!(n >= l); + assert!(n < u); + } +} + +fn seeded_value_stability(expected: &[&str]) { + let mut seed = ::default(); + for (i, x) in seed.as_mut().iter_mut().enumerate() { + *x = (i as u8).wrapping_mul(191); + } + let mut rng = R::from_seed(seed); + for (i, &s) in expected.iter().enumerate() { + let n: BigUint = s.parse().unwrap(); + let r = rng.random_biguint((1 << i) + i as u64); + assert_eq!(n, r); + } +} + +#[test] +fn test_chacha_value_stability() { + const EXPECTED: &[&str] = &[ + "0", + "5", + "9", + "1737", + "937307", + "65028028536", + "279291567688057625466", + "32684601755397150610585259058469172136086", + "3318554627982869824783889169605932904164279211594869435303134228496248029647136", + "2709999098147667596661525260287323466371098577491054736003573524257229599510813\ + 950331137383286571179078064518241667831227807123801978731742492319680324618975", + ]; + use rand_chacha::ChaChaRng; + seeded_value_stability::(EXPECTED); +} + +#[test] +fn test_isaac_value_stability() { + const EXPECTED: &[&str] = &[ + "1", + "7", + "25", + "1683", + "117995", + "67859584168", + "518835239072611148581", + "16943196501667364713622917423027819778745", + "3128439869237800760464479228021661628664566245028432430864688183042066505636910", + "1300089857736899601863677638636111069172421900222879296808895380997042087658914\ + 2976856687389999376070549940495143742173349057217984875028392967823670091580", + ]; + use rand_isaac::IsaacRng; + seeded_value_stability::(EXPECTED); +} + +#[test] +fn test_xorshift_value_stability() { + const EXPECTED: &[&str] = &[ + "1", + "5", + "4", + "1412", + "508011", + "45408819789", + "35449504201620804505", + "1704901123711211464150080787386475139719", + "3602478137269873610186753678058899426147621466964026295406154931693450025738094", + "2676219517644961904040299206849473763923504769692259108134502736982105190584472\ + 145963969095305013973645948123528298553311404505627077619646814679735036805715", + ]; + use rand_xorshift::XorShiftRng; + seeded_value_stability::(EXPECTED); +} + +#[test] +fn test_roots_rand() { + fn check>(x: T, n: u32) { + let x: BigUint = x.into(); + let root = x.nth_root(n); + println!("check {}.nth_root({}) = {}", x, n, root); + + if n == 2 { + assert_eq!(root, x.sqrt()) + } else if n == 3 { + assert_eq!(root, x.cbrt()) + } + + let lo = root.pow(n); + assert!(lo <= x); + assert_eq!(lo.nth_root(n), root); + if !lo.is_zero() { + assert_eq!((&lo - 1u32).nth_root(n), &root - 1u32); + } + + let hi = (&root + 1u32).pow(n); + assert!(hi > x); + assert_eq!(hi.nth_root(n), &root + 1u32); + assert_eq!((&hi - 1u32).nth_root(n), root); + } + + let mut rng = rand::rng(); + let bit_range = Uniform::new(0, 2048).unwrap(); + let sample_bits: Vec<_> = bit_range.sample_iter(&mut rng).take(100).collect(); + for bits in sample_bits { + let x = rng.random_biguint(bits); + for n in 2..11 { + check(x.clone(), n); + } + check(x.clone(), 100); + } +} diff --git a/ci/big_rand_0_10/src/lib.rs b/ci/big_rand_0_10/src/lib.rs new file mode 100644 index 00000000..57f705a0 --- /dev/null +++ b/ci/big_rand_0_10/src/lib.rs @@ -0,0 +1,13 @@ +//! Test randomization of `BigUint` and `BigInt` +//! +//! This test is in a completely separate crate so `rand::rng()` +//! can be available without "infecting" the rest of the build with +//! `rand`'s default features, especially not `rand/std`. + +#![cfg(test)] + +use num_bigint::BigRng010 as BigRng; + +mod bigint; +mod biguint; +mod torture; diff --git a/ci/big_rand/src/torture.rs b/ci/big_rand_0_10/src/torture.rs similarity index 76% rename from ci/big_rand/src/torture.rs rename to ci/big_rand_0_10/src/torture.rs index 72059ef2..87871a88 100644 --- a/ci/big_rand/src/torture.rs +++ b/ci/big_rand_0_10/src/torture.rs @@ -1,4 +1,5 @@ -use num_bigint::RandBigInt; +use crate::BigRng; + use num_traits::Zero; use rand::prelude::*; use rand_xorshift::XorShiftRng; @@ -13,11 +14,11 @@ fn test_mul_divide_torture_count(count: usize) { for _ in 0..count { // Test with numbers of random sizes: - let xbits = rng.gen_range(0..bits_max); - let ybits = rng.gen_range(0..bits_max); + let xbits = rng.random_range(0..bits_max); + let ybits = rng.random_range(0..bits_max); - let x = rng.gen_biguint(xbits); - let y = rng.gen_biguint(ybits); + let x = rng.random_biguint(xbits); + let y = rng.random_biguint(ybits); if x.is_zero() || y.is_zero() { continue; @@ -45,10 +46,10 @@ fn test_factored_mul_torture_count(count: usize) { let mut rng = get_rng(); for _ in 0..count { - let w = rng.gen_biguint(bits); - let x = rng.gen_biguint(bits); - let y = rng.gen_biguint(bits); - let z = rng.gen_biguint(bits); + let w = rng.random_biguint(bits); + let x = rng.random_biguint(bits); + let y = rng.random_biguint(bits); + let z = rng.random_biguint(bits); let prod1 = (&w * &x) * (&y * &z); let prod2 = (&w * &y) * (&x * &z); diff --git a/ci/big_rand_0_9/Cargo.toml b/ci/big_rand_0_9/Cargo.toml new file mode 100644 index 00000000..2ca2a408 --- /dev/null +++ b/ci/big_rand_0_9/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "big_rand" +version = "0.0.0" +edition = "2021" +rust-version = "1.63" +publish = false + +[dependencies] +num-traits = "0.2.11" +rand = "0.9" +rand_chacha = "0.9" +rand_isaac = "0.4" +rand_xorshift = "0.4" + +[dependencies.num-bigint] +features = ["rand_0_9"] +path = "../.." diff --git a/ci/big_rand_0_9/src/lib.rs b/ci/big_rand_0_9/src/lib.rs new file mode 100644 index 00000000..b5fc07a2 --- /dev/null +++ b/ci/big_rand_0_9/src/lib.rs @@ -0,0 +1,16 @@ +//! Test randomization of `BigUint` and `BigInt` +//! +//! This test is in a completely separate crate so `rand::rng()` +//! can be available without "infecting" the rest of the build with +//! `rand`'s default features, especially not `rand/std`. + +#![cfg(test)] + +use num_bigint::BigRng09 as BigRng; + +#[path = "../../big_rand_0_10/src/bigint.rs"] +mod bigint; +#[path = "../../big_rand_0_10/src/biguint.rs"] +mod biguint; +#[path = "../../big_rand_0_10/src/torture.rs"] +mod torture; diff --git a/ci/big_serde/Cargo.toml b/ci/big_serde/Cargo.toml index f18413a2..678fe034 100644 --- a/ci/big_serde/Cargo.toml +++ b/ci/big_serde/Cargo.toml @@ -3,6 +3,7 @@ name = "big_serde" version = "0.1.0" authors = ["Josh Stone "] edition = "2018" +rust-version = "1.60" [dependencies] num-traits = "0.2.11" diff --git a/ci/rustup.sh b/ci/rustup.sh index 6cfe67ef..918fa028 100755 --- a/ci/rustup.sh +++ b/ci/rustup.sh @@ -5,6 +5,6 @@ set -ex ci=$(dirname $0) -for version in 1.60.0 stable beta nightly; do +for version in 1.60.0 1.63.0 1.85.0 stable beta nightly; do rustup run "$version" "$ci/test_full.sh" done diff --git a/ci/test_full.sh b/ci/test_full.sh index f28eafd6..85097fb0 100755 --- a/ci/test_full.sh +++ b/ci/test_full.sh @@ -21,23 +21,33 @@ check_version() { ]] } +export CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS=fallback +generate_lockfile() { + cargo generate-lockfile + if ! check_version 1.85 ; then + cargo +stable update + fi +} + echo "Testing $CRATE on rustc $RUST_VERSION" if ! check_version $MSRV ; then echo "The minimum for $CRATE is rustc $MSRV" exit 1 fi -STD_FEATURES=(arbitrary quickcheck rand serde) -NO_STD_FEATURES=(serde rand) +NO_STD_FEATURES=(serde) +check_version 1.63 && NO_STD_FEATURES+=(rand_core_0_9 rand_0_9) +check_version 1.85 && NO_STD_FEATURES+=(rand_core_0_10 rand_0_10) +STD_FEATURES=(${NO_STD_FEATURES[*]} arbitrary quickcheck) echo "Testing supported features: ${STD_FEATURES[*]}" if [ -n "${NO_STD_FEATURES[*]}" ]; then echo " no_std supported features: ${NO_STD_FEATURES[*]}" fi -# arbitrary 1.1.4 started using array::from_fn -check_version 1.63.0 || cargo update -p arbitrary --precise 1.1.3 +generate_lockfile -check_version 1.63.0 || cargo update -p libc --precise 0.2.163 +# arbitrary 1.1.4 started using array::from_fn, but didn't set rust-version +check_version 1.63.0 || cargo update -p arbitrary --precise 1.1.3 set -x @@ -81,18 +91,22 @@ fi case "${STD_FEATURES[*]}" in *serde*) ( cd ci/big_serde + generate_lockfile + cargo test + ) ;;& + *rand_0_9*) ( + cd ci/big_rand_0_9 + generate_lockfile cargo test ) ;;& - *rand*) ( - cd ci/big_rand - check_version 1.63.0 || cargo update -p libc --precise 0.2.163 - check_version 1.61.0 || cargo update -p ppv-lite86 --precise 0.2.17 + *rand_0_10*) ( + cd ci/big_rand_0_10 + generate_lockfile cargo test ) ;;& *quickcheck*) ( cd ci/big_quickcheck - check_version 1.63.0 || cargo update -p libc --precise 0.2.163 - check_version 1.61.0 || cargo update -p syn --precise 2.0.67 + generate_lockfile cargo test ) ;;& esac diff --git a/src/bigrand.rs b/src/bigrand.rs index e5cbacdb..ab7403b4 100644 --- a/src/bigrand.rs +++ b/src/bigrand.rs @@ -1,285 +1,76 @@ //! Randomization of big integers -#![cfg(feature = "rand")] -#![cfg_attr(docsrs, doc(cfg(feature = "rand")))] -use rand::distributions::uniform::{SampleBorrow, SampleUniform, UniformSampler}; -use rand::prelude::*; +#[cfg(any(feature = "rand_0_10", feature = "rand_0_9"))] +mod structs { + use crate::{BigInt, BigUint}; -use crate::BigInt; -use crate::BigUint; -use crate::Sign::*; - -use crate::biguint::biguint_from_vec; - -use num_integer::Integer; -use num_traits::{ToPrimitive, Zero}; - -/// A trait for sampling random big integers. -/// -/// The `rand` feature must be enabled to use this. See crate-level documentation for details. -pub trait RandBigInt { - /// Generate a random [`BigUint`] of the given bit size. - fn gen_biguint(&mut self, bit_size: u64) -> BigUint; - - /// Generate a random [ BigInt`] of the given bit size. - fn gen_bigint(&mut self, bit_size: u64) -> BigInt; - - /// Generate a random [`BigUint`] less than the given bound. Fails - /// when the bound is zero. - fn gen_biguint_below(&mut self, bound: &BigUint) -> BigUint; - - /// Generate a random [`BigUint`] within the given range. The lower - /// bound is inclusive; the upper bound is exclusive. Fails when - /// the upper bound is not greater than the lower bound. - fn gen_biguint_range(&mut self, lbound: &BigUint, ubound: &BigUint) -> BigUint; - - /// Generate a random [`BigInt`] within the given range. The lower - /// bound is inclusive; the upper bound is exclusive. Fails when - /// the upper bound is not greater than the lower bound. - fn gen_bigint_range(&mut self, lbound: &BigInt, ubound: &BigInt) -> BigInt; -} - -fn gen_bits(rng: &mut R, data: &mut [u32], rem: u64) { - // `fill` is faster than many `gen::` calls - rng.fill(data); - if rem > 0 { - let last = data.len() - 1; - data[last] >>= 32 - rem; + /// The back-end implementing rand's `UniformSampler` for [`BigUint`]. + #[derive(Clone, Debug)] + pub struct UniformBigUint { + pub(super) base: BigUint, + pub(super) len: BigUint, } -} - -impl RandBigInt for R { - cfg_digit!( - fn gen_biguint(&mut self, bit_size: u64) -> BigUint { - let (digits, rem) = bit_size.div_rem(&32); - let len = (digits + (rem > 0) as u64) - .to_usize() - .expect("capacity overflow"); - let mut data = vec![0u32; len]; - gen_bits(self, &mut data, rem); - biguint_from_vec(data) - } - fn gen_biguint(&mut self, bit_size: u64) -> BigUint { - use core::slice; - - let (digits, rem) = bit_size.div_rem(&32); - let len = (digits + (rem > 0) as u64) - .to_usize() - .expect("capacity overflow"); - let native_digits = Integer::div_ceil(&bit_size, &64); - let native_len = native_digits.to_usize().expect("capacity overflow"); - let mut data = vec![0u64; native_len]; - unsafe { - // Generate bits in a `&mut [u32]` slice for value stability - let ptr = data.as_mut_ptr() as *mut u32; - debug_assert!(native_len * 2 >= len); - let data = slice::from_raw_parts_mut(ptr, len); - gen_bits(self, data, rem); - } - #[cfg(target_endian = "big")] - for digit in &mut data { - // swap u32 digits into u64 endianness - *digit = (*digit << 32) | (*digit >> 32); - } - biguint_from_vec(data) - } - ); - - fn gen_bigint(&mut self, bit_size: u64) -> BigInt { - loop { - // Generate a random BigUint... - let biguint = self.gen_biguint(bit_size); - // ...and then randomly assign it a Sign... - let sign = if biguint.is_zero() { - // ...except that if the BigUint is zero, we need to try - // again with probability 0.5. This is because otherwise, - // the probability of generating a zero BigInt would be - // double that of any other number. - if self.gen() { - continue; - } else { - NoSign - } - } else if self.gen() { - Plus - } else { - Minus - }; - return BigInt::from_biguint(sign, biguint); - } + /// The back-end implementing rand's `UniformSampler` for [`BigInt`]. + #[derive(Clone, Debug)] + pub struct UniformBigInt { + pub(super) base: BigInt, + pub(super) len: BigUint, } - fn gen_biguint_below(&mut self, bound: &BigUint) -> BigUint { - assert!(!bound.is_zero()); - let bits = bound.bits(); - loop { - let n = self.gen_biguint(bits); - if n < *bound { - return n; - } - } + /// A random distribution for [`BigUint`] and [`BigInt`] values of a particular bit size. + #[derive(Clone, Copy, Debug)] + pub struct RandomBits { + pub(super) bits: u64, } - fn gen_biguint_range(&mut self, lbound: &BigUint, ubound: &BigUint) -> BigUint { - assert!(*lbound < *ubound); - if lbound.is_zero() { - self.gen_biguint_below(ubound) - } else { - lbound + self.gen_biguint_below(&(ubound - lbound)) - } - } - - fn gen_bigint_range(&mut self, lbound: &BigInt, ubound: &BigInt) -> BigInt { - assert!(*lbound < *ubound); - if lbound.is_zero() { - BigInt::from(self.gen_biguint_below(ubound.magnitude())) - } else if ubound.is_zero() { - lbound + BigInt::from(self.gen_biguint_below(lbound.magnitude())) - } else { - let delta = ubound - lbound; - lbound + BigInt::from(self.gen_biguint_below(delta.magnitude())) + impl RandomBits { + #[inline] + pub fn new(bits: u64) -> RandomBits { + RandomBits { bits } } } } -/// The back-end implementing rand's [`UniformSampler`] for [`BigUint`]. -#[derive(Clone, Debug)] -pub struct UniformBigUint { - base: BigUint, - len: BigUint, -} - -impl UniformSampler for UniformBigUint { - type X = BigUint; - - #[inline] - fn new(low_b: B1, high_b: B2) -> Self - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - let low = low_b.borrow(); - let high = high_b.borrow(); - assert!(low < high); - UniformBigUint { - len: high - low, - base: low.clone(), - } - } +#[cfg(any(feature = "rand_0_10", feature = "rand_0_9"))] +pub use structs::{RandomBits, UniformBigInt, UniformBigUint}; - #[inline] - fn new_inclusive(low_b: B1, high_b: B2) -> Self - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - let low = low_b.borrow(); - let high = high_b.borrow(); - assert!(low <= high); - Self::new(low, high + 1u32) - } +#[cfg(feature = "rand_core_0_10")] +#[path = "bigrand"] +mod impl_0_10 { + use rand_core_0_10::Rng; - #[inline] - fn sample(&self, rng: &mut R) -> Self::X { - &self.base + rng.gen_biguint_below(&self.len) - } + mod traits; - #[inline] - fn sample_single(low: B1, high: B2, rng: &mut R) -> Self::X - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - rng.gen_biguint_range(low.borrow(), high.borrow()) - } -} + pub use traits::BigRng; -impl SampleUniform for BigUint { - type Sampler = UniformBigUint; -} + #[cfg(feature = "rand_0_10")] + use rand_0_10 as rand; -/// The back-end implementing rand's [`UniformSampler`] for [`BigInt`]. -#[derive(Clone, Debug)] -pub struct UniformBigInt { - base: BigInt, - len: BigUint, + #[cfg(feature = "rand_0_10")] + #[cfg_attr(docsrs, doc(cfg(feature = "rand_0_10")))] + mod distr; } -impl UniformSampler for UniformBigInt { - type X = BigInt; +#[cfg(feature = "rand_core_0_10")] +pub use impl_0_10::BigRng as BigRng010; - #[inline] - fn new(low_b: B1, high_b: B2) -> Self - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - let low = low_b.borrow(); - let high = high_b.borrow(); - assert!(low < high); - UniformBigInt { - len: (high - low).into_parts().1, - base: low.clone(), - } - } +#[cfg(feature = "rand_core_0_9")] +#[path = "bigrand"] +mod impl_0_9 { + use rand_core_0_9::RngCore as Rng; - #[inline] - fn new_inclusive(low_b: B1, high_b: B2) -> Self - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - let low = low_b.borrow(); - let high = high_b.borrow(); - assert!(low <= high); - Self::new(low, high + 1u32) - } + mod traits; - #[inline] - fn sample(&self, rng: &mut R) -> Self::X { - &self.base + BigInt::from(rng.gen_biguint_below(&self.len)) - } + pub use traits::BigRng; - #[inline] - fn sample_single(low: B1, high: B2, rng: &mut R) -> Self::X - where - B1: SampleBorrow + Sized, - B2: SampleBorrow + Sized, - { - rng.gen_bigint_range(low.borrow(), high.borrow()) - } -} - -impl SampleUniform for BigInt { - type Sampler = UniformBigInt; -} + #[cfg(feature = "rand_0_9")] + use rand_0_9 as rand; -/// A random distribution for [`BigUint`] and [`BigInt`] values of a particular bit size. -/// -/// The `rand` feature must be enabled to use this. See crate-level documentation for details. -#[derive(Clone, Copy, Debug)] -pub struct RandomBits { - bits: u64, -} - -impl RandomBits { - #[inline] - pub fn new(bits: u64) -> RandomBits { - RandomBits { bits } - } + #[cfg(feature = "rand_0_9")] + #[cfg_attr(docsrs, doc(cfg(feature = "rand_0_9")))] + mod distr; } -impl Distribution for RandomBits { - #[inline] - fn sample(&self, rng: &mut R) -> BigUint { - rng.gen_biguint(self.bits) - } -} - -impl Distribution for RandomBits { - #[inline] - fn sample(&self, rng: &mut R) -> BigInt { - rng.gen_bigint(self.bits) - } -} +#[cfg(feature = "rand_core_0_9")] +pub use impl_0_9::BigRng as BigRng09; diff --git a/src/bigrand/distr.rs b/src/bigrand/distr.rs new file mode 100644 index 00000000..20c7a1db --- /dev/null +++ b/src/bigrand/distr.rs @@ -0,0 +1,202 @@ +//! Random distributions of big integers + +use super::{rand, BigRng}; +use crate::{BigInt, BigUint, RandomBits, UniformBigInt, UniformBigUint}; + +use rand::distr::uniform::{Error, SampleBorrow, SampleUniform, UniformSampler}; +use rand::distr::Distribution; +use rand::Rng; + +/// Registers [`UniformBigUint`] as the back-end enabling +/// [`Uniform`][rand::distr::Uniform] for [`BigUint`]. +impl SampleUniform for BigUint { + type Sampler = UniformBigUint; +} + +/// Enables [`Uniform`][rand::distr::Uniform] for [`BigUint`]. +impl UniformSampler for UniformBigUint { + type X = BigUint; + + #[inline] + fn new(low_b: B1, high_b: B2) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = low_b.borrow(); + let high = high_b.borrow(); + if low < high { + Ok(UniformBigUint { + len: high - low, + base: low.clone(), + }) + } else { + Err(Error::EmptyRange) + } + } + + #[inline] + fn new_inclusive(low_b: B1, high_b: B2) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = low_b.borrow(); + let high = high_b.borrow(); + if low <= high { + Ok(UniformBigUint { + len: high - low + 1u32, + base: low.clone(), + }) + } else { + Err(Error::EmptyRange) + } + } + + #[inline] + fn sample(&self, rng: &mut R) -> Self::X { + &self.base + rng.random_biguint_below(&self.len) + } + + #[inline] + fn sample_single( + low: B1, + high: B2, + rng: &mut R, + ) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = low.borrow(); + let high = high.borrow(); + if low < high { + Ok(rng.random_biguint_range(low, high)) + } else { + Err(Error::EmptyRange) + } + } + + #[inline] + fn sample_single_inclusive( + low: B1, + high: B2, + rng: &mut R, + ) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = low.borrow(); + let high = high.borrow(); + if low <= high { + Ok(rng.random_biguint_range(low, &(high + 1u32))) + } else { + Err(Error::EmptyRange) + } + } +} + +/// Registers [`UniformBigInt`] as the back-end enabling +/// [`Uniform`][rand::distr::Uniform] for [`BigInt`]. +impl SampleUniform for BigInt { + type Sampler = UniformBigInt; +} + +/// Enables [`Uniform`][rand::distr::Uniform] for [`BigInt`]. +impl UniformSampler for UniformBigInt { + type X = BigInt; + + #[inline] + fn new(low_b: B1, high_b: B2) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = low_b.borrow(); + let high = high_b.borrow(); + if low < high { + Ok(UniformBigInt { + len: (high - low).into_parts().1, + base: low.clone(), + }) + } else { + Err(Error::EmptyRange) + } + } + + #[inline] + fn new_inclusive(low_b: B1, high_b: B2) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = low_b.borrow(); + let high = high_b.borrow(); + if low <= high { + Ok(UniformBigInt { + len: (high - low).into_parts().1 + 1u32, + base: low.clone(), + }) + } else { + Err(Error::EmptyRange) + } + } + + #[inline] + fn sample(&self, rng: &mut R) -> Self::X { + &self.base + BigInt::from(rng.random_biguint_below(&self.len)) + } + + #[inline] + fn sample_single( + low: B1, + high: B2, + rng: &mut R, + ) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = low.borrow(); + let high = high.borrow(); + if low < high { + Ok(rng.random_bigint_range(low, high)) + } else { + Err(Error::EmptyRange) + } + } + + #[inline] + fn sample_single_inclusive( + low: B1, + high: B2, + rng: &mut R, + ) -> Result + where + B1: SampleBorrow + Sized, + B2: SampleBorrow + Sized, + { + let low = low.borrow(); + let high = high.borrow(); + if low <= high { + Ok(rng.random_bigint_range(low, &(high + 1u32))) + } else { + Err(Error::EmptyRange) + } + } +} + +impl Distribution for RandomBits { + #[inline] + fn sample(&self, rng: &mut R) -> BigUint { + rng.random_biguint(self.bits) + } +} + +impl Distribution for RandomBits { + #[inline] + fn sample(&self, rng: &mut R) -> BigInt { + rng.random_bigint(self.bits) + } +} diff --git a/src/bigrand/traits.rs b/src/bigrand/traits.rs new file mode 100644 index 00000000..e5f5ad5c --- /dev/null +++ b/src/bigrand/traits.rs @@ -0,0 +1,147 @@ +//! Randomization of big integers + +use super::Rng; +use crate::Sign::*; +use crate::{BigInt, BigUint}; + +use crate::big_digit::BigDigit; +use crate::biguint::biguint_from_vec; + +use core::mem::size_of; +use num_integer::Integer; +use num_traits::Zero; + +/// A trait extending [`Rng`] for sampling random big integers. +pub trait BigRng: Rng { + /// Generate a random [`BigUint`] of the given bit size. + fn random_biguint(&mut self, bit_size: u64) -> BigUint; + + /// Generate a random [ BigInt`] of the given bit size. + fn random_bigint(&mut self, bit_size: u64) -> BigInt; + + /// Generate a random [`BigUint`] less than the given bound. Fails + /// when the bound is zero. + fn random_biguint_below(&mut self, bound: &BigUint) -> BigUint; + + /// Generate a random [`BigUint`] within the given range. The lower + /// bound is inclusive; the upper bound is exclusive. Fails when + /// the upper bound is not greater than the lower bound. + fn random_biguint_range(&mut self, lbound: &BigUint, ubound: &BigUint) -> BigUint; + + /// Generate a random [`BigInt`] within the given range. The lower + /// bound is inclusive; the upper bound is exclusive. Fails when + /// the upper bound is not greater than the lower bound. + fn random_bigint_range(&mut self, lbound: &BigInt, ubound: &BigInt) -> BigInt; + + #[deprecated(since = "0.5.0", note = "Renamed to `random_biguint`")] + fn gen_biguint(&mut self, bit_size: u64) -> BigUint { + self.random_biguint(bit_size) + } + + #[deprecated(since = "0.5.0", note = "Renamed to `random_bigint`")] + fn gen_bigint(&mut self, bit_size: u64) -> BigInt { + self.random_bigint(bit_size) + } + + #[deprecated(since = "0.5.0", note = "Renamed to `random_biguint_below`")] + fn gen_biguint_below(&mut self, bound: &BigUint) -> BigUint { + self.random_biguint_below(bound) + } + + #[deprecated(since = "0.5.0", note = "Renamed to `random_biguint_range`")] + fn gen_biguint_range(&mut self, lbound: &BigUint, ubound: &BigUint) -> BigUint { + self.random_biguint_range(lbound, ubound) + } + + #[deprecated(since = "0.5.0", note = "Renamed to `random_bigint_range`")] + fn gen_bigint_range(&mut self, lbound: &BigInt, ubound: &BigInt) -> BigInt { + self.random_bigint_range(lbound, ubound) + } +} + +impl BigRng for R { + fn random_biguint(&mut self, bit_size: u64) -> BigUint { + let mask = (1 << (bit_size % 8)) - 1; + let bytes = Integer::div_ceil(&bit_size, &8); + let bytes = usize::try_from(bytes).expect("capacity overflow"); + + let digits = Integer::div_ceil(&bit_size, &BigDigit::BITS.into()); + let digits = usize::try_from(digits).unwrap(); + debug_assert_eq!(digits, Integer::div_ceil(&bytes, &size_of::())); + + let mut data = vec![0 as BigDigit; digits]; + { + let ptr = data.as_mut_ptr().cast::(); + let slice = unsafe { core::slice::from_raw_parts_mut(ptr, bytes) }; + self.fill_bytes(slice); + if mask != 0 { + slice[bytes - 1] &= mask; + } + } + #[cfg(not(target_endian = "little"))] + for digit in &mut data { + *digit = BigDigit::from_le(*digit); + } + biguint_from_vec(data) + } + + fn random_bigint(&mut self, bit_size: u64) -> BigInt { + loop { + // Generate a random BigUint... + let biguint = self.random_biguint(bit_size); + // ...and then randomly assign it a Sign... + let negative = (self.next_u32() as i32) < 0; + let sign = if biguint.is_zero() { + // ...except that if the BigUint is zero, we need to try + // again with probability 0.5. This is because otherwise, + // the probability of generating a zero BigInt would be + // double that of any other number. + if negative { + continue; + } + NoSign + } else if negative { + Minus + } else { + Plus + }; + return BigInt::from_biguint(sign, biguint); + } + } + + fn random_biguint_below(&mut self, bound: &BigUint) -> BigUint { + assert!(!bound.is_zero()); + let bits = bound.bits(); + if bound.trailing_zeros() == Some(bits - 1) { + // The exclusive bound is trivially met in this case + return self.random_biguint(bits - 1); + } + loop { + let n = self.random_biguint(bits); + if n < *bound { + return n; + } + } + } + + fn random_biguint_range(&mut self, lbound: &BigUint, ubound: &BigUint) -> BigUint { + assert!(*lbound < *ubound); + if lbound.is_zero() { + self.random_biguint_below(ubound) + } else { + lbound + self.random_biguint_below(&(ubound - lbound)) + } + } + + fn random_bigint_range(&mut self, lbound: &BigInt, ubound: &BigInt) -> BigInt { + assert!(*lbound < *ubound); + if lbound.is_zero() { + BigInt::from(self.random_biguint_below(ubound.magnitude())) + } else if ubound.is_zero() { + lbound + BigInt::from(self.random_biguint_below(lbound.magnitude())) + } else { + let delta = ubound - lbound; + lbound + BigInt::from(self.random_biguint_below(delta.magnitude())) + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 6e47479d..85a294df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,19 +42,24 @@ //! //! It's easy to generate large random numbers: //! -//! ```rust,ignore -//! use num_bigint::{ToBigInt, RandBigInt}; +#![cfg_attr(feature = "rand_0_10", doc = "```")] +#![cfg_attr(not(feature = "rand_0_10"), doc = "```ignore")] +//! # use ::rand_0_10 as rand; +//! use rand::{SeedableRng, rngs::SmallRng}; +//! use num_bigint::{BigInt, BigRng010}; //! -//! let mut rng = rand::thread_rng(); -//! let a = rng.gen_bigint(1000); +//! // This seed is just for demonstration, but in most cases +//! // you'll probably want a non-deterministic `rng`. +//! let mut rng = SmallRng::seed_from_u64(42); +//! let a = rng.random_bigint(1000); //! -//! let low = -10000.to_bigint().unwrap(); -//! let high = 10000.to_bigint().unwrap(); -//! let b = rng.gen_bigint_range(&low, &high); +//! let low = BigInt::from(-10000); +//! let high = BigInt::from(10000); +//! let b = rng.random_bigint_range(&low, &high); //! //! // Probably an even larger number. //! println!("{}", a * b); -//! ``` +#![doc = "```"] //! //! See the "Features" section for instructions for enabling random number generation. //! @@ -67,16 +72,26 @@ //! //! ### Random Generation //! -//! `num-bigint` supports the generation of random big integers when the `rand` -//! feature is enabled. To enable it include rand as +//! `num-bigint` supports the generation of random big integers when either of the `rand_0_9` or +//! `rand_0_10` features are enabled. The [`BigRng09`] and [`BigRng010`] traits provide extension +//! methods for any `rand_core` RNG of their respective version, while the structs [`RandomBits`], +//! [`UniformBigInt`], and [`UniformBigUint`] fulfill further functionality for random +//! distributions in `rand::distr`. +//! +//! For example, using `rand v0.10` in your `Cargo.toml` may look like this: //! //! ```toml -//! rand = "0.8" -//! num-bigint = { version = "0.4", features = ["rand"] } +//! rand = "0.10" +//! num-bigint = { version = "0.5", features = ["rand_0_10"] } //! ``` //! -//! Note that you must use the version of `rand` that `num-bigint` is compatible -//! with: `0.8`. +//! Note that you must use the same version of `rand` as the feature you enable in `num-bigint`. +//! It's also fine for multiple versions to be enabled at once -- the random-distribution structs +//! will be shared with trait implementations for each `rand` feature that is enabled, while the +//! `BigRng` traits are distinct. +//! +//! You can instead use `rand_core_0_9` or `rand_core_0_10` for a more restricted subset, with +//! *only* the `BigRng` traits. //! //! ### Arbitrary Big Integers //! @@ -96,7 +111,6 @@ //! The `num-bigint` crate is tested for rustc 1.60 and greater. #![cfg_attr(docsrs, feature(doc_cfg))] -#![doc(html_root_url = "https://docs.rs/num-bigint/0.4")] #![warn(rust_2018_idioms)] #![no_std] @@ -220,9 +234,14 @@ pub use crate::bigint::BigInt; pub use crate::bigint::Sign; pub use crate::bigint::ToBigInt; -#[cfg(feature = "rand")] -#[cfg_attr(docsrs, doc(cfg(feature = "rand")))] -pub use crate::bigrand::{RandBigInt, RandomBits, UniformBigInt, UniformBigUint}; +#[cfg(feature = "rand_core_0_10")] +pub use crate::bigrand::BigRng010; + +#[cfg(feature = "rand_core_0_9")] +pub use crate::bigrand::BigRng09; + +#[cfg(any(feature = "rand_0_10", feature = "rand_0_9"))] +pub use crate::bigrand::{RandomBits, UniformBigInt, UniformBigUint}; mod big_digit { // A [`BigDigit`] is a [`BigUint`]'s composing element.