InternalGamma.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.rng.sampling.distribution;

  18. /**
  19.  * <h3>
  20.  *  Adapted and stripped down copy of class
  21.  *  {@code "org.apache.commons.math4.special.Gamma"}.
  22.  * </h3>
  23.  *
  24.  * <p>
  25.  * This is a utility class that provides computation methods related to the
  26.  * &Gamma; (Gamma) family of functions.
  27.  * </p>
  28.  */
  29. final class InternalGamma { // Class is package-private on purpose; do not make it public.
  30.     /**
  31.      * Constant \( g = \frac{607}{128} \) in the Lanczos approximation.
  32.      */
  33.     public static final double LANCZOS_G = 607.0 / 128.0;

  34.     /** Lanczos coefficients. */
  35.     private static final double[] LANCZOS_COEFFICIENTS = {
  36.         0.99999999999999709182,
  37.         57.156235665862923517,
  38.         -59.597960355475491248,
  39.         14.136097974741747174,
  40.         -0.49191381609762019978,
  41.         .33994649984811888699e-4,
  42.         .46523628927048575665e-4,
  43.         -.98374475304879564677e-4,
  44.         .15808870322491248884e-3,
  45.         -.21026444172410488319e-3,
  46.         .21743961811521264320e-3,
  47.         -.16431810653676389022e-3,
  48.         .84418223983852743293e-4,
  49.         -.26190838401581408670e-4,
  50.         .36899182659531622704e-5,
  51.     };

  52.     /** Avoid repeated computation of log(2*PI) / 2 in logGamma. */
  53.     private static final double HALF_LOG_2_PI = 0.91893853320467274178032973640562;

  54.     /**
  55.      * Class contains only static methods.
  56.      */
  57.     private InternalGamma() {}

  58.     /**
  59.      * Computes the function \( \ln \Gamma(x) \) for \( x \gt 0 \).
  60.      *
  61.      * <p>
  62.      * For \( x \leq 8 \), the implementation is based on the double precision
  63.      * implementation in the <em>NSWC Library of Mathematics Subroutines</em>,
  64.      * {@code DGAMLN}. For \( x \geq 8 \), the implementation is based on
  65.      * </p>
  66.      *
  67.      * <ul>
  68.      * <li><a href="http://mathworld.wolfram.com/GammaFunction.html">Gamma
  69.      *     Function</a>, equation (28).</li>
  70.      * <li><a href="http://mathworld.wolfram.com/LanczosApproximation.html">
  71.      *     Lanczos Approximation</a>, equations (1) through (5).</li>
  72.      * <li><a href="http://my.fit.edu/~gabdo/gamma.txt">Paul Godfrey, A note on
  73.      *     the computation of the convergent Lanczos complex Gamma
  74.      *     approximation</a></li>
  75.      * </ul>
  76.      *
  77.      * @param x Argument.
  78.      * @return \( \ln \Gamma(x) \), or {@code NaN} if {@code x <= 0}.
  79.      */
  80.     public static double logGamma(double x) {
  81.         // Stripped-down version of the same method defined in "Commons Math":
  82.         // Unused "if" branches (for when x < 8) have been removed here since
  83.         // this method is only used (by class "InternalUtils") in order to
  84.         // compute log(n!) for x > 20.

  85.         final double sum = lanczos(x);
  86.         final double tmp = x + LANCZOS_G + 0.5;
  87.         return (x + 0.5) * Math.log(tmp) - tmp + HALF_LOG_2_PI + Math.log(sum / x);
  88.     }

  89.     /**
  90.      * Computes the Lanczos approximation used to compute the gamma function.
  91.      *
  92.      * <p>
  93.      * The Lanczos approximation is related to the Gamma function by the
  94.      * following equation
  95.      * \[
  96.      * \Gamma(x) = \sqrt{2\pi} \, \frac{(g + x + \frac{1}{2})^{x + \frac{1}{2}} \, e^{-(g + x + \frac{1}{2})} \, \mathrm{lanczos}(x)}
  97.      *                                 {x}
  98.      * \]
  99.      * where \(g\) is the Lanczos constant.
  100.      * </p>
  101.      *
  102.      * @param x Argument.
  103.      * @return The Lanczos approximation.
  104.      *
  105.      * @see <a href="http://mathworld.wolfram.com/LanczosApproximation.html">Lanczos Approximation</a>
  106.      * equations (1) through (5), and Paul Godfrey's
  107.      * <a href="http://my.fit.edu/~gabdo/gamma.txt">Note on the computation
  108.      * of the convergent Lanczos complex Gamma approximation</a>
  109.      */
  110.     private static double lanczos(final double x) {
  111.         double sum = 0.0;
  112.         for (int i = LANCZOS_COEFFICIENTS.length - 1; i > 0; --i) {
  113.             sum += LANCZOS_COEFFICIENTS[i] / (x + i);
  114.         }
  115.         return sum + LANCZOS_COEFFICIENTS[0];
  116.     }
  117. }