Skip to content

Statistics

Ten probability distributions, each implementing ContinuousDistribution<T> or DiscreteDistribution<T> traits.

Requires the stats Cargo feature (implies special):

numeris = { version = "0.2", features = ["stats"] }

All distributions work with f32 and f64, are no-std compatible, and have no heap allocation.

Distribution Traits

pub trait ContinuousDistribution<T> {
    fn pdf(&self, x: T) -> T;       // probability density function
    fn ln_pdf(&self, x: T) -> T;    // log-PDF (avoids underflow)
    fn cdf(&self, x: T) -> T;       // cumulative distribution function
    fn quantile(&self, p: T) -> T;  // inverse CDF: x such that P(X ≤ x) = p
    fn mean(&self) -> T;
    fn variance(&self) -> T;
}

pub trait DiscreteDistribution<T> {
    fn pmf(&self, k: u64) -> T;     // probability mass function P(X = k)
    fn ln_pmf(&self, k: u64) -> T;  // log-PMF (avoids underflow)
    fn cdf(&self, k: u64) -> T;     // cumulative P(X ≤ k)
    fn quantile(&self, p: T) -> u64; // smallest k such that P(X ≤ k) ≥ p
    fn mean(&self) -> T;
    fn variance(&self) -> T;
}

Continuous Distributions

Normal (Gaussian)

Normal<T> { mean: T, std_dev: T }

use numeris::stats::Normal;

let n = Normal::new(0.0_f64, 1.0).unwrap();  // standard normal N(0,1)
let p = n.pdf(0.0);      // 1/√(2π) ≈ 0.3989
let c = n.cdf(1.645);    // ≈ 0.95 (95th percentile)

let n2 = Normal::new(100.0_f64, 15.0).unwrap(); // IQ distribution
let prob_above_130 = 1.0 - n2.cdf(130.0);       // P(IQ > 130) ≈ 2.3%

Uniform

Uniform<T> { a: T, b: T } — continuous uniform on [a, b].

use numeris::stats::Uniform;

let u = Uniform::new(0.0_f64, 1.0).unwrap();
let p = u.pdf(0.5);      // 1.0 (constant density)
let c = u.cdf(0.3);      // 0.3
assert_eq!(u.mean(), 0.5);
assert!((u.variance() - 1.0/12.0).abs() < 1e-10);

Exponential

Exponential<T> { rate: T } — rate λ (mean = 1/λ).

use numeris::stats::Exponential;

let e = Exponential::new(2.0_f64).unwrap();  // λ=2, mean=0.5
let p = e.pdf(1.0);      // 2·e⁻² ≈ 0.2707
let c = e.cdf(1.0);      // 1 - e⁻² ≈ 0.8647
assert!((e.mean() - 0.5).abs() < 1e-12);

Gamma

Gamma<T> { shape: T, rate: T } — shape α, rate β (mean = α/β).

The chi-squared and exponential distributions are special cases.

use numeris::stats::Gamma;

let g = Gamma::new(2.0_f64, 1.0).unwrap();  // Gamma(α=2, β=1)
let p = g.pdf(1.0);   // 1·e⁻¹ ≈ 0.3679
let c = g.cdf(2.0);   // uses regularized incomplete gamma P(2, 2)
assert!((g.mean() - 2.0).abs() < 1e-10);
assert!((g.variance() - 2.0).abs() < 1e-10);

Beta

Beta<T> { alpha: T, beta: T } — shape parameters α, β. Support: [0, 1].

use numeris::stats::Beta;

let b = Beta::new(2.0_f64, 5.0).unwrap();
let p = b.pdf(0.3);    // uses regularized incomplete beta
let c = b.cdf(0.5);
assert!((b.mean() - 2.0/7.0).abs() < 1e-10);

Chi-Squared

ChiSquared<T> { k: T } — k degrees of freedom. Special case of Gamma(k/2, 1/2).

use numeris::stats::ChiSquared;

let chi = ChiSquared::new(3.0_f64).unwrap();  // χ²(3)
let p_val = 1.0 - chi.cdf(7.815);    // p-value for χ²=7.815 with 3 df ≈ 0.05
assert_eq!(chi.mean(), 3.0);
assert_eq!(chi.variance(), 6.0);

Student's t

StudentT<T> { nu: T } — ν degrees of freedom.

use numeris::stats::StudentT;

let t = StudentT::new(10.0_f64).unwrap();  // t(10)
let c = t.cdf(2.228);   // ≈ 0.975 (95% two-sided CI critical value)
let p = t.pdf(0.0);     // peak of distribution
assert_eq!(t.mean(), 0.0);
assert!((t.variance() - 10.0/8.0).abs() < 1e-10);  // ν/(ν-2)

CDF Comparison

Discrete Distributions

Bernoulli

Bernoulli<T> { p: T } — single trial with success probability p.

use numeris::stats::Bernoulli;

let b = Bernoulli::new(0.3_f64).unwrap();
assert!((b.pmf(1) - 0.3).abs() < 1e-12);
assert!((b.pmf(0) - 0.7).abs() < 1e-12);
assert!((b.mean() - 0.3).abs() < 1e-12);
assert!((b.variance() - 0.21).abs() < 1e-12);

Binomial

Binomial<T> { n: u64, p: T } — n independent Bernoulli trials.

use numeris::stats::Binomial;

let b = Binomial::new(10, 0.5_f64).unwrap();    // B(10, 0.5)
let p5 = b.pmf(5);    // P(X=5) ≈ 0.2461
let c7  = b.cdf(7);   // P(X≤7) ≈ 0.9453
assert!((b.mean() - 5.0).abs() < 1e-10);
assert!((b.variance() - 2.5).abs() < 1e-10);

Poisson

Poisson<T> { lambda: T } — number of events in a fixed interval.

use numeris::stats::Poisson;

let pois = Poisson::new(3.0_f64).unwrap();  // Poisson(λ=3)
let p3 = pois.pmf(3);    // P(X=3) = e⁻³·3³/3! ≈ 0.2240
let c5 = pois.cdf(5);    // P(X≤5) ≈ 0.9161
assert_eq!(pois.mean(), 3.0);
assert_eq!(pois.variance(), 3.0);

Distribution Summary Table

Distribution Type Parameters Mean Variance
Normal Continuous μ, σ μ σ²
Uniform Continuous a, b (a+b)/2 (b-a)²/12
Exponential Continuous λ 1/λ 1/λ²
Gamma Continuous α, β α/β α/β²
Beta Continuous α, β α/(α+β) αβ/((α+β)²(α+β+1))
ChiSquared Continuous k k 2k
StudentT Continuous ν 0 (ν>1) ν/(ν-2) (ν>2)
Bernoulli Discrete p p p(1-p)
Binomial Discrete n, p np np(1-p)
Poisson Discrete λ λ λ

Random Sampling

Every distribution provides sample() and sample_array() methods using the built-in [Rng] (xoshiro256++ PRNG). No external crate dependency.

use numeris::stats::{Rng, Normal, Uniform, Poisson};
use numeris::stats::{ContinuousDistribution, DiscreteDistribution};

let mut rng = Rng::new(42);  // seed with any u64

// Sample from a normal distribution
let n = Normal::new(0.0_f64, 1.0).unwrap();
let x = n.sample(&mut rng);

// Fill an array with 100 samples
let samples: [f64; 100] = n.sample_array(&mut rng);

// Works with all distributions
let u = Uniform::new(0.0_f64, 10.0).unwrap();
let y = u.sample(&mut rng);

let pois = Poisson::new(3.0_f64).unwrap();
let k = pois.sample(&mut rng);  // returns T (float), cast to integer if needed

The Rng struct is deterministic — same seed produces the same sequence, useful for reproducible simulations.

use numeris::stats::Rng;

let mut rng1 = Rng::new(123);
let mut rng2 = Rng::new(123);
assert_eq!(rng1.next_u64(), rng2.next_u64());
assert_eq!(rng1.next_f64(), rng2.next_f64());

Rng Methods

Method Returns
next_u64() Uniform u64
next_f64() Uniform f64 in [0, 1)
next_f32() Uniform f32 in [0, 1)
next_normal_f64() Standard normal f64 (Box-Muller)
next_normal_f32() Standard normal f32 (Box-Muller)

Sampling Algorithms

Distribution Algorithm
Normal Box-Muller transform
Uniform Inverse CDF (affine transform)
Exponential Inverse CDF (−ln(U)/λ)
Gamma Marsaglia & Tsang's method
Beta Ratio of two Gamma variates
Chi-Squared Gamma(k/2, 1/2)
Student's t Normal / √(χ²/ν)
Bernoulli Threshold on uniform
Binomial Sum of Bernoulli (small n) or normal approximation (large n)
Poisson Knuth's method (small λ) or normal approximation (large λ)

Quantile (Inverse CDF)

All distributions implement quantile() for computing percentiles:

use numeris::stats::{Normal, ContinuousDistribution};

let n = Normal::new(0.0_f64, 1.0).unwrap();
let z_95 = n.quantile(0.95);  // ≈ 1.645
let z_99 = n.quantile(0.99);  // ≈ 2.326
use numeris::stats::{Binomial, DiscreteDistribution};

let b = Binomial::new(20, 0.5_f64).unwrap();
let k = b.quantile(0.95);  // smallest k with P(X ≤ k) ≥ 0.95
assert_eq!(k, 14);

Error Handling

use numeris::stats::StatsError;

match Normal::new(0.0_f64, -1.0) {  // std_dev must be > 0
    Err(StatsError::InvalidParameter) => { /* bad parameter */ }
    Ok(n) => { /* use n */ }
}