1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#![cfg(not(miri))]
//! Reference-table tests for the Normal distribution against `cdfnor`.
mod common;
use cdflib::{ContinuousCdf, Normal};
use common::{assert_close_eps, read_csv, DEFAULT_ABS_TOL, DINVNR_REL_TOL, KERNEL_REL_TOL};
#[test]
fn cdf_and_ccdf_match_cdfnor_reference() {
for row in read_csv("tests/data/normal_cdf.csv") {
let [mean, sd, x, expected_cdf, expected_sf] = row[..] else {
panic!("width");
};
let n = Normal::new(mean, sd);
// Normal::cdf is a direct cumnor wrapper; no iterative routine.
assert_close_eps(n.cdf(x), expected_cdf, KERNEL_REL_TOL, DEFAULT_ABS_TOL);
assert_close_eps(n.ccdf(x), expected_sf, KERNEL_REL_TOL, DEFAULT_ABS_TOL);
}
}
#[test]
fn inverse_cdf_matches_cdfnor_reference() {
// Each row supplies (p, q) with full precision in *both* tails (one
// generated as cum, the other as ccum, neither via 1 - other). The
// trait single-arg API can only carry one of them with full
// precision, so we route each row to its accurate branch:
// - p ≤ 0.5 → inverse_cdf(p) is the accurate call
// - q ≤ 0.5 → inverse_sf(q) is the accurate call
for row in read_csv("tests/data/normal_inverse_cdf.csv") {
let [mean, sd, p, q, expected_x] = row[..] else {
panic!("width");
};
let n = Normal::new(mean, sd);
if p <= 0.5 {
assert_close_eps(
n.inverse_cdf(p).unwrap(),
expected_x,
DINVNR_REL_TOL,
DINVNR_REL_TOL,
);
}
if q <= 0.5 {
assert_close_eps(
n.inverse_ccdf(q).unwrap(),
expected_x,
DINVNR_REL_TOL,
DINVNR_REL_TOL,
);
}
}
}