pub fn normal_logcdf(x: f64) -> f64Expand description
Numerically stable ln Φ(x) for the standard normal CDF. For x ≥ 0,
evaluates ln(1 - 0.5 erfc(x/sqrt(2))) with ln_1p, retaining the small
negative result after Φ(x) itself rounds to one. For x < 0, rewrites
ln Φ(x) = −u² + ln(½·erfcx(u)), u = −x/√2,
which preserves digits throughout the representable left tail without a
probability floor. Returns the corresponding IEEE limit at infinities and
propagates NaN.
Examples found in repository?
examples/special_audit_dump.rs (line 84)
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}