编写接受任意两个数字(任意实数或任意整数)的函数
Writing a function that accepts any two numbers (any real or any integer)
我有一个接受两个数字的函数,我不关心它们是整数、实数、32 位还是 64 位。对于下面的例子,我只是把它写成一个简单的乘法。在 Fortran 90 中,您可以使用接口块来完成此操作,但如果您想要涵盖两个数字相乘的所有可能交互,则必须编写 16(!)个函数,每个数字都可以是 int32、int64、real32 或真实 64.
对于 Fortran 2003,您还有一些其他选项,例如 class(*)
多态性,我找到了一种方法,只需在乘法之前将所有输入转换为实数即可:
! compiled on linux with gfortran 4.8.5
program main
integer, target :: i = 2
real(4), target :: x = 2.0
real(8), target :: y = 2.0
character, target :: c = 'a'
print *, multiply(i,x)
print *, multiply(x,i)
print *, multiply(i,i)
print *, multiply(y,y)
print *, multiply(c,c)
contains
function multiply(p,q)
real :: multiply
class(*) :: p, q
real :: r, s
r = 0.0 ; s = 0.0
select type(p)
type is (integer(4)) ; r = p
type is (integer(8)) ; r = p
type is (real(4)) ; r = p
type is (real(8)) ; r = p
class default ; print *, "p is not a real or int"
end select
select type(q)
type is (integer(4)) ; s = q
type is (integer(8)) ; s = q
type is (real(4)) ; s = q
type is (real(8)) ; s = q
class default ; print *, "q is not a real or int"
end select
multiply = r * s
end function multiply
end program main
这似乎是一个改进。至少这里的代码量是类型数量的线性而不是二次方,但我想知道是否还有更好的方法来做到这一点?如您所见,我仍然需要编写两次 select type
代码,将 'r' 更改为 's' 并将 'p' 更改为 'q'。
我试图将 select 类型块转换为一个函数,但无法使其工作。但我对可以进一步改进这一点的任何和所有替代方案感兴趣。这似乎是一个常见问题,但到目前为止我还没有找到比这更好的通用方法。
编辑添加: 显然有改进 Fortran 的计划 w.r.t。正如@SteveLionel 在评论中指出的那样,这个问题将在未来出现。 @roygvib 进一步提供了一个 link 到一个特定的提案,它也很好地解释了这个问题:https://j3-fortran.org/doc/year/13/13-236.txt
不是泛型的解决方案,但对于 "converting the select type blocks into a function",以下代码似乎有效(如果包含一些非平凡的转换可能会有用 (?))。
program main
implicit none
integer :: i = 2
real*4 :: x = 2.0
real*8 :: y = 2.0
character(3) :: c = 'abc'
print *, multiply( i, x )
print *, multiply( x, i )
print *, multiply( i, i )
print *, multiply( y, y )
print *, multiply( c, c )
contains
function toreal( x ) result( y )
class(*) :: x
real :: y
select type( x )
type is (integer) ; y = x
type is (real(4)) ; y = x
type is (real(8)) ; y = x
type is (character(*)) ; y = len(x)
class default ; stop "no match for x"
endselect
end
function multiply( p, q ) result( ans )
class(*) :: p, q
real :: ans
ans = toreal( p ) * toreal( q )
end
end program
! gfortran-8 test.f90 && ./a.out
4.00000000
4.00000000
4.00000000
4.00000000
9.00000000
另一种方法可能只是将实际参数转换为实数(尽管它可能对更实际的目的没有用...)
program main
implicit none
integer :: i = 2
real*4 :: x = 2.0
real*8 :: y = 2.0
character :: c = 'a'
print *, multiply( real(i), real(x) )
print *, multiply( real(x), real(i) )
print *, multiply( real(i), real(i) )
print *, multiply( real(y), real(y) )
! print *, multiply( real(c), real(c) ) ! error
contains
function multiply( p, q ) result( ans )
real :: p, q
real :: ans
ans = p * q
end
end program
这是通过接口块使用静态重载函数的替代方法,正如我的问题和@roygvib 的回答中隐含提到的那样。 (我认为明确地写这个是有意义的,特别是如果有人可以改进它的话。)
接口块方法的两个优点是:
- 它大约快了 3 倍(@roygvib 也发现了这一点,尽管我
不知道他到底是怎么写这个函数的)
- 它只需要 Fortran 90(不需要 Fortran 2003)
主要缺点是必须多次编写函数。如问题中所述,在此示例中,您必须编写 16 次乘法函数,才能处理 32 位和 64 位实数和整数的所有组合。这里并没有那么糟糕,功能是一行代码,但是您可以很容易地看到这对于许多实际用例来说更为严重。
下面是我用来测试界面块方法的代码。为了保持相对简洁,我只测试了 32 位实数和整数的 4 种排列。我重新使用主程序来测试@roygvib 代码。在我的 2015 macbook 上,大约需要 16 秒(界面块)与 48 秒(class(*) 方法)。
模块:
module mult_mod
use, intrinsic :: iso_fortran_env, only: i4 => int32, r4 => real32
interface mult
module procedure mult_real4_real4
module procedure mult_int4_real4
module procedure mult_real4_int4
module procedure mult_int4_int4
end interface mult
contains
function mult_real4_real4( p, q ) result( ans )
real(r4) :: p, q
real(r4) :: ans
ans = p * q
end function mult_real4_real4
function mult_int4_real4( p, q ) result( ans )
integer(i4) :: p
real(r4) :: q
real(r4) :: ans
ans = p * q
end function mult_int4_real4
function mult_real4_int4( p, q ) result( ans )
real(r4) :: p
integer(i4) :: q
real(r4) :: ans
ans = p * q
end function mult_real4_int4
function mult_int4_int4( p, q ) result( ans )
integer(i4) :: p, q
real(r4) :: ans
ans = p * q
end function mult_int4_int4
end module mult_mod
计划:
program main
use mult_mod
integer(i4) :: i = 2
real(r4) :: x = 2.0
integer(i4) :: i_end = 1e9
real(r4) :: result
do j = 1, i_end
result = mult( x, x )
result = mult( x, i )
result = mult( i, x )
result = mult( i, i )
end do
end program main
我有一个接受两个数字的函数,我不关心它们是整数、实数、32 位还是 64 位。对于下面的例子,我只是把它写成一个简单的乘法。在 Fortran 90 中,您可以使用接口块来完成此操作,但如果您想要涵盖两个数字相乘的所有可能交互,则必须编写 16(!)个函数,每个数字都可以是 int32、int64、real32 或真实 64.
对于 Fortran 2003,您还有一些其他选项,例如 class(*)
多态性,我找到了一种方法,只需在乘法之前将所有输入转换为实数即可:
! compiled on linux with gfortran 4.8.5
program main
integer, target :: i = 2
real(4), target :: x = 2.0
real(8), target :: y = 2.0
character, target :: c = 'a'
print *, multiply(i,x)
print *, multiply(x,i)
print *, multiply(i,i)
print *, multiply(y,y)
print *, multiply(c,c)
contains
function multiply(p,q)
real :: multiply
class(*) :: p, q
real :: r, s
r = 0.0 ; s = 0.0
select type(p)
type is (integer(4)) ; r = p
type is (integer(8)) ; r = p
type is (real(4)) ; r = p
type is (real(8)) ; r = p
class default ; print *, "p is not a real or int"
end select
select type(q)
type is (integer(4)) ; s = q
type is (integer(8)) ; s = q
type is (real(4)) ; s = q
type is (real(8)) ; s = q
class default ; print *, "q is not a real or int"
end select
multiply = r * s
end function multiply
end program main
这似乎是一个改进。至少这里的代码量是类型数量的线性而不是二次方,但我想知道是否还有更好的方法来做到这一点?如您所见,我仍然需要编写两次 select type
代码,将 'r' 更改为 's' 并将 'p' 更改为 'q'。
我试图将 select 类型块转换为一个函数,但无法使其工作。但我对可以进一步改进这一点的任何和所有替代方案感兴趣。这似乎是一个常见问题,但到目前为止我还没有找到比这更好的通用方法。
编辑添加: 显然有改进 Fortran 的计划 w.r.t。正如@SteveLionel 在评论中指出的那样,这个问题将在未来出现。 @roygvib 进一步提供了一个 link 到一个特定的提案,它也很好地解释了这个问题:https://j3-fortran.org/doc/year/13/13-236.txt
不是泛型的解决方案,但对于 "converting the select type blocks into a function",以下代码似乎有效(如果包含一些非平凡的转换可能会有用 (?))。
program main
implicit none
integer :: i = 2
real*4 :: x = 2.0
real*8 :: y = 2.0
character(3) :: c = 'abc'
print *, multiply( i, x )
print *, multiply( x, i )
print *, multiply( i, i )
print *, multiply( y, y )
print *, multiply( c, c )
contains
function toreal( x ) result( y )
class(*) :: x
real :: y
select type( x )
type is (integer) ; y = x
type is (real(4)) ; y = x
type is (real(8)) ; y = x
type is (character(*)) ; y = len(x)
class default ; stop "no match for x"
endselect
end
function multiply( p, q ) result( ans )
class(*) :: p, q
real :: ans
ans = toreal( p ) * toreal( q )
end
end program
! gfortran-8 test.f90 && ./a.out
4.00000000
4.00000000
4.00000000
4.00000000
9.00000000
另一种方法可能只是将实际参数转换为实数(尽管它可能对更实际的目的没有用...)
program main
implicit none
integer :: i = 2
real*4 :: x = 2.0
real*8 :: y = 2.0
character :: c = 'a'
print *, multiply( real(i), real(x) )
print *, multiply( real(x), real(i) )
print *, multiply( real(i), real(i) )
print *, multiply( real(y), real(y) )
! print *, multiply( real(c), real(c) ) ! error
contains
function multiply( p, q ) result( ans )
real :: p, q
real :: ans
ans = p * q
end
end program
这是通过接口块使用静态重载函数的替代方法,正如我的问题和@roygvib 的回答中隐含提到的那样。 (我认为明确地写这个是有意义的,特别是如果有人可以改进它的话。)
接口块方法的两个优点是:
- 它大约快了 3 倍(@roygvib 也发现了这一点,尽管我 不知道他到底是怎么写这个函数的)
- 它只需要 Fortran 90(不需要 Fortran 2003)
主要缺点是必须多次编写函数。如问题中所述,在此示例中,您必须编写 16 次乘法函数,才能处理 32 位和 64 位实数和整数的所有组合。这里并没有那么糟糕,功能是一行代码,但是您可以很容易地看到这对于许多实际用例来说更为严重。
下面是我用来测试界面块方法的代码。为了保持相对简洁,我只测试了 32 位实数和整数的 4 种排列。我重新使用主程序来测试@roygvib 代码。在我的 2015 macbook 上,大约需要 16 秒(界面块)与 48 秒(class(*) 方法)。
模块:
module mult_mod
use, intrinsic :: iso_fortran_env, only: i4 => int32, r4 => real32
interface mult
module procedure mult_real4_real4
module procedure mult_int4_real4
module procedure mult_real4_int4
module procedure mult_int4_int4
end interface mult
contains
function mult_real4_real4( p, q ) result( ans )
real(r4) :: p, q
real(r4) :: ans
ans = p * q
end function mult_real4_real4
function mult_int4_real4( p, q ) result( ans )
integer(i4) :: p
real(r4) :: q
real(r4) :: ans
ans = p * q
end function mult_int4_real4
function mult_real4_int4( p, q ) result( ans )
real(r4) :: p
integer(i4) :: q
real(r4) :: ans
ans = p * q
end function mult_real4_int4
function mult_int4_int4( p, q ) result( ans )
integer(i4) :: p, q
real(r4) :: ans
ans = p * q
end function mult_int4_int4
end module mult_mod
计划:
program main
use mult_mod
integer(i4) :: i = 2
real(r4) :: x = 2.0
integer(i4) :: i_end = 1e9
real(r4) :: result
do j = 1, i_end
result = mult( x, x )
result = mult( x, i )
result = mult( i, x )
result = mult( i, i )
end do
end program main