|
24 | 24 | # log10(x, prec) |
25 | 25 | # log1p(x, prec) |
26 | 26 | # expm1(x, prec) |
| 27 | +# gamma(x, prec) |
| 28 | +# lgamma(x, prec) |
27 | 29 | # PI (prec) |
28 | 30 | # E (prec) == exp(1.0,prec) |
29 | 31 | # |
@@ -568,6 +570,123 @@ def expm1(x, prec) |
568 | 570 | exp_prec > 0 ? exp(x, exp_prec).sub(1, prec) : BigDecimal(-1) |
569 | 571 | end |
570 | 572 |
|
| 573 | + # call-seq: |
| 574 | + # BigMath.gamma(decimal, numeric) -> BigDecimal |
| 575 | + # |
| 576 | + # Computes the gamma function of +decimal+ to the specified number of |
| 577 | + # digits of precision, +numeric+. |
| 578 | + # |
| 579 | + # BigMath.gamma(BigDecimal('0.5'), 32).to_s |
| 580 | + # #=> "0.17724538509055160272981674833411e1" |
| 581 | + # |
| 582 | + def gamma(x, prec) |
| 583 | + prec = BigDecimal::Internal.coerce_validate_prec(prec, :gamma) |
| 584 | + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :gamma) |
| 585 | + prec2 = prec + BigDecimal.double_fig |
| 586 | + if x < 0.5 |
| 587 | + raise Math::DomainError 'Numerical argument is out of domain - gamma' if x.frac.zero? |
| 588 | + |
| 589 | + # Euler's reflection formula: gamma(z) * gamma(1-z) = pi/sin(pi*z) |
| 590 | + pi = PI(prec2) |
| 591 | + sin = _sinpix(x, pi, prec2) |
| 592 | + return pi.div(gamma(1 - x, prec).mult(sin, prec2), prec) |
| 593 | + elsif x.frac.zero? && x < 1000 * prec |
| 594 | + return _gamma_positive_integer(x, prec2).mult(1, prec) |
| 595 | + end |
| 596 | + |
| 597 | + a, sum = _gamma_spouge_sum_part(x, prec2) |
| 598 | + (x + (a - 1)).power(x - 0.5, prec2).mult(BigMath.exp(1 - x, prec2), prec2).mult(sum, prec) |
| 599 | + end |
| 600 | + |
| 601 | + # call-seq: |
| 602 | + # BigMath.lgamma(decimal, numeric) -> [BigDecimal, Integer] |
| 603 | + # |
| 604 | + # Computes the natural logarithm of the absolute value of the gamma function |
| 605 | + # of +decimal+ to the specified number of digits of precision, +numeric+ and its sign. |
| 606 | + # |
| 607 | + # BigMath.lgamma(BigDecimal('0.5'), 32) |
| 608 | + # #=> [0.57236494292470008707171367567653e0, 1] |
| 609 | + # |
| 610 | + def lgamma(x, prec) |
| 611 | + prec = BigDecimal::Internal.coerce_validate_prec(prec, :lgamma) |
| 612 | + x = BigDecimal::Internal.coerce_to_bigdecimal(x, prec, :lgamma) |
| 613 | + prec2 = prec + BigDecimal.double_fig |
| 614 | + if x < 0.5 |
| 615 | + return [BigDecimal::INFINITY, 1] if x.frac.zero? |
| 616 | + |
| 617 | + # Euler's reflection formula: gamma(z) * gamma(1-z) = pi/sin(pi*z) |
| 618 | + pi = PI(prec2) |
| 619 | + sin = _sinpix(x, pi, prec2) |
| 620 | + log_gamma = BigMath.log(pi, prec2).sub(lgamma(1 - x, prec).first + BigMath.log(sin.abs, prec2), prec) |
| 621 | + [log_gamma, sin > 0 ? 1 : -1] |
| 622 | + elsif x.frac.zero? && x < 1000 * prec |
| 623 | + log_gamma = BigMath.log(_gamma_positive_integer(x, prec2), prec) |
| 624 | + [log_gamma, 1] |
| 625 | + else |
| 626 | + a, sum = _gamma_spouge_sum_part(x, prec2) |
| 627 | + log_gamma = BigMath.log(sum, prec2).add((x - 0.5).mult(BigMath.log(x.add(a - 1, prec2), prec2), prec2) + 1 - x, prec) |
| 628 | + [log_gamma, 1] |
| 629 | + end |
| 630 | + end |
| 631 | + |
| 632 | + # Returns sum part: sqrt(2*pi) and c[k]/(x+k) terms of Spouge's approximation |
| 633 | + private_class_method def _gamma_spouge_sum_part(x, prec) # :nodoc: |
| 634 | + x -= 1 |
| 635 | + # Spouge's approximation |
| 636 | + # x! = (x + a)**(x + 0.5) * exp(-x - a) * (sqrt(2 * pi) + (1..a - 1).sum{|k| c[k] / (x + k) } + epsilon) |
| 637 | + # where c[k] = (-1)**k * (a - k)**(k - 0.5) * exp(a - k) / (k - 1)! |
| 638 | + # and epsilon is bounded by a**(-0.5) * (2 * pi) ** (-a - 0.5) |
| 639 | + |
| 640 | + # Estimate required a for given precision |
| 641 | + a = (prec / Math.log10(2 * Math::PI)).ceil |
| 642 | + |
| 643 | + # Calculate exponent of c[k] in low precision to estimate required precision |
| 644 | + low_prec = 16 |
| 645 | + log10f = Math.log(10) |
| 646 | + x_low_prec = x.mult(1, low_prec) |
| 647 | + loggamma_k = 0 |
| 648 | + ck_exponents = (1..a-1).map do |k| |
| 649 | + loggamma_k += Math.log10(k - 1) if k > 1 |
| 650 | + -loggamma_k - k / log10f + (k - 0.5) * Math.log10(a - k) - BigMath.log10(x_low_prec.add(k, low_prec), low_prec) |
| 651 | + end |
| 652 | + |
| 653 | + # Estimate exponent of sum by Stirling's approximation |
| 654 | + approx_sum_exponent = x < 1 ? -Math.log10(a) / 2 : Math.log10(2 * Math::PI) / 2 + x_low_prec.add(0.5, low_prec) * Math.log10(x_low_prec / x_low_prec.add(a, low_prec)) |
| 655 | + |
| 656 | + # Determine required precision of c[k] |
| 657 | + prec2 = [ck_exponents.max.ceil - approx_sum_exponent.floor, 0].max + prec |
| 658 | + |
| 659 | + einv = BigMath.exp(-1, prec2) |
| 660 | + sum = (PI(prec) * 2).sqrt(prec).mult(BigMath.exp(-a, prec), prec) |
| 661 | + y = BigDecimal(1) |
| 662 | + (1..a - 1).each do |k| |
| 663 | + # c[k] = (-1)**k * (a - k)**(k - 0.5) * exp(-k) / (k-1)! / (x + k) |
| 664 | + y = y.div(1 - k, prec2) if k > 1 |
| 665 | + y = y.mult(einv, prec2) |
| 666 | + z = y.mult(BigDecimal((a - k) ** k), prec2).div(BigDecimal(a - k).sqrt(prec2).mult(x.add(k, prec2), prec2), prec2) |
| 667 | + # sum += c[k] / (x + k) |
| 668 | + sum = sum.add(z, prec2) |
| 669 | + end |
| 670 | + [a, sum] |
| 671 | + end |
| 672 | + |
| 673 | + private_class_method def _gamma_positive_integer(x, prec) # :nodoc: |
| 674 | + return x if x == 1 |
| 675 | + numbers = (1..x - 1).map {|i| BigDecimal(i) } |
| 676 | + while numbers.size > 1 |
| 677 | + numbers = numbers.each_slice(2).map {|a, b| b ? a.mult(b, prec) : a } |
| 678 | + end |
| 679 | + numbers.first |
| 680 | + end |
| 681 | + |
| 682 | + # Returns sin(pi * x), for gamma reflection formula calculation |
| 683 | + private_class_method def _sinpix(x, pi, prec) # :nodoc: |
| 684 | + x = x % 2 |
| 685 | + sign = x > 1 ? -1 : 1 |
| 686 | + x %= 1 |
| 687 | + x = 1 - x if x > 0.5 # to avoid sin(pi*x) loss of precision for x close to 1 |
| 688 | + sign * sin(x.mult(pi, prec), prec) |
| 689 | + end |
571 | 690 |
|
572 | 691 | # call-seq: |
573 | 692 | # PI(numeric) -> BigDecimal |
|
0 commit comments