I decided to try implementing the factorial function in Fortran 95 (f2py limitation) but my efforts are only yielding two return-type-mismatch errors.
Inspiration for solution
In Haskell, we can do something like
fac_helper n acc =
if n <= 1
then acc
else fac_helper (n - 1) (acc * n)
factorial n = fac_helper n 1
Attempted solution: fac.f95
recursive function facHelper(n, acc) result(returner)
integer::n
integer::acc
integer::returner
if (n <= 1) then
returner = acc
else
returner = facHelper(n - 1, n * acc)
endif
end function facHelper
function factorial(n)
integer::n
integer::factorial
factorial = facHelper(n, 1)
end function factorial
When GNU Fortran (GCC) 4.8.3 is used on fac.f95 as
gfortran -std=f95 ./fac.f95 -o fac
the result is:
Error: Return type mismatch of function 'fachelper' at (1) (REAL(4)/INTEGER(4))
Error: Return type mismatch of function 'factorial' at (1) (REAL(4)/INTEGER(4))
These errors appear (to the un-familiar Fortraner) as out of touch with the code of which compilation was attempted on. I am certain that there are no real numbers declared or used in the attempted solution. ...?
How would the tail-recursive factorial implementation look in Fortran95?
The error you are showing us is likely related to how you are calling factorial()
and not in this code. If I wrap your code in the following example:
program test
implicit none
integer :: i
do i=1,10
write (*,'(i2,"! = ",i8)') i, factorial(i)
end do
contains
[cut and paste the code from your question]
end program
and compile with gfortran 4.8.3:
gfortran -std=f95 -o fac fac.f90
I get the output:
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800
Make sure you are calling factorial()
with the proper argument types and if it is not in a module or internal procedure, use an explicit interface. I notice that you are not using implicit none
in your code, so also verify that you are explicitly declaring the variables you are calling factorial
with to ensure the proper type is used.
If you are using these procedures as external procedures (eg not in your main program or contained in a module) then to let the calling procedure to know what to expect, you should use an explicit interface. See the following example.
program test
implicit none
integer :: i
interface
function factorial(n)
integer, intent(in) :: n
integer :: factorial
end function factorial
end interface
do i=1,10
write (*,'(i2,"! = ",i8)') i, factorial(i)
end do
end program
recursive function facHelper(n, acc) result(returner)
implicit none
integer, intent(in) :: n, acc
integer :: returner
if (n <= 1) then
returner = acc
else
returner = facHelper(n - 1, n * acc)
endif
end function facHelper
function factorial(n)
implicit none
integer, intent(in) :: n
integer :: factorial
interface
function facHelper(n,acc)
integer, intent(in) :: n, acc
integer :: facHelper
end function
end interface
factorial = facHelper(n, 1)
end function factorial
The changes I've made to your example are limited to:
implicit none
to disallow implicit typing With implicit none
your original attempt at compiling the code would have failed. Because you did not have it, your external function call to factorial
, which starts with f
is assumed to be real. When your function returns an integer, this causes a type mismatch. You solved this by explicitly declaring factorial
to be an integer, but a better solution is to completely specify the interface so the compiler can check the arguments and not just the return types. In my code the main program calls factorial
and so that is where an explicit interface block has been added. Likewise, factorial
calls facHelper
and similarly needs an interface for it.
You can avoid explicit interface blocks by containing your functions within a module or in the main program after a contains
statement. As the above example should illustrate, nothing has been changed in your proposed algorithm, the changes are limited to issues with how Fortran handles external procedures and implicit typing -- in both cases this example has chosen the best practice of being explicit.
I would personally choose to contain these functions in a module and entirely avoid the need for explicit interfaces. See this example:
module fact
private :: facHelper
contains
recursive function facHelper(n, acc) result(returner)
implicit none
integer, intent(in) :: n, acc
integer :: returner
if (n <= 1) then
returner = acc
else
returner = facHelper(n - 1, n * acc)
endif
end function facHelper
function factorial(n)
implicit none
integer, intent(in) :: n
integer :: factorial
factorial = facHelper(n, 1)
end function factorial
end module fact
program test
use fact
implicit none
integer :: i
do i=1,10
write (*,'(i2,"! = ",i8)') i, factorial(i)
end do
end program
In this example, factorial
is in a module and facHelper
is in the module but not callable externally (declared private and hidden from the main program). You'll see that your algorithm here is almost identical to your proposed code, the only difference being the implicit none
that is added. Here in the main program the line use fact
lets the program know about the interface to the function.
After fussing around with things like implicit none etc, it appears that the problem was actually in the factorial function.
Even if facHelper had been defined with
recursive integer function facHelper(n, acc) result(returner)
and having the factorial function being defined after facHelper, factorial still would not know that facHelper returns an integer.
The solution then is to add a line to the factorial function telling it that facHelper is integer:
function factorial(n)
integer::n
integer::factorial
integer::facHelper ! <-- Missing information now available
factorial = facHelper(n, 1)
end function factorial
How would the tail-recursive factorial implementation look in Fortran95?
The tail-recursive factorial function in Fortran95 could be implemented as:
fac.f95
recursive function facHelper(n, acc) result(returner)
integer::n
integer::acc
integer::returner
if (n <= 1) then
returner = acc
else
returner = facHelper(n - 1, n * acc)
endif
end function facHelper
function factorial(n)
integer::n
integer::factorial
integer::facHelper
factorial = facHelper(n, 1)
end function factorial
Or, more asthetically pleasing (in my opinion):
fac.f95
recursive integer function facHelper(n, acc) result(returner)
integer::n
integer::acc
if (n <= 1) then
returner = acc
else
returner = facHelper(n - 1, n * acc)
endif
end function facHelper
integer function factorial(n)
integer::n
integer::facHelper
factorial = facHelper(n, 1)
end function factorial
Both of these will now compile under GNU Fortran (GCC) 4.8.3 with
gfortran --std=f95 -c ./fac.f95
Using f2py v2 with NumPy v1.8.0
While both versions of fac.f95 abover will compile with gfortran, the second version will cause f2py to think that the returner from facHelper is a real. However, f2py does correctly handle the first version of fac.f95.
I wanted to benchmark (for time) the tail-recursive factorial in Fortran. I added a non-tail-recursive version of the factorial (named vanillaFac). The integer sizes were also increased to kind=8.
fac.f95 now contains
recursive function tailFacHelper(n, acc) result(returner)
integer (kind=8)::n
integer (kind=8)::acc
integer (kind=8)::returner
if (n <= 1) then
returner = acc
else
returner = tailFacHelper(n - 1, n * acc)
endif
end function tailFacHelper
function tailFac(n)
integer (kind=8)::n
integer (kind=8)::tailFac
integer (kind=8)::tailFacHelper
tailFac = tailFacHelper(n, 1_8)
end function tailFac
recursive function vanillaFac(n) result(returner)
integer (kind=8)::n
integer (kind=8)::returner
if (n <= 1) then
returner = 1
else
returner = n * vanillaFac(n - 1)
endif
end function vanillaFac
The new fac.f95 was compiled with
f2py --overwrite-signature --no-lower fac.f95 -m liboptfac -h fac.pyf;
f2py -c --f90flags=--std=f95 --opt=-O3 fac.pyf fac.f95;
f2py --overwrite-signature --no-lower fac.f95 -m libnooptfac -h fac.pyf;
f2py -c --f90flags=--std=f95 --noopt fac.pyf fac.f95;
I wrote a python script timer.py
import liboptfac
import libnooptfac
import timeit
def py_vanilla_fac(n):
if n <= 1:
return 1
else:
return n * py_vanilla_fac(n - 1)
def py_tail_fac_helper(n, acc):
if n <= 1:
return acc
else:
return py_tail_fac_helper(n - 1, n * acc)
def py_tail_fac(n):
return py_tail_fac_helper(n, 1)
LOOPS = 10 ** 6
print "\n*****Fortran (optimizations level 03 enabled)*****"
print "\nliboptfac.vanillaFac(20)\n" + str(LOOPS) + " calls\nBest of ten: ", min(timeit.repeat(lambda: liboptfac.vanillaFac(20), repeat = 10, number = LOOPS))
print "\nliboptfac.tailFac(20)\n" + str(LOOPS) + " calls\nBest of ten: ", min(timeit.repeat(lambda: liboptfac.tailFac(20), repeat = 10, number = LOOPS))
print "\nliboptfac.tailFacHelper(20, 1)\n" + str(LOOPS) + " calls\nBest of ten: ", min(timeit.repeat(lambda: liboptfac.tailFacHelper(20, 1), repeat = 10, number = LOOPS))
print "\n\n*****Fortran (no optimizations enabled)*****"
print "\nlibnooptfac.vanillaFac(20)\n" + str(LOOPS) + " calls\nBest of ten: ", min(timeit.repeat(lambda: libnooptfac.vanillaFac(20), repeat = 10, number = LOOPS))
print "\nlibnooptfac.tailFac(20)\n" + str(LOOPS) + " calls\nBest of ten: ", min(timeit.repeat(lambda: libnooptfac.tailFac(20), repeat = 10, number = LOOPS))
print "\nlibnooptfac.tailFacHelper(20, 1)\n" + str(LOOPS) + " calls\nBest of ten: ", min(timeit.repeat(lambda: libnooptfac.tailFacHelper(20, 1), repeat = 10, number = LOOPS))
print "\n\n*****Python*****"
print "\npy_vanilla_fac(20)\n" + str(LOOPS) + " calls\nBest of ten: ", min(timeit.repeat(lambda: py_vanilla_fac(20), repeat = 10, number = LOOPS))
print "\npy_tail_fac(20)\n" + str(LOOPS) + " calls\nBest of ten: ", min(timeit.repeat(lambda: py_tail_fac(20), repeat = 10, number = LOOPS))
print "\npy_tail_fac_helper(20, 1)\n" + str(LOOPS) + " calls\nBest of ten: ", min(timeit.repeat(lambda: py_tail_fac_helper(20, 1), repeat = 10, number = LOOPS))
print "\n\n\n"
Finaly, calling
python timer.py
outputs:
*****Fortran (optimizations level 03 enabled)*****
liboptfac.vanillaFac(20)
1000000 calls
Best of ten: 0.813575983047
liboptfac.tailFac(20)
1000000 calls
Best of ten: 0.843787193298
liboptfac.tailFacHelper(20, 1)
1000000 calls
Best of ten: 0.858899831772
*****Fortran (no optimizations enabled)*****
libnooptfac.vanillaFac(20)
1000000 calls
Best of ten: 1.00723600388
libnooptfac.tailFac(20)
1000000 calls
Best of ten: 0.975327014923
libnooptfac.tailFacHelper(20, 1)
1000000 calls
Best of ten: 0.982407093048
*****Python*****
py_vanilla_fac(20)
1000000 calls
Best of ten: 6.47849297523
py_tail_fac(20)
1000000 calls
Best of ten: 6.93045401573
py_tail_fac_helper(20, 1)
1000000 calls
Best of ten: 6.81205391884
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.