generated from ProcyOS/rust-template
add runs test #5
6 changed files with 90 additions and 8 deletions
|
@ -10,5 +10,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Added
|
||||
- Added a simple monobit test.
|
||||
- Added a block frequency test.
|
||||
- Added a runs test.
|
||||
|
||||
[Unreleased]: https://git.chir.rs/ProcyOS/rand_testsuite
|
||||
|
|
|
@ -7,6 +7,7 @@ use bitvec::{order::BitOrder, slice::BitSlice, store::BitStore};
|
|||
mod rand_core;
|
||||
|
||||
pub mod tests;
|
||||
pub(crate) mod utils;
|
||||
|
||||
#[cfg(feature = "rand_core")]
|
||||
pub use rand_core::RandomTestExt;
|
||||
|
|
11
src/tests.rs
11
src/tests.rs
|
@ -2,12 +2,14 @@
|
|||
|
||||
mod block_frequency;
|
||||
mod monobit;
|
||||
mod runs;
|
||||
|
||||
use core::f64;
|
||||
|
||||
use bitvec::prelude::{BitOrder, BitSlice, BitStore};
|
||||
pub use block_frequency::*;
|
||||
pub use monobit::*;
|
||||
pub use runs::*;
|
||||
|
||||
use crate::RandomTest;
|
||||
|
||||
|
@ -31,15 +33,20 @@ impl RandomTest for CombinedRandomTest {
|
|||
const RECOMMENDED_BIT_SIZE: usize = max_of([
|
||||
Monobit::RECOMMENDED_BIT_SIZE,
|
||||
BlockFrequency::RECOMMENDED_BIT_SIZE,
|
||||
Runs::RECOMMENDED_BIT_SIZE,
|
||||
]);
|
||||
|
||||
const MINIMUM_BIT_SIZE: usize =
|
||||
max_of([Monobit::MINIMUM_BIT_SIZE, BlockFrequency::MINIMUM_BIT_SIZE]);
|
||||
const MINIMUM_BIT_SIZE: usize = max_of([
|
||||
Monobit::MINIMUM_BIT_SIZE,
|
||||
BlockFrequency::MINIMUM_BIT_SIZE,
|
||||
Runs::MINIMUM_BIT_SIZE,
|
||||
]);
|
||||
|
||||
fn evaluate<T: BitStore, O: BitOrder>(&self, bs: &BitSlice<T, O>) -> Option<f64> {
|
||||
let mut worst_test_score = f64::MAX;
|
||||
worst_test_score = worst_test_score.min(Monobit.evaluate(bs)?);
|
||||
worst_test_score = worst_test_score.min(BlockFrequency.evaluate(bs)?);
|
||||
worst_test_score = worst_test_score.min(Runs.evaluate(bs)?);
|
||||
Some(worst_test_score)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use extra_math::gamma::Gamma;
|
|||
#[allow(unused_imports, reason = "redundant in std use cases")]
|
||||
use num_traits::Float;
|
||||
|
||||
use crate::RandomTest;
|
||||
use crate::{utils::ones_proportion, RandomTest};
|
||||
|
||||
/// The Block Frequency Test splits the input data into multiple blocks, counts the frequency 1s and 0s within these blocks, and determines whether these are distributed well.
|
||||
///
|
||||
|
@ -26,11 +26,7 @@ impl RandomTest for BlockFrequency {
|
|||
let block_count = bs.len() / block_size;
|
||||
let mut statistic = 0.0;
|
||||
for i in 0..block_count {
|
||||
let one_count: usize = bs[i * block_size..(i + 1) * block_size]
|
||||
.into_iter()
|
||||
.map(|v| if *v { 1 } else { 0 })
|
||||
.sum();
|
||||
let pi = (one_count as f64) / (block_size as f64);
|
||||
let pi = ones_proportion(&bs[i * block_size..(i + 1) * block_size]);
|
||||
statistic += (pi - 0.5).powi(2);
|
||||
}
|
||||
statistic *= (4 * block_size) as f64;
|
||||
|
|
67
src/tests/runs.rs
Normal file
67
src/tests/runs.rs
Normal file
|
@ -0,0 +1,67 @@
|
|||
//! Runs test code
|
||||
|
||||
use bitvec::{order::BitOrder, slice::BitSlice, store::BitStore};
|
||||
use libm::erfc;
|
||||
#[allow(unused_imports, reason = "redundant in std use cases")]
|
||||
use num_traits::Float;
|
||||
|
||||
use crate::{utils::ones_proportion, RandomTest};
|
||||
|
||||
/// The runs test counts the number of bit transitions inside of a random number generator.
|
||||
///
|
||||
/// This test is defined in the [NIST Special Publication 800-22](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-22r1a.pdf).
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Runs;
|
||||
|
||||
impl RandomTest for Runs {
|
||||
const RECOMMENDED_BIT_SIZE: usize = 100;
|
||||
const MINIMUM_BIT_SIZE: usize = 2;
|
||||
|
||||
fn evaluate<T: BitStore, O: BitOrder>(&self, bs: &BitSlice<T, O>) -> Option<f64> {
|
||||
if bs.len() < Self::MINIMUM_BIT_SIZE {
|
||||
return None;
|
||||
}
|
||||
let pi = ones_proportion(bs);
|
||||
if (pi - 0.5).abs() > 2.0 / (bs.len() as f64).sqrt() {
|
||||
return Some(0.0);
|
||||
}
|
||||
let (_, runs) = bs
|
||||
.into_iter()
|
||||
.skip(1)
|
||||
.fold((bs[0], 1usize), |(last, cum), cur| {
|
||||
if *cur == last {
|
||||
(last, cum)
|
||||
} else {
|
||||
(*cur, cum + 1)
|
||||
}
|
||||
});
|
||||
|
||||
Some(erfc(
|
||||
(runs as f64 - 2.0 * (bs.len() as f64) * pi * (1.0 - pi)).abs()
|
||||
/ (2.0 * (2.0 * bs.len() as f64).sqrt() * pi * (1.0 - pi)),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bitvec::prelude::*;
|
||||
|
||||
use crate::RandomTest;
|
||||
|
||||
use super::Runs;
|
||||
|
||||
#[test]
|
||||
fn runs_test_vector() {
|
||||
let epsilon = bitvec![
|
||||
1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0,
|
||||
0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0,
|
||||
1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1,
|
||||
0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0
|
||||
];
|
||||
let res = Runs.evaluate(epsilon.as_bitslice());
|
||||
assert!(res.is_some());
|
||||
let res = res.unwrap();
|
||||
assert!(res > 0.01, "Data should be evaluated as random");
|
||||
}
|
||||
}
|
10
src/utils.rs
Normal file
10
src/utils.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
//! Various utility functions
|
||||
|
||||
use core::convert::identity;
|
||||
|
||||
use bitvec::{order::BitOrder, slice::BitSlice, store::BitStore};
|
||||
|
||||
/// Calculates the proportion of ones in a bitslice
|
||||
pub fn ones_proportion<B: BitStore, O: BitOrder>(bs: &BitSlice<B, O>) -> f64 {
|
||||
bs.into_iter().filter(|b| **b).count() as f64 / bs.len() as f64
|
||||
}
|
Loading…
Reference in a new issue