/*                                             rgamma.c
 *
 *     Reciprocal Gamma function
 *
 *
 *
 * SYNOPSIS:
 *
 * double x, y, rgamma();
 *
 * y = rgamma( x );
 *
 *
 *
 * DESCRIPTION:
 *
 * Returns one divided by the Gamma function of the argument.
 *
 * The function is approximated by a Chebyshev expansion in
 * the interval [0,1].  Range reduction is by recurrence
 * for arguments between -34.034 and +34.84425627277176174.
 * 0 is returned for positive arguments outside this
 * range.  For arguments less than -34.034 the cosecant
 * reflection formula is applied; lograrithms are employed
 * to avoid unnecessary overflow.
 *
 * The reciprocal Gamma function has no singularities,
 * but overflow and underflow may occur for large arguments.
 * These conditions return either INFINITY or 0 with
 * appropriate sign.
 *
 * ACCURACY:
 *
 *                      Relative error:
 * arithmetic   domain     # trials      peak         rms
 *    IEEE     -30,+30      30000       1.1e-15     2.0e-16
 * For arguments less than -34.034 the peak error is on the
 * order of 5e-15 (DEC), excepting overflow or underflow.
 */

/*
 * Cephes Math Library Release 2.0:  April, 1987
 * Copyright 1985, 1987 by Stephen L. Moshier
 * Direct inquiries to 30 Frost Street, Cambridge, MA 02140
 */
#pragma once

#include "../config.h"
#include "../error.h"
#include "chbevl.h"
#include "const.h"
#include "gamma.h"
#include "trig.h"

namespace xsf {
namespace cephes {

    namespace detail {

        /* Chebyshev coefficients for reciprocal Gamma function
         * in interval 0 to 1.  Function is 1/(x Gamma(x)) - 1
         */

        constexpr double rgamma_R[] = {3.13173458231230000000E-17,  -6.70718606477908000000E-16,
                                       2.20039078172259550000E-15,  2.47691630348254132600E-13,
                                       -6.60074100411295197440E-12, 5.13850186324226978840E-11,
                                       1.08965386454418662084E-9,   -3.33964630686836942556E-8,
                                       2.68975996440595483619E-7,   2.96001177518801696639E-6,
                                       -8.04814124978471142852E-5,  4.16609138709688864714E-4,
                                       5.06579864028608725080E-3,   -6.41925436109158228810E-2,
                                       -4.98558728684003594785E-3,  1.27546015610523951063E-1};

    } // namespace detail

    XSF_HOST_DEVICE double rgamma(double x) {
        double w, y, z;

        if (x == 0) {
            // This case is separate from below to get correct sign for zero.
            return x;
        }

        if (x < 0 && x == std::floor(x)) {
            // Gamma poles.
            return 0.0;
        }

        if (std::abs(x) > 4.0) {
            return 1.0 / Gamma(x);
        }

        z = 1.0;
        w = x;

        while (w > 1.0) { /* Downward recurrence */
            w -= 1.0;
            z *= w;
        }
        while (w < 0.0) { /* Upward recurrence */
            z /= w;
            w += 1.0;
        }
        if (w == 0.0) /* Nonpositive integer */
            return (0.0);
        if (w == 1.0) /* Other integer */
            return (1.0 / z);

        y = w * (1.0 + chbevl(4.0 * w - 2.0, detail::rgamma_R, 16)) / z;
        return (y);
    }
} // namespace cephes
} // namespace xsf
