added observation and z-score to results, emojis

This commit is contained in:
randogoth 2025-01-17 13:27:34 +02:00
parent 10ff2123ac
commit a8c74caf90
19 changed files with 121 additions and 98 deletions

View file

@ -6,12 +6,12 @@ impl Onod {
/// Avalanche randomness test
/// Compares the bit-level differences between consecutive chunks of data and returns a p-value.
pub fn avalanche(samples: &[u8]) -> f64 {
pub fn avalanche(samples: &[u8]) -> (f64, f64, f64) {
const XOR_WINDOW_SIZE: usize = 20; // Bytes. Equivalent to SHA-1 (160 bits).
if samples.len() < 2 * XOR_WINDOW_SIZE {
return 0.0; // Not enough data for meaningful calculation
return (-1.0, 0.0, 1.0); // Not enough data for meaningful calculation
}
let mut means = Vec::new();
@ -46,7 +46,7 @@ impl Onod {
let normal_dist = Normal::new(0.0, 1.0).expect("Failed to create Normal distribution");
let p_value = 2.0 * (1.0 - normal_dist.cdf(z_score.abs()));
p_value
(mean_observed, z_score, p_value)
}
}

View file

@ -4,10 +4,10 @@ use crate::Onod;
impl Onod {
pub fn chi_bit(samples: &[u8]) -> f64 {
pub fn chi_bit(samples: &[u8]) -> (f64, f64, f64) {
if samples.is_empty() {
return 0.0; // empty data
return (-1.0, 0.0, 1.0); // empty data
}
// Lookup table for the number of set bits in each byte (Hamming weight)
@ -57,6 +57,12 @@ impl Onod {
let chi_squared_dist = ChiSquared::new(degrees_of_freedom).expect("Failed to create ChiSquared distribution");
let p_value = 1.0 - chi_squared_dist.cdf(chi_squared_stat);
p_value
// Z-score calculation (standardization of the chi-squared statistic)
let mean = degrees_of_freedom; // Mean of the chi-squared distribution
let std_dev = (2.0 * degrees_of_freedom).sqrt(); // Standard deviation of the chi-squared distribution
let z_score = (chi_squared_stat - mean) / std_dev;
(chi_squared_stat, z_score, p_value)
}
}

View file

@ -7,10 +7,10 @@ impl Onod {
/// ChiByte randomness test
/// Evaluates the uniformity of byte values across the data and returns a p-value.
pub fn chi_byte(samples: &[u8]) -> f64 {
pub fn chi_byte(samples: &[u8]) -> (f64, f64, f64) {
if samples.is_empty() {
return 0.0; // Default to perfect randomness for empty data
return (-1.0, 0.0, 1.0); // Default to perfect randomness for empty data
}
// Count occurrences of each byte value (0-255)
@ -35,7 +35,12 @@ impl Onod {
let chi_squared_dist = ChiSquared::new(degrees_of_freedom).expect("Failed to create ChiSquared distribution");
let p_value = 1.0 - chi_squared_dist.cdf(chi_squared_stat);
p_value
// Z-score calculation (standardization of the chi-squared statistic)
let mean = degrees_of_freedom; // Mean of the chi-squared distribution
let std_dev = (2.0 * degrees_of_freedom).sqrt(); // Standard deviation of the chi-squared distribution
let z_score = (chi_squared_stat - mean) / std_dev;
(chi_squared_stat, z_score, p_value)
}
}

View file

@ -8,19 +8,19 @@ impl Onod {
/// Compression randomness test
/// Estimates randomness by the compressibility of the data and returns a p-value.
pub fn compression(samples: &[u8]) -> f64 {
pub fn compression(samples: &[u8]) -> (f64, f64, f64) {
if samples.is_empty() {
return 0.0; // Perfect randomness for empty data
return (-1.0, 0.0, 1.0); // Perfect randomness for empty data
}
// Compress the data using deflate
let mut encoder = DeflateEncoder::new(Vec::new(), Compression::default());
if encoder.write_all(samples).is_err() {
return 0.0; // Compression failed
return (-1.0, 0.0, 1.0); // Compression failed
}
let compressed_data = match encoder.finish() {
Ok(data) => data,
Err(_) => return 0.0, // Compression failed
Err(_) => return (-1.0, 0.0, 1.0), // Compression failed
};
// Calculate compression ratio
@ -37,6 +37,6 @@ impl Onod {
let normal_dist = Normal::new(0.0, 1.0).expect("Failed to create Normal distribution");
let p_value = 2.0 * (1.0 - normal_dist.cdf(z_score.abs())); // Two-tailed test
p_value
(compression_ratio, z_score, p_value)
}
}

View file

@ -6,10 +6,10 @@ impl Onod {
/// Gaps randomness test
/// Analyzes the gaps between occurrences of a specific byte value and returns a p-value.
pub fn gaps(samples: &[u8]) -> f64 {
pub fn gaps(samples: &[u8]) -> (f64, f64, f64) {
if samples.is_empty() {
return 0.0; // empty data
return (-1.0, 0.0, 1.0); // empty data
}
// Bin edges and expected frequencies from the Java implementation
@ -41,7 +41,7 @@ impl Onod {
}
if all_gaps.is_empty() {
return 1.0; // No gaps found, assume randomness
return (-1.0, 0.0, 1.0); // No gaps found
}
// Create histogram of observed gaps
@ -73,7 +73,12 @@ impl Onod {
let chi_squared_dist = ChiSquared::new(degrees_of_freedom).expect("Failed to create ChiSquared distribution");
let p_value = 1.0 - chi_squared_dist.cdf(chi_squared_stat);
p_value
// Z-score calculation (standardization of the chi-squared statistic)
let mean = degrees_of_freedom; // Mean of the chi-squared distribution
let std_dev = (2.0 * degrees_of_freedom).sqrt(); // Standard deviation of the chi-squared distribution
let z_score = (chi_squared_stat - mean) / std_dev;
(chi_squared_stat, z_score, p_value)
}
}

View file

@ -14,10 +14,10 @@ impl Onod {
// `kolmogorov_smirnov` crate, does not add jitter or handle ties in the same way, and
// adheres to the crate's internal handling of floating-point comparisons. These differences
// may result in slight variations in p-values or KS statistics between the two versions.
pub fn ks(samples: &[u8]) -> f64 {
pub fn ks(samples: &[u8]) -> (f64, f64, f64) {
if samples.is_empty() {
return 0.0; // empty data
return (-1.0, 0.0, 1.0); // empty data
}
// Normalize the input samples to [0, 1] range
@ -32,9 +32,16 @@ impl Onod {
let confidence = 0.01; // Significance level
let result = test_f64(&normalized_samples, &uniform_distribution, confidence);
// Extract p-value from the result
// Extract the KS statistic (D-statistic)
let ks_statistic = result.statistic;
// Calculate the z-score
let sample_size = normalized_samples.len() as f64;
let z_score = ks_statistic * sample_size.sqrt();
// Extract the p-value
let p_value = 1.0 - result.reject_probability;
p_value
(ks_statistic, z_score, p_value)
}
}

View file

@ -6,11 +6,11 @@ impl Onod {
/// Mean randomness test
/// Calculates the p-value for the mean of the byte slice compared to expected mean.
pub fn mean_byte(samples: &[u8]) -> f64 {
pub fn mean_byte(samples: &[u8]) -> (f64, f64, f64) {
let len = samples.len() as f64;
if len == 0.0 {
return 0.0;
return (-1.0, 0.0, 1.0);
}
// Calculate observed mean
@ -29,6 +29,6 @@ impl Onod {
let normal_dist = Normal::new(0.0, 1.0).expect("Failed to create Normal distribution");
let p_value = 2.0 * (1.0 - normal_dist.cdf(z_score.abs())); // Two-tailed test
p_value
(observed_mean, z_score, p_value)
}
}

View file

@ -6,10 +6,10 @@ impl Onod {
/// Monobit randomness test
/// Evaluates the balance of 0s and 1s in the binary representation of the data and returns a p-value.
pub fn monobit(samples: &[u8]) -> f64 {
pub fn monobit(samples: &[u8]) -> (f64, f64, f64) {
if samples.is_empty() {
return 0.0; // Default to perfect randomness for empty data
return (-1.0, 0.0, 1.0); // Default to perfect randomness for empty data
}
// Count the total number of 1s in the dataset
@ -35,6 +35,6 @@ impl Onod {
let normal_dist = Normal::new(0.0, 1.0).expect("Failed to create Normal distribution");
let p_value = 2.0 * (1.0 - normal_dist.cdf(z_score.abs()));
p_value
(observed_proportion, z_score, p_value)
}
}

View file

@ -24,10 +24,10 @@ impl Onod {
///
/// These differences are generally negligible for practical purposes and do not affect the overall functionality or
/// statistical significance of the test.
pub fn pi(samples: &[u8]) -> f64 {
pub fn pi(samples: &[u8]) -> (f64, f64, f64) {
if samples.is_empty() {
return 0.0;
return (-1.0, 0.0, 1.0);
}
// Normalize samples to [0.0, 1.0)
@ -61,7 +61,7 @@ impl Onod {
let normal_dist = Normal::new(0.0, 1.0).expect("Failed to create Normal distribution");
let p_value = 2.0 * (1.0 - normal_dist.cdf(z_score.abs()));
p_value
(test_statistic, z_score, p_value)
}
}

View file

@ -6,10 +6,10 @@ impl Onod {
/// Prediction randomness test
/// Evaluates the predictability of the next bit based on current data and returns a p-value.
pub fn prediction(samples: &[u8]) -> f64 {
pub fn prediction(samples: &[u8]) -> (f64, f64, f64) {
if samples.is_empty() {
return 0.0;
return (-1.0, 0.0, 1.0);
}
let mut correct_predictions = 0;
@ -29,7 +29,7 @@ impl Onod {
}
if total_predictions == 0 {
return 0.0; // No predictions possible
return (-1.0, 0.0, 1.0); // No predictions possible
}
// Calculate observed proportion of correct predictions
@ -46,6 +46,6 @@ impl Onod {
let normal_dist = Normal::new(0.0, 1.0).expect("Failed to create Normal distribution");
let p_value = 2.0 * (1.0 - normal_dist.cdf(z_score.abs()));
p_value
(observed_proportion, z_score, p_value)
}
}

View file

@ -6,10 +6,10 @@ impl Onod {
/// Runs randomness test
/// Evaluates the randomness by counting both increasing and decreasing runs and returns a p-value.
pub fn runs(samples: &[u8]) -> f64 {
pub fn runs(samples: &[u8]) -> (f64, f64, f64) {
if samples.is_empty() {
return 0.0;
return (-1.0, 0.0, 1.0);
}
// Clone the samples to avoid modifying the original input
@ -53,10 +53,10 @@ impl Onod {
let p_value = 2.0 * (1.0 - normal_dist.cdf(z_score.abs()));
if p_value.is_nan() {
return 0.0; // Return 0 if p-value is NaN
return (-1.0, 0.0, 1.0); // Return 0 if p-value is NaN
}
p_value
(runs_observed as f64, z_score, p_value)
}
}

View file

@ -6,12 +6,12 @@ impl Onod {
/// RunUps randomness test
/// Evaluates the number of four-byte run-ups in the data and returns a p-value.
pub fn run_ups(input: &[u8]) -> f64 {
pub fn run_ups(input: &[u8]) -> (f64, f64, f64) {
let samples = input.iter().map(|&x| x as u32).collect::<Vec<u32>>();
if samples.len() < 4 {
return 0.0; // Not enough data for meaningful calculation
return (-1.0, 0.0, 1.0); // Not enough data for meaningful calculation
}
let mut test_statistic = 0;
@ -39,6 +39,11 @@ impl Onod {
let p_value = 1.0 - chi_squared_dist.cdf(chi_squared_stat);
p_value
// Z-score calculation (standardization of the chi-squared statistic)
let mean = 1.0; // Mean of the chi-squared distribution
let std_dev = (2.0 as f64).sqrt(); // Standard deviation of the chi-squared distribution
let z_score = (chi_squared_stat - mean) / std_dev;
(chi_squared_stat, z_score, p_value)
}
}

View file

@ -4,9 +4,9 @@ impl Onod {
/// Sanity randomness test
/// Checks for basic properties of randomness and returns a p-value.
pub fn sanity(samples: &[u8]) -> f64 {
pub fn sanity(samples: &[u8]) -> (f64, f64, f64) {
if samples.is_empty() {
return 0.0;
return (-1.0, 0.0, 1.0);
}
// Check the proportion of ones and zeros in the byte data
@ -22,12 +22,15 @@ impl Onod {
let expected_ratio = 0.5; // Expected for a truly random sequence
let std_dev = (0.5 * 0.5 / total_bits as f64).sqrt(); // Standard deviation for binomial distribution
// Z-score calculation
let z_score = (observed_ratio - expected_ratio) / std_dev;
// Use normal distribution to calculate p-value
use statrs::distribution::{Normal, ContinuousCDF};
let normal_dist = Normal::new(expected_ratio, std_dev).expect("Failed to create Normal distribution");
let p_value = 1.0 - normal_dist.cdf(observed_ratio);
p_value
(observed_ratio, z_score, p_value)
}
}

View file

@ -5,11 +5,11 @@ use crate::Onod;
impl Onod {
/// Entropy randomness test
/// Calculates the Shannon entropy of a byte slice and outputs a p-value.
pub fn shannon(samples: &[u8]) -> f64 {
pub fn shannon(samples: &[u8]) -> (f64, f64, f64) {
let len = samples.len() as f64;
if len == 0.0 {
return 0.0;
return (-1.0, 0.0, 1.0);
}
// Count occurrences of each byte
@ -38,6 +38,6 @@ impl Onod {
let normal_dist = Normal::new(0.0, 1.0).expect("Failed to create Normal distribution");
let p_value = 2.0 * (1.0 - normal_dist.cdf(z_score.abs())); // Two-tailed test
p_value
(entropy, z_score, p_value)
}
}

View file

@ -6,7 +6,7 @@ impl Onod {
/// Shells randomness test
/// Evaluates the uniformity of distances between identical byte values and returns a p-value.
pub fn shells(input: &[u8]) -> f64 {
pub fn shells(input: &[u8]) -> (f64, f64, f64) {
// Define shell radii (precomputed to ensure equal volumes)
const SHELL_RADII: [f64; 35] = [
@ -24,8 +24,10 @@ impl Onod {
let samples = convert_to_3d_points(input);
if samples.len() < 25000 {
eprintln!("Shells test requires at least 25,000 points for statistical validity.");
return 0.0; // Skip the test for small datasets
// eprintln!("---------------------------------------------------------------");
// eprintln!("ERROR: Shells test requires at least 25,000 points for statistical validity. Skipping.");
// eprintln!("---------------------------------------------------------------");
return (-1.0, 0.0, 1.0); // Skip the test for small datasets
}
let sphere_radius = SHELL_RADII[0];
@ -75,7 +77,12 @@ impl Onod {
let chi_squared_dist = ChiSquared::new(degrees_of_freedom).expect("Failed to create ChiSquared distribution");
let p_value = 1.0 - chi_squared_dist.cdf(chi_squared_stat);
p_value
// Z-score calculation (standardization of the chi-squared statistic)
let mean = degrees_of_freedom; // Mean of the chi-squared distribution
let std_dev = (2.0 * degrees_of_freedom).sqrt(); // Standard deviation of the chi-squared distribution
let z_score = (chi_squared_stat - mean) / std_dev;
(chi_squared_stat, z_score, p_value)
}
}

View file

@ -6,12 +6,12 @@ impl Onod {
/// UnCorrelation randomness test
/// Computes the Pearson correlation between the sequence and its shifted version, returning a p-value.
pub fn un_correlation(input: &[u8]) -> f64 {
pub fn un_correlation(input: &[u8]) -> (f64, f64, f64) {
let samples = input.iter().map(|&x| x as i32).collect::<Vec<i32>>();
if samples.len() < 2 {
return 0.0; // Default to perfect randomness for insufficient data
return (-1.0, 0.0, 1.0); // Default to perfect randomness for insufficient data
}
// Convert samples to f64 for correlation computation
@ -51,7 +51,7 @@ impl Onod {
let normal_dist = Normal::new(0.0, 1.0).expect("Failed to create Normal distribution");
let p_value = 2.0 * (1.0 - normal_dist.cdf(t_stat.abs()));
p_value
(correlation, t_stat, p_value)
}
}