
# factsLib.py
# info: https://en.wikipedia.org/wiki/Factorial
# multiple ways of calculating factorial
# input: 50 and a function name

from functools import reduce
import math
# import decimal
from decTrig import *


def factRec(n):
  if n <= 1:
    return 1
  else:
    return n*factRec(n-1)


def factIter(n):
  fact = 1
  while n > 0:
    fact *= n
    n -= 1
  return fact

def factIterUp(n):
  fact = 1; count = 0
  while count < n:
    count += 1
    fact *= count
  return fact

def factRange(n):
  fact = 1
  for i in range(1, n+1):
    fact *= i
  return fact

# ----------- other integer approaches -------------

def factGen():
  fact = 1
  i = 0    # fact(0) = 1
  while True:
    yield fact
    i += 1
    fact *= i

def facts(n):
  # return the first n factorials
  gen = factGen()
  return [ next(gen) for _ in range(n+1) ]

def factProd(n):
  return math.prod(range(1, n+1), start=1)

def factReduce(n):
  return reduce(lambda x, y: x * y, range(1, n+1), 1)


# ---------- approximations -----------

def factLGamma(n):
  # an approximation since lgamma() and exp() are only
  # accurate to 15 places due to their use of floats
  return math.ceil(math.exp(math.lgamma(n+1)))

def stirling(n):
  return math.sqrt(2*math.pi*n)*(n/math.e)**n

def stirling2(n):
  return  (1 + 1/(12*n)) * math.sqrt(2*math.pi*n)*(n/math.e)**n


def stirlingD(n):
  root = (2*PI*D(n)).sqrt()
  return root*((D(n)/E)**D(n))

def stirling2D(n):
  term12 = D(1) + D(1)/(D(12) * D(n))
  root = (2*PI*D(n)).sqrt()
  return term12*root*((D(n)/E)**D(n))


if __name__ == "__main__":
  # print("Recursion limit:", sys.getrecursionlimit())
  # sys.setrecursionlimit(10**6)
  # sys.set_int_max_str_digits(0)  
      # for printing the result; new in v 3.11

  n = int(input("n=? "))
  print("Built-in:", math.factorial(n))
  print("\nRecursive:", factRec(n))
  print("\nIterative:", factIter(n))
