pub fn normal_cdf(x: f64) -> f64Expand description
Standard normal CDF Phi(x) evaluated via the exact special-function identity
Phi(x) = 0.5 * erfc(-x / sqrt(2)).
This is the exact Gaussian CDF semantics used throughout the codebase. The
numerical erfc implementation may use internal approximations, but the
returned function is the standard normal CDF itself rather than a separate
polynomial surrogate surface.
Examples found in repository?
examples/special_audit_dump.rs (line 79)
33fn main() {
34 // ---- Bessel channels -------------------------------------------------
35 for eta in geometric_grid(1e-6, 1e12, 400) {
36 let (centered, ratio, d1) = special::bessel_i0_centered_terms(eta);
37 emit("bessel_centered_log", eta, centered);
38 emit("bessel_ratio", eta, ratio);
39 emit("bessel_d1", eta, d1);
40 emit(
41 "bessel_d2",
42 eta,
43 special::bessel_i0_centered_second_log_derivative_from_log_abs(eta.ln()),
44 );
45 }
46 // Dense sweep across the ascending/asymptotic seam at 20.
47 for eta in linear_grid(15.0, 25.0, 201) {
48 let (centered, ratio, d1) = special::bessel_i0_centered_terms(eta);
49 emit("bessel_centered_log", eta, centered);
50 emit("bessel_ratio", eta, ratio);
51 emit("bessel_d1", eta, d1);
52 emit(
53 "bessel_d2",
54 eta,
55 special::bessel_i0_centered_second_log_derivative_from_log_abs(eta.ln()),
56 );
57 }
58
59 // ---- Polygamma family ------------------------------------------------
60 for x in geometric_grid(1e-8, 1e10, 400) {
61 emit("digamma", x, special::digamma(x));
62 emit("trigamma", x, special::trigamma(x));
63 emit("tetragamma", x, special::tetragamma(x));
64 emit("pentagamma", x, special::pentagamma(x));
65 }
66 for x in linear_grid(0.5, 30.0, 300) {
67 emit("digamma", x, special::digamma(x));
68 emit("trigamma", x, special::trigamma(x));
69 emit("tetragamma", x, special::tetragamma(x));
70 emit("pentagamma", x, special::pentagamma(x));
71 }
72
73 // ---- Normal-distribution channels ------------------------------------
74 let mut normal_args: Vec<f64> = linear_grid(-40.0, 40.0, 801);
75 normal_args.extend(geometric_grid(1e-8, 1e2, 200));
76 normal_args.extend(geometric_grid(1e-8, 1e2, 200).into_iter().map(|v| -v));
77 for x in normal_args {
78 emit("normal_pdf", x, prob::normal_pdf(x));
79 emit("normal_cdf", x, prob::normal_cdf(x));
80 // The upper tail as its own quantity. `1 - normal_cdf(x)` is exactly
81 // zero above x ~ 8.3 and 7% high already at x = 8 (#2562); this channel
82 // exists so that cannot silently return.
83 emit("normal_sf", x, prob::normal_sf(x));
84 emit("normal_logcdf", x, prob::normal_logcdf(x));
85 emit("normal_logsf", x, prob::normal_logsf(x));
86 let (log_cdf, mills) = prob::signed_probit_logcdf_and_mills_ratio(x);
87 emit("probit_logcdf", x, log_cdf);
88 emit("probit_mills", x, mills);
89 let derivatives = prob::normal_logcdf_derivatives(x);
90 for (order, value) in derivatives.iter().enumerate() {
91 emit(&format!("logcdf_d{order}"), x, *value);
92 }
93 if x >= 0.0 {
94 emit("erfcx", x, prob::erfcx_nonnegative(x));
95 }
96 }
97 for x in geometric_grid(1e-3, 1e6, 400) {
98 emit("erfcx", x, prob::erfcx_nonnegative(x));
99 }
100
101 // ---- log1mexp --------------------------------------------------------
102 for a in geometric_grid(1e-14, 50.0, 300) {
103 emit("log1mexp", a, prob::log1mexp_positive(a));
104 }
105
106 // ---- Normal quantile -------------------------------------------------
107 for p in geometric_grid(1e-300, 0.5, 400) {
108 if let Ok(q) = prob::standard_normal_quantile(p) {
109 emit("normal_quantile", p, q);
110 }
111 }
112 for p in linear_grid(0.001, 0.999, 400) {
113 if let Ok(q) = prob::standard_normal_quantile(p) {
114 emit("normal_quantile", p, q);
115 }
116 }
117 for log_p in linear_grid(-700.0, -1e-6, 400) {
118 if let Ok(q) = prob::standard_normal_quantile_from_log_cdf(log_p) {
119 emit("normal_quantile_from_log", log_p, q);
120 }
121 }
122
123 // ---- Gauss-Legendre --------------------------------------------------
124 for n in [
125 3usize, 4, 5, 7, 8, 12, 15, 16, 20, 24, 31, 32, 40, 48, 63, 64, 80, 96, 100, 127, 128, 160,
126 200, 256,
127 ] {
128 let (nodes, weights) = special::gauss_legendre(n);
129 for (i, (node, weight)) in nodes.iter().zip(weights.iter()).enumerate() {
130 println!("gl_node\t{n}\t{i}\t{node:e}");
131 println!("gl_weight\t{n}\t{i}\t{weight:e}");
132 }
133 }
134
135 // ---- Binomial coefficient --------------------------------------------
136 for n in 0usize..=60 {
137 for k in 0..=n {
138 println!(
139 "binomial\t{n}\t{k}\t{:e}",
140 special::binomial_coefficient_f64(n, k)
141 );
142 }
143 }
144
145 // Beta quantiles. The shapes are the ones a beta-regression predictive
146 // interval produces from a mean and a variance, plus a direct sweep of
147 // small and large shape pairs. The lower tail here reaches quantiles far
148 // below `f64::EPSILON`, which is exactly where a solver with an absolute
149 // tolerance in `x` stalls rather than degrading (#2528), so the sweep is
150 // deliberately weighted toward small `a`.
151 for (mu, variance_fraction) in [
152 (0.001_f64, 0.3_f64),
153 (0.01, 0.2),
154 (0.01, 0.5),
155 (0.02, 0.3),
156 (0.05, 0.5),
157 (0.1, 0.5),
158 (0.3, 0.5),
159 (0.5, 0.5),
160 (0.7, 0.5),
161 (0.9, 0.2),
162 ] {
163 let bernoulli_variance = mu * (1.0 - mu);
164 let precision = 1.0 / variance_fraction - 1.0;
165 let (a, b) = (mu * precision, (1.0 - mu) * precision);
166 for p in [0.001_f64, 0.025, 0.1, 0.5, 0.9, 0.975, 0.999] {
167 println!(
168 "beta_quantile\t{a:e}\t{b:e}\t{p:e}\t{:e}",
169 prob::beta_quantile(p, a, b)
170 );
171 }
172 // Keep the moment-matched variance in the record so a reader can see
173 // which mean produced which shape pair.
174 println!("beta_shape\t{mu:e}\t{bernoulli_variance:e}\t{a:e}\t{b:e}");
175 }
176 for (a, b) in [
177 (0.1_f64, 0.1_f64),
178 (0.5, 0.5),
179 (0.5, 20.0),
180 (20.0, 0.5),
181 (1.0, 1.0),
182 (2.0, 3.0),
183 (2.5, 7.5),
184 (100.0, 100.0),
185 (1000.0, 5.0),
186 (5.0, 1000.0),
187 ] {
188 for p in [
189 1.0e-8_f64, 1.0e-4, 0.001, 0.01, 0.025, 0.1, 0.25, 0.5, 0.75, 0.9, 0.975, 0.999,
190 ] {
191 println!(
192 "beta_quantile\t{a:e}\t{b:e}\t{p:e}\t{:e}",
193 prob::beta_quantile(p, a, b)
194 );
195 }
196 }
197
198 // ---- Student-t survival function -------------------------------------
199 // Two-argument channel (nu, t). The complement `1 - cdf` saturates SOONER
200 // the larger nu is -- already at nu = 500, t = 10, where the true tail is
201 // 6.9e-22 -- because the loss is in the subtraction, not in the cdf (#2562).
202 for nu in [1.0_f64, 2.5, 5.0, 30.0, 120.0, 500.0, 5000.0, 1.0e4] {
203 for t in [
204 0.0_f64, 0.5, 1.0, 2.0, 4.0, 6.0, 8.0, 10.0, 15.0, 20.0, 30.0, 40.0, 80.0,
205 ] {
206 println!(
207 "students_t_sf\t{nu:e}\t{t:e}\t{:e}",
208 prob::student_t_sf(t, nu)
209 );
210 println!(
211 "students_t_sf\t{nu:e}\t{:e}\t{:e}",
212 -t,
213 prob::student_t_sf(-t, nu)
214 );
215 }
216 }
217}