【问题标题】:Writing a function that accepts any two numbers (any real or any integer)编写一个接受任何两个数字(任何实数或任何整数)的函数
【发布时间】:2020-01-07 14:50:16
【问题描述】:

我有一个接受两个数字的函数,我不在乎它们是整数、实数还是 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”。

我尝试将选择类型块转换为函数,但无法使其工作。但我对任何可以进一步改进的替代方案感兴趣。看起来这将是一个常见问题,但到目前为止我还没有找到任何比这更好的通用方法。

编辑添加:显然有计划改进 Fortran w.r.t.正如@SteveLionel 的评论中所指出的那样,将来会出现这个问题。 @roygvib 进一步提供了一个特定提案的链接,该提案也很好地解释了这个问题:https://j3-fortran.org/doc/year/13/13-236.txt

【问题讨论】:

  • 如果您使用use iso_fortran_env,您可以使用type is (real(4))type is (real(8))real32real64 而不是48
  • @AboAmmar 谢谢!我已更新问题以反映此建议。
  • 这是一种可以通过泛型编程解决的问题。 Fortran 标准没有很好的特性,但是委员会已经开始开发这样的工作,以便将来对该标准进行修订。
  • @SteveLionel 谢谢,我很期待!

标签: fortran fortran2003


【解决方案1】:

不是泛型的解决方案,而是“将选择类型块转换为函数”,以下代码似乎有效(如果包含一些重要的转换(?),这可能很有用)。

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

【讨论】:

  • 是的,这很好!我试图做一些非常相似的事情,但这更优雅(更不用说我的实际上不会编译;-) 第二种方法(只是包装在 real() 中)是我们目前的方法,但我认为通过一些近似 Julia 的方法来自动化它如果可行,多分派通常是更好的方法。
  • 如果像这样将所有内容都转换为real32,如何保持real64 的精度?
  • 但是一个可能的问题是选择类型可能非常慢(尽管它也可能取决于编译器+选项..)。例如,如果我多次使用上面的 multiply()(比如 5 * 10^8),gfortran-8 -O2 需要 2.8 秒,而 toreal() 的静态重载版本(通过接口块定义)只需要 1.1秒。因此,对于“热”部件,最好避免使用它...
  • @roygvib 好点,我认为情况会如此,但实际上你上面引用的减速对于我的用例来说是好的,相对于获得的简洁性和便利性。但是我可以看到对于其他用例来说这是值得的,如果我正确地考虑了这一点,您只需要使 toreal 函数可管理地更大(即线性,而不是二次,数量增加数字类型),而不是触摸multiply 功能。如果您愿意,可能值得写出新答案或添加到您现有的答案中......
  • 似乎有一个旧提案(2013 年),但不知道当前状态...但是j3-fortran.org/doc/year/13/13-236.txt
【解决方案2】:

这是一种通过接口块使用静态重载函数的替代方法,正如我的问题和@roygvib 的回答中隐含地提到的那样。 (我认为明确地写出来是有意义的,特别是如果有人可以改进它的话。)

接口块方法的两个优点是:

  • 大约快 3 倍(@roygvib 也发现,虽然我 不知道他是怎么写函数的)
  • 它只需要 Fortran 90(不是 Fortran 2003)

主要缺点是您必须多次编写函数。如问题中所述,在此示例中,您必须编写乘法函数 16 次,以处理 32 位和 64 位实数和整数的所有组合。这里并没有那么糟糕,函数是一行代码,但您可以很容易地看到,这对于许多实际用例来说更为严重。

下面是我用来测试接口块方法的代码。为了保持相对简洁,我只测试了 32 位实数和整数的 4 种排列。我重新使用了主程序来测试@roygvib 代码。在我 2015 年的 macbook 上,大约需要 16 秒(接口块)与 48 秒(类(*)方法)。

模块:

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

【讨论】:

  • @jbdv 好的,我只是更仔细地重新阅读链接的问答并了解您的观点。我只是要删除评论,并认为人们知道使用他们使用的任何编译器声明实数和整数的细节;-) 随时再次编辑,我会批准或应用您想做的任何事情,我可以看到你知道你在说什么
猜你喜欢
  • 2012-09-08
  • 1970-01-01
  • 2019-09-14
  • 2019-12-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-28
  • 1970-01-01
相关资源
最近更新 更多