Coverage Report

Created: 2026-11-03 08:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/rand_distr-0.5.1/src/utils.rs
Line
Count
Source
1
// Copyright 2018 Developers of the Rand project.
2
//
3
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6
// option. This file may not be copied, modified, or distributed
7
// except according to those terms.
8
9
//! Math helper functions
10
11
use crate::ziggurat_tables;
12
#[allow(unused_imports)]
13
use num_traits::Float; // Used for `no_std` to get `f64::abs()` working before `rustc 1.84`
14
use rand::distr::hidden_export::IntoFloat;
15
use rand::Rng;
16
17
/// Sample a random number using the Ziggurat method (specifically the
18
/// ZIGNOR variant from Doornik 2005). Most of the arguments are
19
/// directly from the paper:
20
///
21
/// * `rng`: source of randomness
22
/// * `symmetric`: whether this is a symmetric distribution, or one-sided with P(x < 0) = 0.
23
/// * `X`: the $x_i$ abscissae.
24
/// * `F`: precomputed values of the PDF at the $x_i$, (i.e. $f(x_i)$)
25
/// * `F_DIFF`: precomputed values of $f(x_i) - f(x_{i+1})$
26
/// * `pdf`: the probability density function
27
/// * `zero_case`: manual sampling from the tail when we chose the
28
///    bottom box (i.e. i == 0)
29
#[inline(always)] // Forced inlining improves the perf by 25-50%
30
0
pub(crate) fn ziggurat<R: Rng + ?Sized, P, Z>(
31
0
    rng: &mut R,
32
0
    symmetric: bool,
33
0
    x_tab: ziggurat_tables::ZigTable,
34
0
    f_tab: ziggurat_tables::ZigTable,
35
0
    mut pdf: P,
36
0
    mut zero_case: Z,
37
0
) -> f64
38
0
where
39
0
    P: FnMut(f64) -> f64,
40
0
    Z: FnMut(&mut R, f64) -> f64,
41
{
42
    loop {
43
        // As an optimisation we re-implement the conversion to a f64.
44
        // From the remaining 12 most significant bits we use 8 to construct `i`.
45
        // This saves us generating a whole extra random number, while the added
46
        // precision of using 64 bits for f64 does not buy us much.
47
0
        let bits = rng.next_u64();
48
0
        let i = bits as usize & 0xff;
49
50
0
        let u = if symmetric {
51
            // Convert to a value in the range [2,4) and subtract to get [-1,1)
52
            // We can't convert to an open range directly, that would require
53
            // subtracting `3.0 - EPSILON`, which is not representable.
54
            // It is possible with an extra step, but an open range does not
55
            // seem necessary for the ziggurat algorithm anyway.
56
0
            (bits >> 12).into_float_with_exponent(1) - 3.0
57
        } else {
58
            // Convert to a value in the range [1,2) and subtract to get (0,1)
59
0
            (bits >> 12).into_float_with_exponent(0) - (1.0 - f64::EPSILON / 2.0)
60
        };
61
0
        let x = u * x_tab[i];
62
63
0
        let test_x = if symmetric { x.abs() } else { x };
64
65
        // algebraically equivalent to |u| < x_tab[i+1]/x_tab[i] (or u < x_tab[i+1]/x_tab[i])
66
0
        if test_x < x_tab[i + 1] {
67
0
            return x;
68
0
        }
69
0
        if i == 0 {
70
0
            return zero_case(rng, u);
71
0
        }
72
        // algebraically equivalent to f1 + DRanU()*(f0 - f1) < 1
73
0
        if f_tab[i + 1] + (f_tab[i] - f_tab[i + 1]) * rng.random::<f64>() < pdf(x) {
74
0
            return x;
75
0
        }
76
    }
77
0
}
Unexecuted instantiation: rand_distr::utils::ziggurat::<rand::rngs::std::StdRng, <rand_distr::normal::StandardNormal as rand::distr::distribution::Distribution<f64>>::sample::pdf, <rand_distr::normal::StandardNormal as rand::distr::distribution::Distribution<f64>>::sample::zero_case<rand::rngs::std::StdRng>>
Unexecuted instantiation: rand_distr::utils::ziggurat::<_, _, _>