statcpp
C++17 Header-Only Statistics Library
Loading...
Searching...
No Matches
special_functions.hpp
Go to the documentation of this file.
1
9#pragma once
10
11#include <cmath>
12#include <limits>
13#include <stdexcept>
14
15namespace statcpp {
16
17// ============================================================================
18// Constants
19// ============================================================================
20
24inline constexpr double pi = 3.14159265358979323846;
25
29inline constexpr double sqrt_2 = 1.41421356237309504880;
30
34inline constexpr double sqrt_2_pi = 2.50662827463100050242;
35
39inline constexpr double log_sqrt_2_pi = 0.91893853320467274178;
40
41// ============================================================================
42// Gamma Function / Log-Gamma Function
43// ============================================================================
44
57inline double lgamma_impl(double x)
58{
59 if (x <= 0.0 && x == std::floor(x)) {
60 throw std::domain_error("statcpp::lgamma: non-positive integer argument");
61 }
62
63 // Lanczos coefficients for g=7
64 static const double c[9] = {
65 0.99999999999980993,
66 676.5203681218851,
67 -1259.1392167224028,
68 771.32342877765313,
69 -176.61502916214059,
70 12.507343278686905,
71 -0.13857109526572012,
72 9.9843695780195716e-6,
73 1.5056327351493116e-7
74 };
75
76 if (x < 0.5) {
77 // Reflection formula: Gamma(x) * Gamma(1-x) = pi / sin(pi*x)
78 return std::log(pi / std::sin(pi * x)) - lgamma_impl(1.0 - x);
79 }
80
81 x -= 1.0;
82 double a = c[0];
83 for (int i = 1; i < 9; ++i) {
84 a += c[i] / (x + static_cast<double>(i));
85 }
86
87 double t = x + 7.5;
88 return log_sqrt_2_pi + (x + 0.5) * std::log(t) - t + std::log(a);
89}
90
100inline double lgamma(double x)
101{
102 return lgamma_impl(x);
103}
104
116inline double tgamma(double x)
117{
118 if (x <= 0.0 && x == std::floor(x)) {
119 throw std::domain_error("statcpp::tgamma: non-positive integer argument");
120 }
121
122 // For small positive integers, use factorial
123 if (x > 0.0 && x <= 20.0 && x == std::floor(x)) {
124 double result = 1.0;
125 for (int i = 2; i < static_cast<int>(x); ++i) {
126 result *= i;
127 }
128 return result;
129 }
130
131 return std::exp(lgamma_impl(x));
132}
133
134// ============================================================================
135// Beta Function / Incomplete Beta Function
136// ============================================================================
137
148inline double beta(double a, double b)
149{
150 if (a <= 0.0 || b <= 0.0) {
151 throw std::domain_error("statcpp::beta: parameters must be positive");
152 }
153 return std::exp(lgamma(a) + lgamma(b) - lgamma(a + b));
154}
155
166inline double lbeta(double a, double b)
167{
168 if (a <= 0.0 || b <= 0.0) {
169 throw std::domain_error("statcpp::lbeta: parameters must be positive");
170 }
171 return lgamma(a) + lgamma(b) - lgamma(a + b);
172}
173
189inline double betainc_impl(double a, double b, double x, int recursion_depth)
190{
191 // Recursion depth check (prevent infinite recursion)
192 // At most 1 recursive call is made via the symmetry relation I_x(a,b) = 1 - I_{1-x}(b,a).
193 // After that single redirect the continued fraction always converges, so depth > 1 is unreachable.
194 // We keep the guard at depth > 1 to be safe and ensure we never loop.
195 if (recursion_depth > 1) {
196 throw std::runtime_error("statcpp::betainc: maximum recursion depth exceeded");
197 }
198
199 // Use symmetry relation for better convergence
200 // I_x(a, b) = 1 - I_{1-x}(b, a)
201 if (x > (a + 1.0) / (a + b + 2.0)) {
202 return 1.0 - betainc_impl(b, a, 1.0 - x, recursion_depth + 1);
203 }
204
205 // Continued fraction (Lentz's algorithm)
206 const double eps = std::numeric_limits<double>::epsilon();
207 const double tiny = std::numeric_limits<double>::min();
208 const int max_iter = 200;
209
210 double qab = a + b;
211 double qap = a + 1.0;
212 double qam = a - 1.0;
213
214 double c = 1.0;
215 double d = 1.0 - qab * x / qap;
216 if (std::abs(d) < tiny) d = tiny;
217 d = 1.0 / d;
218 double h = d;
219
220 for (int m = 1; m <= max_iter; ++m) {
221 int m2 = 2 * m;
222
223 // Even step
224 double aa = m * (b - m) * x / ((qam + m2) * (a + m2));
225 d = 1.0 + aa * d;
226 if (std::abs(d) < tiny) d = tiny;
227 c = 1.0 + aa / c;
228 if (std::abs(c) < tiny) c = tiny;
229 d = 1.0 / d;
230 h *= d * c;
231
232 // Odd step
233 aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2));
234 d = 1.0 + aa * d;
235 if (std::abs(d) < tiny) d = tiny;
236 c = 1.0 + aa / c;
237 if (std::abs(c) < tiny) c = tiny;
238 d = 1.0 / d;
239 double del = d * c;
240 h *= del;
241
242 if (std::abs(del - 1.0) < eps) {
243 break;
244 }
245 }
246
247 double front = std::exp(a * std::log(x) + b * std::log(1.0 - x) - lbeta(a, b)) / a;
248 return front * h;
249}
250
264inline double betainc(double a, double b, double x)
265{
266 if (a <= 0.0 || b <= 0.0) {
267 throw std::domain_error("statcpp::betainc: parameters must be positive");
268 }
269 if (x < 0.0 || x > 1.0) {
270 throw std::domain_error("statcpp::betainc: x must be in [0, 1]");
271 }
272 if (x == 0.0) return 0.0;
273 if (x == 1.0) return 1.0;
274
275 return betainc_impl(a, b, x, 0);
276}
277
291inline double betaincinv(double a, double b, double p)
292{
293 if (a <= 0.0 || b <= 0.0) {
294 throw std::domain_error("statcpp::betaincinv: parameters must be positive");
295 }
296 if (p < 0.0 || p > 1.0) {
297 throw std::domain_error("statcpp::betaincinv: p must be in [0, 1]");
298 }
299 if (p == 0.0) return 0.0;
300 if (p == 1.0) return 1.0;
301
302 const double eps = 1e-10;
303 const int max_iter = 100;
304
305 // Initial guess using approximation
306 double x = a / (a + b);
307 if (a < 1.0 || b < 1.0) {
308 x = 0.5;
309 }
310
311 // Newton-Raphson with bisection fallback
312 double lo = 0.0, hi = 1.0;
313
314 for (int i = 0; i < max_iter; ++i) {
315 double f = betainc(a, b, x) - p;
316
317 if (std::abs(f) < eps) {
318 return x;
319 }
320
321 // Update bisection bounds
322 if (f < 0.0) {
323 lo = x;
324 } else {
325 hi = x;
326 }
327
328 // Derivative: d/dx I_x(a,b) = x^(a-1) * (1-x)^(b-1) / B(a,b)
329 double df = std::exp((a - 1.0) * std::log(x) + (b - 1.0) * std::log(1.0 - x) - lbeta(a, b));
330
331 double dx = f / df;
332 double x_new = x - dx;
333
334 // Use bisection if Newton step goes out of bounds
335 if (x_new <= lo || x_new >= hi) {
336 x_new = (lo + hi) / 2.0;
337 }
338
339 if (std::abs(x_new - x) < eps * x) {
340 return x_new;
341 }
342
343 x = x_new;
344 }
345
346 return x;
347}
348
349// ============================================================================
350// Error Function (erf / erfc)
351// ============================================================================
352
364inline double erf(double x)
365{
366 return std::erf(x);
367}
368
377inline double erfc(double x)
378{
379 return std::erfc(x);
380}
381
382// ============================================================================
383// Normal Distribution CDF and Quantile (Phi and Phi^{-1})
384// ============================================================================
385
394inline double norm_cdf(double x)
395{
396 return 0.5 * (1.0 + erf(x / sqrt_2));
397}
398
410inline double norm_quantile(double p)
411{
412 if (p <= 0.0) {
413 return -std::numeric_limits<double>::infinity();
414 }
415 if (p >= 1.0) {
416 return std::numeric_limits<double>::infinity();
417 }
418
419 // Coefficients for rational approximation
420 static const double a[6] = {
421 -3.969683028665376e+01,
422 2.209460984245205e+02,
423 -2.759285104469687e+02,
424 1.383577518672690e+02,
425 -3.066479806614716e+01,
426 2.506628277459239e+00
427 };
428 static const double b[5] = {
429 -5.447609879822406e+01,
430 1.615858368580409e+02,
431 -1.556989798598866e+02,
432 6.680131188771972e+01,
433 -1.328068155288572e+01
434 };
435 static const double c[6] = {
436 -7.784894002430293e-03,
437 -3.223964580411365e-01,
438 -2.400758277161838e+00,
439 -2.549732539343734e+00,
440 4.374664141464968e+00,
441 2.938163982698783e+00
442 };
443 static const double d[4] = {
444 7.784695709041462e-03,
445 3.224671290700398e-01,
446 2.445134137142996e+00,
447 3.754408661907416e+00
448 };
449
450 const double p_low = 0.02425;
451 const double p_high = 1.0 - p_low;
452
453 double q, r;
454
455 if (p < p_low) {
456 // Rational approximation for lower region
457 q = std::sqrt(-2.0 * std::log(p));
458 return (((((c[0]*q+c[1])*q+c[2])*q+c[3])*q+c[4])*q+c[5]) /
459 ((((d[0]*q+d[1])*q+d[2])*q+d[3])*q+1.0);
460 } else if (p <= p_high) {
461 // Rational approximation for central region
462 q = p - 0.5;
463 r = q * q;
464 return (((((a[0]*r+a[1])*r+a[2])*r+a[3])*r+a[4])*r+a[5])*q /
465 (((((b[0]*r+b[1])*r+b[2])*r+b[3])*r+b[4])*r+1.0);
466 } else {
467 // Rational approximation for upper region
468 q = std::sqrt(-2.0 * std::log(1.0 - p));
469 return -(((((c[0]*q+c[1])*q+c[2])*q+c[3])*q+c[4])*q+c[5]) /
470 ((((d[0]*q+d[1])*q+d[2])*q+d[3])*q+1.0);
471 }
472}
473
474// ============================================================================
475// Lower Incomplete Gamma Function (for chi-square, gamma distributions)
476// ============================================================================
477
490inline double gammainc_lower(double a, double x)
491{
492 if (a <= 0.0) {
493 throw std::domain_error("statcpp::gammainc_lower: a must be positive");
494 }
495 if (x < 0.0) {
496 throw std::domain_error("statcpp::gammainc_lower: x must be non-negative");
497 }
498 if (x == 0.0) return 0.0;
499
500 const double eps = std::numeric_limits<double>::epsilon();
501 const int max_iter = 200;
502
503 // Use series expansion for x < a + 1
504 if (x < a + 1.0) {
505 double term = 1.0 / a;
506 double sum = term;
507 for (int n = 1; n <= max_iter; ++n) {
508 term *= x / (a + n);
509 sum += term;
510 if (std::abs(term) < eps * std::abs(sum)) {
511 break;
512 }
513 }
514 return sum * std::exp(-x + a * std::log(x) - lgamma(a));
515 }
516
517 // Use continued fraction for x >= a + 1 (Lentz's algorithm)
518 const double tiny = std::numeric_limits<double>::min();
519
520 double b = x + 1.0 - a;
521 double c = 1.0 / tiny;
522 double d = 1.0 / b;
523 double h = d;
524
525 for (int i = 1; i <= max_iter; ++i) {
526 double an = -i * (i - a);
527 b += 2.0;
528 d = an * d + b;
529 if (std::abs(d) < tiny) d = tiny;
530 c = b + an / c;
531 if (std::abs(c) < tiny) c = tiny;
532 d = 1.0 / d;
533 double del = d * c;
534 h *= del;
535 if (std::abs(del - 1.0) < eps) {
536 break;
537 }
538 }
539
540 // Q(a,x) = 1 - P(a,x), where we computed Q via continued fraction
541 double q = std::exp(-x + a * std::log(x) - lgamma(a)) * h;
542 return 1.0 - q;
543}
544
555inline double gammainc_upper(double a, double x)
556{
557 return 1.0 - gammainc_lower(a, x);
558}
559
573inline double gammainc_lower_inv(double a, double p)
574{
575 if (a <= 0.0) {
576 throw std::domain_error("statcpp::gammainc_lower_inv: a must be positive");
577 }
578 if (p < 0.0 || p > 1.0) {
579 throw std::domain_error("statcpp::gammainc_lower_inv: p must be in [0, 1]");
580 }
581 if (p == 0.0) return 0.0;
582 if (p == 1.0) return std::numeric_limits<double>::infinity();
583
584 const double eps = 1e-10;
585 const int max_iter = 100;
586
587 // Initial guess
588 double x;
589 if (a > 1.0) {
590 // Use Wilson-Hilferty approximation
591 double t = norm_quantile(p);
592 double v = t * std::sqrt(1.0 / (9.0 * a)) + 1.0 - 1.0 / (9.0 * a);
593 x = a * v * v * v;
594 if (x <= 0.0) x = 0.5;
595 } else {
596 x = std::pow(p * tgamma(a + 1.0), 1.0 / a);
597 }
598
599 // 二分法の探索範囲を初期化
600 double lo = 0.0;
601 double hi = a + 50.0 * std::sqrt(a);
602 if (hi < 10.0) hi = 10.0;
603
604 // hi が実際に上界であることを保証する
605 while (gammainc_lower(a, hi) < p) {
606 hi *= 2.0;
607 }
608
609 bool converged = false;
610
611 // Newton-Raphson (二分法の範囲追跡付き)
612 for (int i = 0; i < max_iter; ++i) {
613 double f = gammainc_lower(a, x) - p;
614 if (std::abs(f) < eps) {
615 converged = true;
616 break;
617 }
618
619 // 二分法の範囲を更新
620 if (f < 0.0) {
621 lo = x;
622 } else {
623 hi = x;
624 }
625
626 // Derivative: d/dx P(a,x) = x^(a-1) * e^(-x) / Gamma(a)
627 double df = std::exp((a - 1.0) * std::log(x) - x - lgamma(a));
628 if (df == 0.0) {
629 // 導関数がゼロ: 二分法フォールバックへ
630 break;
631 }
632
633 double x_new = x - f / df;
634
635 // Newton ステップが範囲外なら二分法を使う
636 if (x_new <= lo || x_new >= hi) {
637 x_new = (lo + hi) / 2.0;
638 }
639
640 if (std::abs(x_new - x) < eps * x) {
641 converged = true;
642 x = x_new;
643 break;
644 }
645
646 x = x_new;
647 }
648
649 // Newton-Raphson が収束しなかった場合の二分法フォールバック
650 if (!converged) {
651 for (int bisect_iter = 0; bisect_iter < 100; ++bisect_iter) {
652 double mid = (lo + hi) / 2.0;
653 double f_mid = gammainc_lower(a, mid) - p;
654 if (std::abs(f_mid) < 1e-12 || (hi - lo) < 1e-12 * mid) {
655 return mid;
656 }
657 if (f_mid < 0) {
658 lo = mid;
659 } else {
660 hi = mid;
661 }
662 }
663 return (lo + hi) / 2.0;
664 }
665
666 return x;
667}
668
669} // namespace statcpp
double betainc(double a, double b, double x)
Regularized incomplete beta function.
double lbeta(double a, double b)
Log-beta function.
double tgamma(double x)
Gamma function.
double erf(double x)
Error function.
constexpr double pi
Pi constant.
double lgamma_impl(double x)
Internal log-gamma function implementation.
auto sum(Iterator first, Iterator last)
Sum.
double norm_cdf(double x)
Standard normal CDF.
double erfc(double x)
Complementary error function.
double norm_quantile(double p)
Standard normal quantile function.
constexpr double sqrt_2
Square root of 2.
constexpr double sqrt_2_pi
Square root of 2*pi.
double gammainc_lower_inv(double a, double p)
Inverse lower regularized incomplete gamma function.
double betainc_impl(double a, double b, double x, int recursion_depth)
Internal regularized incomplete beta function.
double beta(double a, double b)
Beta function.
double betaincinv(double a, double b, double p)
Inverse regularized incomplete beta function.
double gammainc_lower(double a, double x)
Lower regularized incomplete gamma function.
constexpr double log_sqrt_2_pi
Natural logarithm of sqrt(2*pi)
double gammainc_upper(double a, double x)
Upper regularized incomplete gamma function.
double lgamma(double x)
Log-gamma function.