math.f90

View source code here on GitHub!

function  math/factorial(n)
Parameters:

n [integer]

Return:

answer [integer(i18t)] :: n!

function  math/n_choose_r(n, r)
Parameters:
  • n [integer]

  • r [integer]

Return:

answer [integer(i18t)] :: nCr

function  math/n_choose_r_slow(n, r)

This is a helper function for numbers that would otherwise overflow using the naive calculation of factorial(n) / factorial(r) / factorial(n - r). It's a slower path, but a much more reliable one because it actually factorizes the numbers and cancels out as many as possible before multiplying.

Parameters:
  • n [integer]

  • r [integer]

Return:

answer [integer(i18t)] :: nCr

 1module math
 2    use constants
 3    implicit none
 4contains
 5    pure integer(i18t) function factorial(n) result(answer)
 6        integer, intent(in) :: n
 7        integer :: i
 8        answer = 1
 9        do i = 2, n
10            answer = answer * i
11        end do
12    end function
13
14    integer(i18t) function n_choose_r(n, r) result(answer)
15        integer, intent(in) :: n, r
16        if (n <= MAX_FACTORIAL_64) then
17            answer = factorial(n) / factorial(r) / factorial(n-r)  ! fast path if small enough
18        else
19            answer = n_choose_r_slow(n, r)  ! slow path for larger numbers
20        end if
21    end function
22
23    integer(i18t) function n_choose_r_slow(n, r) result(answer)
24        integer, intent(in) :: n, r
25        integer(i2t), allocatable :: factors(:)
26        integer(i18t) :: tmp
27        integer :: i, j
28        allocate(factors(n))
29        if (.not. allocated(factors)) then
30            print *, "n_choose_r allocation failed. Exiting."
31            stop ERROR_ALLOCATE_FAILED
32        end if
33        factors = 0
34        ! collect factors of final number
35        do i = 2, n
36            factors(i) = 1
37        end do
38        ! negative factor values indicate need to divide
39        do i = 2, r
40            factors(i) = factors(i) - 1_i2t
41        end do
42        do i = 2, n - r
43            factors(i) = factors(i) - 1_i2t
44        end do
45        do i = n, 2, -1  ! this loop reduces to prime factors only
46            do j = 2, i - 1
47                if (mod(i, j) == 0) then
48                    factors(j) = factors(j) + factors(i)
49                    factors(i / j) = factors(i / j) + factors(i)
50                    factors(i) = 0
51                    exit
52                end if
53            end do
54        end do
55        i = 2
56        j = 2
57        answer = 1
58        do while (i <= n)
59            do while (factors(i) > 0)
60                tmp = answer
61                answer = answer * i
62                do while (answer < tmp .and. j <= n)
63                    do while (factors(j) < 0)
64                        tmp = tmp / j
65                        factors(j) = factors(j) + 1_i2t
66                    end do
67                    j = j + 1_i2t
68                    answer = tmp * i
69                end do
70                if (answer < tmp) then
71                    answer = -1  ! this indicates an overflow
72                    return
73                end if
74                factors(i) = factors(i) - 1_i2t
75            end do
76            i = i + 1_i2t
77        end do
78        do while (j <= n)
79            do while (factors(j) < 0)
80                answer = answer / j
81                factors(j) = factors(j) + 1_i2t
82            end do
83            j = j + 1_i2t
84        end do
85        deallocate(factors)
86    end function
87end module math