lgamma.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. /* eslint-disable no-loss-of-precision */
  2. // References
  3. // ----------
  4. // [1] Hare, "Computing the Principal Branch of log-Gamma", Journal of Algorithms, 1997.
  5. // [2] https://math.stackexchange.com/questions/1338753/how-do-i-calculate-values-for-gamma-function-with-complex-arguments
  6. import { lgammaNumber, lnSqrt2PI } from '../../plain/number/index.js';
  7. import { factory } from '../../utils/factory.js';
  8. import { copysign } from '../../utils/number.js';
  9. var name = 'lgamma';
  10. var dependencies = ['Complex', 'typed'];
  11. export var createLgamma = /* #__PURE__ */factory(name, dependencies, _ref => {
  12. var {
  13. Complex,
  14. typed
  15. } = _ref;
  16. // Stirling series is non-convergent, we need to use the recurrence `lgamma(z) = lgamma(z+1) - log z` to get
  17. // sufficient accuracy.
  18. //
  19. // These two values are copied from Scipy implementation:
  20. // https://github.com/scipy/scipy/blob/v1.8.0/scipy/special/_loggamma.pxd#L37
  21. var SMALL_RE = 7;
  22. var SMALL_IM = 7;
  23. /**
  24. * The coefficients are B[2*n]/(2*n*(2*n - 1)) where B[2*n] is the (2*n)th Bernoulli number. See (1.1) in [1].
  25. *
  26. * If you cannot access the paper, can also get these values from the formula in [2].
  27. *
  28. * 1 / 12 = 0.00833333333333333333333333333333
  29. * 1 / 360 = 0.00277777777777777777777777777778
  30. * ...
  31. * 3617 / 133400 = 0.02955065359477124183006535947712
  32. */
  33. var coeffs = [-2.955065359477124183e-2, 6.4102564102564102564e-3, -1.9175269175269175269e-3, 8.4175084175084175084e-4, -5.952380952380952381e-4, 7.9365079365079365079e-4, -2.7777777777777777778e-3, 8.3333333333333333333e-2];
  34. /**
  35. * Logarithm of the gamma function for real, positive numbers and complex numbers,
  36. * using Lanczos approximation for numbers and Stirling series for complex numbers.
  37. *
  38. * Syntax:
  39. *
  40. * math.lgamma(n)
  41. *
  42. * Examples:
  43. *
  44. * math.lgamma(5) // returns 3.178053830347945
  45. * math.lgamma(0) // returns Infinity
  46. * math.lgamma(-0.5) // returns NaN
  47. * math.lgamma(math.i) // returns -0.6509231993018536 - 1.8724366472624294i
  48. *
  49. * See also:
  50. *
  51. * gamma
  52. *
  53. * @param {number | Complex} n A real or complex number
  54. * @return {number | Complex} The log gamma of `n`
  55. */
  56. return typed(name, {
  57. number: lgammaNumber,
  58. Complex: lgammaComplex,
  59. BigNumber: function BigNumber() {
  60. throw new Error("mathjs doesn't yet provide an implementation of the algorithm lgamma for BigNumber");
  61. }
  62. });
  63. function lgammaComplex(n) {
  64. var TWOPI = 6.2831853071795864769252842; // 2*pi
  65. var LOGPI = 1.1447298858494001741434262; // log(pi)
  66. var REFLECTION = 0.1;
  67. if (n.isNaN()) {
  68. return new Complex(NaN, NaN);
  69. } else if (n.im === 0) {
  70. return new Complex(lgammaNumber(n.re), 0);
  71. } else if (n.re >= SMALL_RE || Math.abs(n.im) >= SMALL_IM) {
  72. return lgammaStirling(n);
  73. } else if (n.re <= REFLECTION) {
  74. // Reflection formula. see Proposition 3.1 in [1]
  75. var tmp = copysign(TWOPI, n.im) * Math.floor(0.5 * n.re + 0.25);
  76. var a = n.mul(Math.PI).sin().log();
  77. var b = lgammaComplex(new Complex(1 - n.re, -n.im));
  78. return new Complex(LOGPI, tmp).sub(a).sub(b);
  79. } else if (n.im >= 0) {
  80. return lgammaRecurrence(n);
  81. } else {
  82. return lgammaRecurrence(n.conjugate()).conjugate();
  83. }
  84. }
  85. function lgammaStirling(z) {
  86. // formula ref in [2]
  87. // computation ref:
  88. // https://github.com/scipy/scipy/blob/v1.8.0/scipy/special/_loggamma.pxd#L101
  89. // left part
  90. // x (log(x) - 1) + 1/2 (log(2PI) - log(x))
  91. // => (x - 0.5) * log(x) - x + log(2PI) / 2
  92. var leftPart = z.sub(0.5).mul(z.log()).sub(z).add(lnSqrt2PI);
  93. // right part
  94. var rz = new Complex(1, 0).div(z);
  95. var rzz = rz.div(z);
  96. var a = coeffs[0];
  97. var b = coeffs[1];
  98. var r = 2 * rzz.re;
  99. var s = rzz.re * rzz.re + rzz.im * rzz.im;
  100. for (var i = 2; i < 8; i++) {
  101. var tmp = b;
  102. b = -s * a + coeffs[i];
  103. a = r * a + tmp;
  104. }
  105. var rightPart = rz.mul(rzz.mul(a).add(b));
  106. // plus left and right
  107. return leftPart.add(rightPart);
  108. }
  109. function lgammaRecurrence(z) {
  110. // computation ref:
  111. // https://github.com/scipy/scipy/blob/v1.8.0/scipy/special/_loggamma.pxd#L78
  112. var signflips = 0;
  113. var sb = 0;
  114. var shiftprod = z;
  115. z = z.add(1);
  116. while (z.re <= SMALL_RE) {
  117. shiftprod = shiftprod.mul(z);
  118. var nsb = shiftprod.im < 0 ? 1 : 0;
  119. if (nsb !== 0 && sb === 0) signflips++;
  120. sb = nsb;
  121. z = z.add(1);
  122. }
  123. return lgammaStirling(z).sub(shiftprod.log()).sub(new Complex(0, signflips * 2 * Math.PI * 1));
  124. }
  125. });