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 // Evaluated through erfc rather than as 0.5 (1 + erf(x / sqrt(2))). In the
397 // left tail erf approaches -1, so that sum cancels catastrophically: it loses
398 // all significance below x = -5.8 and underflows to exactly zero below
399 // x = -8.33, where the true value is still far inside the representable
400 // range. The complementary form is computed directly by std::erfc and stays
401 // accurate over the whole domain.
402 return 0.5 * erfc(-x / sqrt_2);
403}
404
421inline double norm_sf(double x)
422{
423 return 0.5 * erfc(x / sqrt_2);
424}
425
437inline double norm_quantile(double p)
438{
439 if (p <= 0.0) {
440 return -std::numeric_limits<double>::infinity();
441 }
442 if (p >= 1.0) {
443 return std::numeric_limits<double>::infinity();
444 }
445
446 // Coefficients for rational approximation
447 static const double a[6] = {
448 -3.969683028665376e+01,
449 2.209460984245205e+02,
450 -2.759285104469687e+02,
451 1.383577518672690e+02,
452 -3.066479806614716e+01,
453 2.506628277459239e+00
454 };
455 static const double b[5] = {
456 -5.447609879822406e+01,
457 1.615858368580409e+02,
458 -1.556989798598866e+02,
459 6.680131188771972e+01,
460 -1.328068155288572e+01
461 };
462 static const double c[6] = {
463 -7.784894002430293e-03,
464 -3.223964580411365e-01,
465 -2.400758277161838e+00,
466 -2.549732539343734e+00,
467 4.374664141464968e+00,
468 2.938163982698783e+00
469 };
470 static const double d[4] = {
471 7.784695709041462e-03,
472 3.224671290700398e-01,
473 2.445134137142996e+00,
474 3.754408661907416e+00
475 };
476
477 const double p_low = 0.02425;
478 const double p_high = 1.0 - p_low;
479
480 double q, r;
481
482 if (p < p_low) {
483 // Rational approximation for lower region
484 q = std::sqrt(-2.0 * std::log(p));
485 return (((((c[0]*q+c[1])*q+c[2])*q+c[3])*q+c[4])*q+c[5]) /
486 ((((d[0]*q+d[1])*q+d[2])*q+d[3])*q+1.0);
487 } else if (p <= p_high) {
488 // Rational approximation for central region
489 q = p - 0.5;
490 r = q * q;
491 return (((((a[0]*r+a[1])*r+a[2])*r+a[3])*r+a[4])*r+a[5])*q /
492 (((((b[0]*r+b[1])*r+b[2])*r+b[3])*r+b[4])*r+1.0);
493 } else {
494 // Rational approximation for upper region
495 q = std::sqrt(-2.0 * std::log(1.0 - p));
496 return -(((((c[0]*q+c[1])*q+c[2])*q+c[3])*q+c[4])*q+c[5]) /
497 ((((d[0]*q+d[1])*q+d[2])*q+d[3])*q+1.0);
498 }
499}
500
501// ============================================================================
502// Lower Incomplete Gamma Function (for chi-square, gamma distributions)
503// ============================================================================
504
517inline double gammainc_lower(double a, double x)
518{
519 if (a <= 0.0) {
520 throw std::domain_error("statcpp::gammainc_lower: a must be positive");
521 }
522 if (x < 0.0) {
523 throw std::domain_error("statcpp::gammainc_lower: x must be non-negative");
524 }
525 if (x == 0.0) return 0.0;
526
527 const double eps = std::numeric_limits<double>::epsilon();
528 const int max_iter = 200;
529
530 // Use series expansion for x < a + 1
531 if (x < a + 1.0) {
532 double term = 1.0 / a;
533 double sum = term;
534 for (int n = 1; n <= max_iter; ++n) {
535 term *= x / (a + n);
536 sum += term;
537 if (std::abs(term) < eps * std::abs(sum)) {
538 break;
539 }
540 }
541 return sum * std::exp(-x + a * std::log(x) - lgamma(a));
542 }
543
544 // Use continued fraction for x >= a + 1 (Lentz's algorithm)
545 const double tiny = std::numeric_limits<double>::min();
546
547 double b = x + 1.0 - a;
548 double c = 1.0 / tiny;
549 double d = 1.0 / b;
550 double h = d;
551
552 for (int i = 1; i <= max_iter; ++i) {
553 double an = -i * (i - a);
554 b += 2.0;
555 d = an * d + b;
556 if (std::abs(d) < tiny) d = tiny;
557 c = b + an / c;
558 if (std::abs(c) < tiny) c = tiny;
559 d = 1.0 / d;
560 double del = d * c;
561 h *= del;
562 if (std::abs(del - 1.0) < eps) {
563 break;
564 }
565 }
566
567 // Q(a,x) = 1 - P(a,x), where we computed Q via continued fraction
568 double q = std::exp(-x + a * std::log(x) - lgamma(a)) * h;
569 return 1.0 - q;
570}
571
582inline double gammainc_upper(double a, double x)
583{
584 return 1.0 - gammainc_lower(a, x);
585}
586
600inline double gammainc_lower_inv(double a, double p)
601{
602 if (a <= 0.0) {
603 throw std::domain_error("statcpp::gammainc_lower_inv: a must be positive");
604 }
605 if (p < 0.0 || p > 1.0) {
606 throw std::domain_error("statcpp::gammainc_lower_inv: p must be in [0, 1]");
607 }
608 if (p == 0.0) return 0.0;
609 if (p == 1.0) return std::numeric_limits<double>::infinity();
610
611 const double eps = 1e-10;
612 const int max_iter = 100;
613
614 // Initial guess
615 double x;
616 if (a > 1.0) {
617 // Use Wilson-Hilferty approximation
618 double t = norm_quantile(p);
619 double v = t * std::sqrt(1.0 / (9.0 * a)) + 1.0 - 1.0 / (9.0 * a);
620 x = a * v * v * v;
621 if (x <= 0.0) x = 0.5;
622 } else {
623 x = std::pow(p * tgamma(a + 1.0), 1.0 / a);
624 }
625
626 // 二分法の探索範囲を初期化
627 double lo = 0.0;
628 double hi = a + 50.0 * std::sqrt(a);
629 if (hi < 10.0) hi = 10.0;
630
631 // hi が実際に上界であることを保証する
632 while (gammainc_lower(a, hi) < p) {
633 hi *= 2.0;
634 }
635
636 bool converged = false;
637
638 // Newton-Raphson (二分法の範囲追跡付き)
639 for (int i = 0; i < max_iter; ++i) {
640 double f = gammainc_lower(a, x) - p;
641 if (std::abs(f) < eps) {
642 converged = true;
643 break;
644 }
645
646 // 二分法の範囲を更新
647 if (f < 0.0) {
648 lo = x;
649 } else {
650 hi = x;
651 }
652
653 // Derivative: d/dx P(a,x) = x^(a-1) * e^(-x) / Gamma(a)
654 double df = std::exp((a - 1.0) * std::log(x) - x - lgamma(a));
655 if (df == 0.0) {
656 // 導関数がゼロ: 二分法フォールバックへ
657 break;
658 }
659
660 double x_new = x - f / df;
661
662 // Newton ステップが範囲外なら二分法を使う
663 if (x_new <= lo || x_new >= hi) {
664 x_new = (lo + hi) / 2.0;
665 }
666
667 if (std::abs(x_new - x) < eps * x) {
668 converged = true;
669 x = x_new;
670 break;
671 }
672
673 x = x_new;
674 }
675
676 // Newton-Raphson が収束しなかった場合の二分法フォールバック
677 if (!converged) {
678 for (int bisect_iter = 0; bisect_iter < 100; ++bisect_iter) {
679 double mid = (lo + hi) / 2.0;
680 double f_mid = gammainc_lower(a, mid) - p;
681 if (std::abs(f_mid) < 1e-12 || (hi - lo) < 1e-12 * mid) {
682 return mid;
683 }
684 if (f_mid < 0) {
685 lo = mid;
686 } else {
687 hi = mid;
688 }
689 }
690 return (lo + hi) / 2.0;
691 }
692
693 return x;
694}
695
696} // namespace statcpp
double betainc(double a, double b, double x)
Regularized incomplete beta function.
double norm_sf(double x)
Standard normal survival 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.