// File: bobverity[P]MavericK/misc.cpp
// Function: approx_gamma(double)
double approx_gamma(double x) {

	if (x <= 0.0)
	{
		std::stringstream os;
        os << "Invalid input argument " << x <<  ". Argument must be positive.";
        throw std::invalid_argument( os.str() );
	}
    
    // Split the function domain into three intervals:
    // (0, 0.001), [0.001, 12), and (12, infinity)
    
    ///////////////////////////////////////////////////////////////////////////
    // First interval: (0, 0.001)
	//
	// For small x, 1/gamma(x) has power series x + gamma x^2  - ...
	// So in this range, 1/gamma(x) = x + gamma x^2 with error on the order of x^3.
	// The relative error over this interval is less than 6e-7.
    
	const double gamma = 0.577215664901532860606512090; // Euler's gamma constant
    
    if (x < 0.001)
        return 1.0/(x*(1.0 + gamma*x));
    
    ///////////////////////////////////////////////////////////////////////////
    // Second interval: [0.001, 12)
    
	if (x < 12.0)
    {
        // The algorithm directly approximates gamma over (1,2) and uses
        // reduction identities to reduce other arguments to this interval.
		
		double y = x;
        int n = 0;
        bool arg_was_less_than_one = (y < 1.0);
        
        // Add or subtract integers as necessary to bring y into (1,2)
        // Will correct for this below
        if (arg_was_less_than_one)
        {
            y += 1.0;
        }
        else
        {
            n = static_cast<int> (floor(y)) - 1;  // will use n later
            y -= n;
        }
        
        // numerator coefficients for approximation over the interval (1,2)
        static const double p[] =
        {
            -1.71618513886549492533811E+0,
            2.47656508055759199108314E+1,
            -3.79804256470945635097577E+2,
            6.29331155312818442661052E+2,
            8.66966202790413211295064E+2,
            -3.14512729688483675254357E+4,
            -3.61444134186911729807069E+4,
            6.64561438202405440627855E+4
        };
        
        // denominator coefficients for approximation over the interval (1,2)
        static const double q[] =
        {
            -3.08402300119738975254353E+1,
            3.15350626979604161529144E+2,
            -1.01515636749021914166146E+3,
            -3.10777167157231109440444E+3,
            2.25381184209801510330112E+4,
            4.75584627752788110767815E+3,
            -1.34659959864969306392456E+5,
            -1.15132259675553483497211E+5
        };
        
        double num = 0.0;
        double den = 1.0;
        int i;
        
        double z = y - 1;
        for (i = 0; i < 8; i++)
        {
            num = (num + p[i])*z;
            den = den*z + q[i];
        }
        double result = num/den + 1.0;
        
        // Apply correction if argument was not initially in (1,2)
        if (arg_was_less_than_one)
        {
            // Use identity gamma(z) = gamma(z+1)/z
            // The variable "result" now holds gamma of the original y + 1
            // Thus we use y-1 to get back the orginal y.
            result /= (y-1.0);
        }
        else
        {
            // Use the identity gamma(z+n) = z*(z+1)* ... *(z+n-1)*gamma(z)
            for (i = 0; i < n; i++)
                result *= y++;
        }
        
		return result;
    }
    
    ///////////////////////////////////////////////////////////////////////////
    // Third interval: [12, infinity)
    
    if (x > 171.624)
    {
		// Correct answer too large to display. Force +infinity.
		double temp = numeric_limits<double>::max();
		return temp*2.0;
    }
    
    return exp(approx_lgamma(x));
}

// Assembly:
subq $0x238, %rsp            # imm = 0x238
movsd %xmm0, 0x228(%rsp)
xorps %xmm0, %xmm0
ucomisd 0x228(%rsp), %xmm0
jb 0x36913
leaq 0xa0(%rsp), %rdi
callq 0x42f0
leaq 0xb0(%rsp), %rdi
leaq 0x26495(%rip), %rsi     # 0x5cca3
callq 0x4430
movq %rax, 0x18(%rsp)
jmp 0x3681a
movq 0x18(%rsp), %rdi
movsd 0x228(%rsp), %xmm0
callq 0x4790
movq %rax, 0x10(%rsp)
jmp 0x36834
movq 0x10(%rsp), %rdi
leaq 0x2647b(%rip), %rsi     # 0x5ccbb
callq 0x4430
jmp 0x36847
movb $0x1, 0x6f(%rsp)
movl $0x10, %edi
callq 0x41d0
movq %rax, 0x8(%rsp)
leaq 0x70(%rsp), %rdi
leaq 0xa0(%rsp), %rsi
callq 0x4420
jmp 0x3686f
movq 0x8(%rsp), %rdi
leaq 0x70(%rsp), %rsi
callq 0x4710
jmp 0x36880
movq 0x8(%rsp), %rdi
movb $0x0, 0x6f(%rsp)
movq 0x3673f(%rip), %rsi     # 0x6cfd0
movq 0x36728(%rip), %rdx     # 0x6cfc0
callq 0x46e0
jmp 0x36bba
movq %rax, %rcx
movl %edx, %eax
movq %rcx, 0x98(%rsp)
movl %eax, 0x94(%rsp)
jmp 0x36901
movq %rax, %rcx
movl %edx, %eax
movq %rcx, 0x98(%rsp)
movl %eax, 0x94(%rsp)
jmp 0x368ec
movq %rax, %rcx
movl %edx, %eax
movq %rcx, 0x98(%rsp)
movl %eax, 0x94(%rsp)
leaq 0x70(%rsp), %rdi
callq 0x4270
testb $0x1, 0x6f(%rsp)
jne 0x368f5
jmp 0x368ff
movq 0x8(%rsp), %rdi
callq 0x4310
jmp 0x36901
leaq 0xa0(%rsp), %rdi
callq 0x4330
jmp 0x36bad
movsd 0x26215(%rip), %xmm0    # 0x5cb30
movsd %xmm0, 0x60(%rsp)
movsd 0x261ff(%rip), %xmm0    # 0x5cb28
ucomisd 0x228(%rsp), %xmm0
jbe 0x3697c
movsd 0x228(%rsp), %xmm1
movsd 0x228(%rsp), %xmm0
movsd 0x261e2(%rip), %xmm2    # 0x5cb30
mulsd %xmm2, %xmm0
movsd 0x258a6(%rip), %xmm2    # 0x5c200
addsd %xmm2, %xmm0
mulsd %xmm0, %xmm1
movsd 0x25896(%rip), %xmm0    # 0x5c200
divsd %xmm1, %xmm0
movsd %xmm0, 0x230(%rsp)
jmp 0x36b9c
movsd 0x261b4(%rip), %xmm0    # 0x5cb38
ucomisd 0x228(%rsp), %xmm0
jbe 0x36b45
movsd 0x228(%rsp), %xmm0
movsd %xmm0, 0x58(%rsp)
movl $0x0, 0x54(%rsp)
movsd 0x2584e(%rip), %xmm0    # 0x5c200
ucomisd 0x58(%rsp), %xmm0
seta %al
andb $0x1, %al
movb %al, 0x53(%rsp)
testb $0x1, 0x53(%rsp)
je 0x369de
movsd 0x25830(%rip), %xmm0    # 0x5c200
addsd 0x58(%rsp), %xmm0
movsd %xmm0, 0x58(%rsp)
jmp 0x36a0a
movsd 0x58(%rsp), %xmm0
callq 0x46b0
cvttsd2si %xmm0, %eax
subl $0x1, %eax
movl %eax, 0x54(%rsp)
cvtsi2sdl 0x54(%rsp), %xmm1
movsd 0x58(%rsp), %xmm0
subsd %xmm1, %xmm0
movsd %xmm0, 0x58(%rsp)
xorps %xmm0, %xmm0
movsd %xmm0, 0x48(%rsp)
movsd 0x257e5(%rip), %xmm0    # 0x5c200
movsd %xmm0, 0x40(%rsp)
movsd 0x58(%rsp), %xmm0
movsd 0x257d1(%rip), %xmm1    # 0x5c200
subsd %xmm1, %xmm0
movsd %xmm0, 0x30(%rsp)
movl $0x0, 0x3c(%rsp)
cmpl $0x8, 0x3c(%rsp)
jge 0x36aa3
movsd 0x48(%rsp), %xmm0
movslq 0x3c(%rsp), %rcx
leaq 0x262b6(%rip), %rax     # 0x5cd10
addsd (%rax,%rcx,8), %xmm0
mulsd 0x30(%rsp), %xmm0
movsd %xmm0, 0x48(%rsp)
movsd 0x40(%rsp), %xmm0
movsd 0x30(%rsp), %xmm2
movslq 0x3c(%rsp), %rcx
leaq 0x262cd(%rip), %rax     # 0x5cd50
movsd (%rax,%rcx,8), %xmm1
mulsd %xmm2, %xmm0
addsd %xmm1, %xmm0
movsd %xmm0, 0x40(%rsp)
movl 0x3c(%rsp), %eax
addl $0x1, %eax
movl %eax, 0x3c(%rsp)
jmp 0x36a41
movsd 0x48(%rsp), %xmm0
divsd 0x40(%rsp), %xmm0
movsd 0x25749(%rip), %xmm1    # 0x5c200
addsd %xmm1, %xmm0
movsd %xmm0, 0x28(%rsp)
testb $0x1, 0x53(%rsp)
je 0x36aec
movsd 0x58(%rsp), %xmm1
movsd 0x2572a(%rip), %xmm0    # 0x5c200
subsd %xmm0, %xmm1
movsd 0x28(%rsp), %xmm0
divsd %xmm1, %xmm0
movsd %xmm0, 0x28(%rsp)
jmp 0x36b34
movl $0x0, 0x3c(%rsp)
movl 0x3c(%rsp), %eax
cmpl 0x54(%rsp), %eax
jge 0x36b32
movsd 0x58(%rsp), %xmm0
movsd 0x256f4(%rip), %xmm2    # 0x5c200
movaps %xmm0, %xmm1
addsd %xmm2, %xmm1
movsd %xmm1, 0x58(%rsp)
mulsd 0x28(%rsp), %xmm0
movsd %xmm0, 0x28(%rsp)
movl 0x3c(%rsp), %eax
addl $0x1, %eax
movl %eax, 0x3c(%rsp)
jmp 0x36af4
jmp 0x36b34
movsd 0x28(%rsp), %xmm0
movsd %xmm0, 0x230(%rsp)
jmp 0x36b9c
movsd 0x228(%rsp), %xmm0
movsd 0x25fea(%rip), %xmm1    # 0x5cb40
ucomisd %xmm1, %xmm0
jbe 0x36b80
callq 0x370a0
movsd %xmm0, 0x20(%rsp)
movsd 0x25499(%rip), %xmm0    # 0x5c008
mulsd 0x20(%rsp), %xmm0
movsd %xmm0, 0x230(%rsp)
jmp 0x36b9c
movsd 0x228(%rsp), %xmm0
callq 0x36bc0
callq 0x4760
movsd %xmm0, 0x230(%rsp)
movsd 0x230(%rsp), %xmm0
addq $0x238, %rsp            # imm = 0x238
retq
movq 0x98(%rsp), %rdi
callq 0x4730
nopw (%rax,%rax)