Lanczos approximation

From Wikipedia, the free encyclopedia
Jump to: navigation, search

In mathematics, the Lanczos approximation is a method for computing the Gamma function numerically, published by Cornelius Lanczos in 1964. It is a practical alternative to the more popular Stirling's approximation for calculating the Gamma function with fixed precision.


The Lanczos approximation consists of the formula

\Gamma(z+1) = \sqrt{2\pi} {\left( z + g + \frac{1}{2} \right)}^{z + \frac{1}{2} } e^{-\left(z+g+\frac{1}{2}\right)} A_g(z)

for the Gamma function, with

A_g(z) = \frac{1}{2}p_0(g) + p_1(g) \frac{z}{z+1} + p_2(g) \frac{z(z-1)}{(z+1)(z+2)} + \cdots.

Here g is a constant that may be chosen arbitrarily subject to the restriction that Re(z) > 1/2.[1] The coefficients p, which depend on g, are slightly more difficult to calculate (see below). Although the formula as stated here is only valid for arguments in the right complex half-plane, it can be extended to the entire complex plane by the reflection formula,

\Gamma(1-z) \; \Gamma(z) = {\pi \over \sin \pi z}.

The series A is convergent, and may be truncated to obtain an approximation with the desired precision. By choosing an appropriate g (typically a small integer), only some 5-10 terms of the series are needed to compute the Gamma function with typical single or double floating-point precision. If a fixed g is chosen, the coefficients can be calculated in advance and the sum is recast into the following form:

A_g(z) = c_0 + \sum_{k=1}^{N} \frac{c_k}{z+k}

Thus computing the Gamma function becomes a matter of evaluating only a small number of elementary functions and multiplying by stored constants. The Lanczos approximation was popularized by Numerical Recipes, according to which computing the Gamma function becomes "not much more difficult than other built-in functions that we take for granted, such as sin x or ex". The method is also implemented in the GNU Scientific Library.


The coefficients are given by

p_k(g) = \sum_{a=0}^k C(2k+1, 2a+1) \frac{\sqrt{2}}{\pi} \left(a - \begin{matrix} \frac{1}{2} \end{matrix} \right)! 
{\left(a + g + \begin{matrix} \frac{1}{2} \end{matrix} \right)}^{- \left( a + \frac{1}{2} \right) } e^{a + g + \frac{1}{2} }

with C(i,j) denoting the (i, j)th element of the Chebyshev polynomial coefficient matrix which can be calculated recursively from the identities

C(1,1) = 1\,
C(2,2) = 1\,
C(i,1) = -C(i-2, 1)\, i = 3, 4, \dots\,
C(i,j) = 2 C(i-1, j-1)\, i = j = 3, 4, \dots\,
C(i,j) = 2 C(i-1, j-1) - C(i-2, j)\, i > j = 2, 3, \dots .

Paul Godfrey describes how to obtain the coefficients and also the value of the truncated series A as a matrix product.


Lanczos derived the formula from Leonhard Euler's integral

\Gamma(z+1) = \int_0^\infty  t^{z}\,e^{-t}\,dt,

performing a sequence of basic manipulations to obtain

\Gamma(z+1) = (z+g+1)^{z+1} e^{-(z+g+1)} \int_0^e [v(1-\log v)]^{z-\frac{1}{2}} v^g\,dv,

and deriving a series for the integral.

Simple implementation[edit]

The following implementation in the Python programming language works for complex arguments and typically gives 15 correct decimal places:

def gamma(z):             # great function from Wiki, but maybe could use memorization?
    epsilon = 0.0000001
    def withinepsilon(x):
        return abs(x) <= epsilon

    from cmath import sin,sqrt,pi,exp

    p = [ 676.5203681218851,   -1259.1392167224028,  771.32342877765313,
         -176.61502916214059,     12.507343278686905, -0.13857109526572012,
            9.9843695780195716e-6, 1.5056327351493116e-7]
    z = complex(z)

    # Reflection formula  (edit: this use of reflection (thus the if-else structure) seems unnecessary and just adds more code to execute. it calls itself again, so it still needs to execute the same "for" loop yet has an extra calculation at the end)
    if z.real < 0.5:
        result = pi / (sin(pi*z) * gamma(1-z))
        z -= 1
        x = 0.99999999999980993

        for (i, pval) in enumerate(p):
            x += pval/(z+i+1)

        t = z + len(p) - 0.5
        result = sqrt(2*pi) * t**(z+0.5) * exp(-t) * x

    if withinepsilon(result.imag):
        return result.real
    return result

See also[edit]


  1. ^ Pugh thesis