我决定尝试在Fortran 95中实现阶乘函数(f2py限制),但我的努力只产生两个返回类型不匹配错误。
解决方案的灵感
在Haskell中,我们可以做类似
的事情fac_helper n acc =
if n <= 1
then acc
else fac_helper (n - 1) (acc * n)
factorial n = fac_helper n 1
尝试解决方案: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
当fac.f95上使用GNU Fortran(GCC)4.8.3作为
gfortran -std=f95 ./fac.f95 -o fac
结果是:
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))
这些错误(对于不熟悉的Fortraner)显示为与尝试编译的代码脱节。我确信在尝试的解决方案中没有声明或使用的实数。 ...?
Fortran95中的尾递归因子实现如何?
答案 0 :(得分:5)
您向我们展示的错误可能与您调用factorial()
的方式有关,而与此代码无关。如果我在以下示例中包装您的代码:
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
并使用gfortran 4.8.3进行编译:
gfortran -std=f95 -o fac fac.f90
我得到了输出:
1! = 1
2! = 2
3! = 6
4! = 24
5! = 120
6! = 720
7! = 5040
8! = 40320
9! = 362880
10! = 3628800
确保使用正确的参数类型调用factorial()
,如果它不在模块或内部过程中,请使用显式接口。我注意到您没有在代码中使用implicit none
,因此还要验证您是否明确声明要调用的变量factorial
,以确保使用正确的类型。
如果您将这些过程用作外部过程(例如,不在主程序中或包含在模块中),那么为了让调用过程知道要发生什么,您应该使用显式接口。请参阅以下示例。
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
我对您的示例所做的更改仅限于:
implicit none
以禁止隐式输入使用implicit none
,您编译代码的原始尝试将失败。因为您没有它,所以以factorial
开头的f
的外部函数调用被认为是真实的。当函数返回一个整数时,这会导致类型不匹配。您通过明确声明factorial
是一个整数来解决这个问题,但更好的解决方案是完全指定接口,以便编译器可以检查参数而不仅仅是返回类型。在我的代码中,主程序调用{{1}},因此这是添加显式接口块的地方。同样,factorial
调用factorial
,同样需要一个接口。
通过在facHelper
语句后包含模块内或主程序中的函数,可以避免使用显式接口块。正如上面的例子所示,你提出的算法中没有任何改变,这些改变仅限于Fortran处理外部程序和隐式类型的问题 - 在这两个例子中,这个例子选择了显式的最佳实践。
我个人会选择在模块中包含这些函数,完全避免使用显式接口。见这个例子:
contains
在此示例中,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
位于模块中,factorial
位于模块中,但不能在外部调用(声明为私有且对主程序隐藏)。您会发现此处的算法与您提出的代码几乎完全相同,唯一的区别是添加了facHelper
。在主程序中,行implicit none
让程序知道函数的接口。
答案 1 :(得分:0)
在搞乱隐含无等问题之后,似乎问题实际上是在阶乘函数中。
即使用
定义了facHelperrecursive integer function facHelper(n, acc) result(returner)
并且在facHelper之后定义了阶乘函数,factorial仍然不知道facHelper返回一个整数。
然后解决方案是在factorial函数中添加一行,告诉它facHelper是整数:
function factorial(n)
integer::n
integer::factorial
integer::facHelper ! <-- Missing information now available
factorial = facHelper(n, 1)
end function factorial
Fortran95中的尾递归因子实现如何?
Fortran95中的尾递归因子函数可以实现为:
<强> 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
或者,更令人愉悦(在我看来):
<强> 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
这两个现在都将在GNU Fortran(GCC)4.8.3下使用
进行编译gfortran --std=f95 -c ./fac.f95
将f2py v2与NumPy v1.8.0一起使用
虽然两个版本的fac.f95 abover都将使用gfortran进行编译,但第二个版本将导致f2py认为来自facHelper的返回者是真实的。但是,f2py可以正确处理第一个版本的fac.f95。
我想在Fortran中对尾部递归因子进行基准测试(时间)。我添加了一个非尾递归版本的阶乘(名为vanillaFac)。整数大小也增加到kind = 8。
fac.f95现在包含
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
新的fac.f95是用
编译的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;
我写了一个python脚本 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"
最后,致电
python timer.py
输出:
*****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