我一直在尝试在Fortran 77中制作一个非常基本的LCG伪随机数生成器,将1000个随机数打印到文件中,但无论出于何种原因,输出只有1000 0s。整个代码很短,所以我多次梳理它并尝试改变一些事情,但我不能为我的生活弄清楚什么是错的。我预感它可能是一个范围问题(如果这样的概念在Fortran中甚至有用),但这确实没有根据。
PROGRAM RANDOM
COMMON ISEED, RANDOMNUMBER
ISEED = 123
OPEN (UNIT=1,FILE='rand.in',STATUS='UNKNOWN')
J=1
7 CALL RANDU(ISEED)
J=J+1
WRITE(1,*) RANDOMNUMBER
IF(J<1000)GOTO 7
STOP
END
SUBROUTINE RANDU(ISEED)
PARAMETER (IMAX = 2147483647, IMAXINV = 1./IMAX)
ISEED = ISEED * 65539
IF(ISEED<0) ISEED = ISEED + IMAX + 1
RANDOMNUMBER = ISEED * IMAXINV
RETURN
END
有没有人有任何想法?我刚刚结束了。
答案 0 :(得分:1)
自从我在Fortran编程以来,已有几十年了,但我会尽力帮助。
首先,IMAXINV
是一个整数变量,因为名称以I
开头,并且您没有声明它是一个浮点数。因此,除法结果将被截断为整数值0
,这将解释零输出。在任何情况下,你的随机数生成器都应该坚持使用整数运算,而不是为了正确性和速度而引入泛洪点操作。
Fortran 77支持返回值的函数,是吗?与将子程序的结果存储在全局变量中相比,这将更清晰,更模块化。
COMMON
语句用于在模块之间共享全局值,这对随机数生成器的私有状态来说是一个危险的事情。
您有一个名为COMMON
的{{1}}全局变量和一个具有相同名称的子例程形式参数(除非我记错了Fortran子例程声明的工作方式)。这会使事情变得混乱,应该修复。让子例程更新其参数ISEED
而不是全局变量将导致它在每次循环调用它时返回相同的值。也就是说,除非形式参数是实际参数的按引用调用别名 - 在此代码中具有相同名称。你知道,这令人困惑。
你有调试器吗?如果是这样,单步执行程序并观察变量将很快显示程序偏离预期的位置。
答案 1 :(得分:1)
现在可以通过@Jerry101来增加答案,我已经编写了修改过的代码。这里的关键问题是IMAXINV
未明确声明为REAL
,因此它被解释为INTEGER
(因此,IMAXINV = 1.0 / IMAX
在原始代码中始终为0 )。此外,我已从ISEED
块中删除COMMON
(因为它作为参数传递),并在COMMON
中添加另一个RANDU
语句以在例程之间共享变量。通过这些修改,程序似乎可以正常工作。
PROGRAM RANDOM
COMMON RANDOMNUMBER !<--- ISEED is deleted from here
ISEED = 123
J=1
7 CALL RANDU(ISEED)
J=J+1
WRITE(*,*) RANDOMNUMBER !<--- write to STDOUT for test
IF (J < 100) GOTO 7
END
SUBROUTINE RANDU(ISEED)
real IMAXINV !<--- this is necessary
COMMON RANDOMNUMBER !<--- this is also necessary to share variables
PARAMETER (IMAX = 2147483647, IMAXINV = 1./IMAX)
ISEED = ISEED * 65539
IF(ISEED<0) ISEED = ISEED + IMAX + 1
RANDOMNUMBER = ISEED * IMAXINV
END
正如其他答案中所建议的,我们也可以使用FUNCTION
直接返回变量。然后我们不需要使用COMMON
,因此代码变得更加清晰。
PROGRAM RANDOM
ISEED = 123
J=1
7 RANDOMNUMBER = RANDU(ISEED)
J=J+1
WRITE(*,*) RANDOMNUMBER
IF (J < 100) GOTO 7
END
FUNCTION RANDU(ISEED)
real IMAXINV
PARAMETER (IMAX = 2147483647, IMAXINV = 1./IMAX)
ISEED = ISEED * 65539
IF(ISEED<0) ISEED = ISEED + IMAX + 1
RANDU = ISEED * IMAXINV !<--- "RANDU" is the return variable
END
但请注意,当使用FUNCTION
时,如果函数名称不符合隐式规则,则应在调用例程中显式声明返回变量的类型。 (在上面的代码中,RANDU
未明确声明,因为它被解释为REAL
)。所以无论如何,Fortran77中的隐式类型规则有许多警告......
附加说明:
为了避免这些陷阱,我建议使用Fortran&gt; = 90(而不是Fortran77),因为它提供了许多功能来防止此类错误。例如,最低限度修改的代码可能如下所示:
module mymodule
contains
subroutine randu ( istate, ran )
implicit none
integer, parameter :: IMAX = 2147483647
real, parameter :: IMAXINV = 1.0 / IMAX
integer, intent(inout) :: istate
real, intent(out) :: ran
istate = istate * 65539
if ( istate < 0 ) istate = istate + IMAX + 1
ran = istate * IMAXINV
end subroutine
end module
program main
use mymodule, only: randu
implicit none
integer :: j, istate
real :: randomnumber
istate = 123 !! seed for RANDU()
do j = 1, 99
call randu ( istate, randomnumber )
write(*,*) randomnumber
enddo
end program
下面,
implicit none
用于明确强制声明所有变量。这有助于避免错误输入变量(例如问题中的IMAXINV
。)。RANDU
包含在module
中,因此编译器提供了显式接口和许多有用的检查(简而言之,module
类似于C ++中的命名空间)。 module
也可用于以比COMMON
更安全的方式定义全局变量。do
... enddo
构造来循环j
,而不是手动递增并使用goto
。前者实际上更容易使用,而goto
往往会使代码的可读性更低...... iseed
存储了关于(伪)随机数生成器当前状态的信息,所以最好使用一些不同的可变名称(如istate等?)来提醒它的值需要在通话期间保留。] 因此,如果您有兴趣,请考虑使用更现代版的Fortran(而非Fortran77),它允许我们编写更安全,更健壮的代码:)