线性同余发生器 - 输出全为0?

时间:2015-10-18 04:00:49

标签: random scope fortran fortran77

我一直在尝试在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

有没有人有任何想法?我刚刚结束了。

2 个答案:

答案 0 :(得分:1)

自从我在Fortran编程以来,已有几十年了,但我会尽力帮助。

首先,IMAXINV是一个整数变量,因为名称以I开头,并且您没有声明它是一个浮点数。因此,除法结果将被截断为整数值0,这将解释零输出。在任何情况下,你的随机数生成器都应该坚持使用整数运算,而不是为了正确性和速度而引入泛洪点操作。

Fortran 77支持返回值的函数,是吗?与将子程序的结果存储在全局变量中相比,这将更清晰,更模块化。

IIRC,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往往会使代码的可读性更低......
  • 我将程序文件命名为&#34; test.f90&#34; (注意后缀.f90),允许自由格式。此外,可以使用小写字母表示变量。
  • [另外,因为iseed存储了关于(伪)随机数生成器当前状态的信息,所以最好使用一些不同的可变名称(如istate等?)来提醒它的值需要在通话期间保留。]

因此,如果您有兴趣,请考虑使用更现代版的Fortran(而非Fortran77),它允许我们编写更安全,更健壮的代码:)