编写接受任意两个数字(任意实数或任意整数)的函数

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