Random 随机数发生器的上界

Random 随机数发生器的上界,random,fortran,rounding,fortran77,mersenne-twister,Random,Fortran,Rounding,Fortran77,Mersenne Twister,这实际上是前一个问题的后续问题: 在用上一个问题的答案解决了我的问题后,我再次尝试运行我的程序,发现我也有同样的问题 我使用的Mersenne Twister实现生成一个有符号的32位随机整数。 实现RNG的人使用此函数生成[0,1]范围内的随机双精度浮点: 它的工作原理完美无瑕,因此根据上一个问题中的建议,我使用以下函数生成了一个随机单精度浮点,范围我认为是[0,1]: 但是,我得到了与以前相同的错误,是由1.0数字引起的。因此我编写了一个小程序来显示我的genrand_real实际生成了1

这实际上是前一个问题的后续问题:

在用上一个问题的答案解决了我的问题后,我再次尝试运行我的程序,发现我也有同样的问题

我使用的Mersenne Twister实现生成一个有符号的32位随机整数。 实现RNG的人使用此函数生成[0,1]范围内的随机双精度浮点:

它的工作原理完美无瑕,因此根据上一个问题中的建议,我使用以下函数生成了一个随机单精度浮点,范围我认为是[0,1]:

但是,我得到了与以前相同的错误,是由1.0数字引起的。因此我编写了一个小程序来显示我的genrand_real实际生成了1.0,发现我是对的,并且生成了1.0。这导致了我生成范围为[1,MAX]的整数的方式(在本例中为[1,5])无法生成值MAX+1,以及我正在处理的代码中的其他不便之处

  i = 0
  do while (.true.)
    r = genrand_real()
    if (r .gt. 0.99999) then
        i = i + 1
        print *, 'number is:', r
        print *, 'conversion is: ', int(5*r)+1
    endif
    if (i .gt. tot_large) exit
  enddo

我的问题是,为什么它适用于双精度浮点而不适用于单精度浮点?我看不出它失败的原因,因为2**32适用于单精度浮点。另外,我应该怎么做来解决它?我考虑过用2.0**32+1除以数字,而不是2.0**32,但我不确定它在理论上是否正确,并且数字是否正确s将是统一的。

我不确定是否将此答案张贴在旧问题上或此处。在任何情况下,我可能会有一个解决方案(在第二个代码块中)

从大约两年前开始,我在同一项任务中使用的例行程序如下:

function uniran( )
    implicit none
    integer, parameter :: dp = selected_real_kind(15, 307)
    real(dp)  ::  tmp
    real :: uniran
    tmp = 0.5_dp + 0.2328306e-9_dp * genrand_int32( )
    uniran = real(tmp)
end function uniran
我忘记了代码来自何处,尽管代码很简单,但其中有一个微妙的技巧,我现在才意识到。明显的区别是乘法而不是除法,但这只是因为用固定数字乘法比除法(0.2328306e-9=1/4294967296)快。
诀窍是:这不是真的。1/4294967296=0.23283064365386962890625e-9,因此程序使用的有效数字少于双精度所能容纳的有效数字(15,而仅使用7)。如果增加位数,则生成的数字将更接近1,并在以后的转换过程中正好变为1。您可以尝试:如果只使用一个数字,则它将开始失败(=1.0)。 显然,这个解决方案有点像黑客,所以我也尝试了另一种方法,如果结果正好是1,则重新采样:

recursive function resample_uniran( ) result(res)
    implicit none
    integer, parameter :: dp = selected_real_kind(15, 307)
    real(dp)  ::  tmp
    real :: res
    tmp = 0.5_dp + 0.23283064365386962890625e-9_dp * genrand_int32( )
    res = real(tmp)
    if (res == 1.0) then
        res = resample_uniran()
    end if
end function resample_uniran
我编写了一个测试函数的程序(包含函数和子例程的模块在文章的末尾,相对较长):

结果是,
genrand_real
经常失败(=1.0)(我们说的是每几百万个数字),而其他两个迄今为止从未失败过。 递归版本需要花费时间,但在技术上更好,因为最大可能的数字接近1

我还测试了速度和“一致性”,并将其与内在的
随机数
子例程进行了比较,该子例程在[0,1]中也给出了一致的随机数。 (小心,这会创建3 x 512 MB的文件)

尽管时间不同,但原则上结果始终相同:

uniran took   0.700139999      s to produce    134217728  PRNs
resamp took   0.737253010      s to produce    134217728  PRNs
intrin took   0.773686171      s to produce    134217728  PRNs
uniran比resample_uniran快,resample_uniran比内在的要快(虽然这在很大程度上取决于PRNG,但Mersene twister将比内在的慢)

我还查看了每个方法(使用Python)提供的输出:

所有三个直方图在视觉上看起来都很好,但最高值揭示了uniran的缺点:

uniran:
0.999999880790710
resample uniran:
0.999999940395355
intrinsic:
0.999999940395355
我运行了几次,结果总是相同的。
resample\u uniran
和内在函数具有相同的最高值,而
uniran
也总是相同的,但较低。 我想做一些稳健的统计测试来表明输出的一致性,但是在尝试Anderson-Darling测试、Kuiper测试和我遇到的Kolmogorov-Smirnov测试时,基本上,样本越多,测试发现输出有问题的可能性就越高。 也许有人应该这样做,但我还没想到

为完整起见,
模块

module mod_prngtest
implicit none
integer :: iseed_i, iseed_j, iseed_k, iseed_n
integer, dimension(4) :: seed

contains

    function uniran( )
    ! Generate uniformly distributed random numbers in [0, 1) from genrand_int32
    ! New version
        integer, parameter :: dp = selected_real_kind(15, 307)
        real(dp)  ::  tmp
        real :: uniran
        tmp = 0.5_dp + 0.2328306e-9_dp * genrand_int32( )
        uniran = real(tmp)
    end function uniran

    recursive function resample_uniran( ) result(res)
    ! Generate uniformly distributed random numbers in [0, 1) from genrand_int32
    ! New version, now recursive
        integer, parameter :: dp = selected_real_kind(15, 307)
        real(dp)  ::  tmp
        real :: res
        tmp = 0.5_dp + 0.23283064365386962890625e-9_dp * genrand_int32( )
        res = real(tmp)
        if (res == 1.0) then
            res = resample_uniran()
        end if
    end function resample_uniran

    recursive subroutine both(uniran, resamp)
        integer, parameter :: dp = selected_real_kind(15, 307)
        real(dp)  ::  tmp1, tmp2
        integer :: prn
        real :: uniran, resamp

        prn = genrand_int32( )

        tmp1 = 0.5_dp + 0.2328306e-9_dp * prn
        uniran = real(tmp1)

        tmp2 = 0.5_dp + 0.23283064365386962890625e-9_dp * prn
        resamp = real(tmp2)
        if (resamp == 1.0) then
            call both(uniran, resamp)
        end if
    end subroutine both

    function genrand_real()
    ! Generate uniformly distributed random numbers in [0, 1) from genrand_int32
    ! Your version, modified by me earlier
        real genrand_real, r
        r = real(genrand_int32())
        if (r .lt. 0.0) r = r + 2.0**32
        genrand_real = r / 4294967296.0
        return
    end

    subroutine init_genrand_int32()
    ! seed the PRNG, if you don't have /dev/urandom comment out this block ...
        open(11, file='/dev/urandom', form='unformatted', access='stream')
        read(11) seed
        iseed_i=1+abs(seed( 1))
        iseed_j=1+abs(seed( 2))
        iseed_k=1+abs(seed( 3))
        iseed_n=1+abs(seed( 4))

    ! ... and use this block instead (any integer > 0)
        !iseed_i = 1253795357
        !iseed_j = 520466003
        !iseed_k = 68202083
        !iseed_n = 1964789093
    end subroutine init_genrand_int32

    function genrand_int32()
    ! From Marsaglia 1994, return pseudorandom integer over the
    ! whole range. Fortran doesn't have a function like that intrinsically.
    ! Replace this with your Mersegne twister PRNG
        implicit none
        integer :: genrand_int32
        genrand_int32=iseed_i-iseed_k
        if(genrand_int32.lt.0)genrand_int32=genrand_int32+2147483579
        iseed_i=iseed_j
        iseed_j=iseed_k
        iseed_k=genrand_int32
        iseed_n=69069*iseed_n+1013904243
        genrand_int32=genrand_int32+iseed_n
    end function genrand_int32

    subroutine init_random_seed()
        use iso_fortran_env, only: int64
        implicit none
        integer, allocatable :: seed(:)
        integer :: i, n, un, istat, dt(8), pid
        integer(int64) :: t

        call random_seed(size = n)
        allocate(seed(n))
        ! First try if the OS provides a random number generator
        open(newunit=un, file="/dev/urandom", access="stream", &
            form="unformatted", action="read", status="old", iostat=istat)
        if (istat == 0) then
            read(un) seed
            close(un)
        else
            ! Fallback to XOR:ing the current time and pid. The PID is
            ! useful in case one launches multiple instances of the same
            ! program in parallel.
            call system_clock(t)
            if (t == 0) then
                call date_and_time(values=dt)
                t = (dt(1) - 1970) * 365_int64 * 24 * 60 * 60 * 1000 &
                     + dt(2) * 31_int64 * 24 * 60 * 60 * 1000 &
                     + dt(3) * 24_int64 * 60 * 60 * 1000 &
                     + dt(5) * 60 * 60 * 1000 &
                     + dt(6) * 60 * 1000 + dt(7) * 1000 &
                     + dt(8)
            end if
            pid = getpid()
            t = ieor(t, int(pid, kind(t)))
            do i = 1, n
                seed(i) = lcg(t)
            end do
        end if
        call random_seed(put=seed)
    contains
        ! This simple PRNG might not be good enough for real work, but is
        ! sufficient for seeding a better PRNG.
        function lcg(s)
           integer :: lcg
           integer(int64) :: s
           if (s == 0) then
               s = 104729
           else
               s = mod(s, 4294967296_int64)
           end if
           s = mod(s * 279470273_int64, 4294967291_int64)
           lcg = int(mod(s, int(huge(0), int64)), kind(0))
        end function lcg
      end subroutine init_random_seed
end module mod_prngtest

我一点也不懂Fortran,但请尝试以下内容:

function genrand_real()
  real genrand_real, r
  integer genrand_int32
  r = real(IAND(genrand_int32(), 16777215))
  genrand_real = r / 16777216.0
  return
end
我冒着用一种我不知道的语言误传浮点舍入的优点的风险,但无论如何我会尝试

您的问题是,您试图将太多位压缩到32位浮点值的尾数中。这会导致舍入问题,这会使值太接近1.0而精确到1.0。同时,它会导致值从0.0舍入,并且由于没有任何低于0的值舍入到0,因此会给您留下错误获得0.0的可能性比正常情况小


如果您试图通过使用32位并调整比例因子使其安全地低于1.0来解决此问题,那么您仍然面临着非均匀分布的问题。但是如果您仅通过使用尽可能多的位来修复整数空间中的范围(32位浮点为24位)这样,您就不必担心值会以不平衡的方式向上或向下舍入。

这里有很多关于浮点运算的微妙之处。您对这些概念的总体理解如何?不过,一般的答案可能是:不要使用实变量(
r
)存储这样大小的整数。我上过计算机体系结构的课程,知道它的基础知识(虽然不是很深的知识)。单精度不足以存储2.0**32(据我所知,它是)?如果我需要从一个32整数生成一个单精度浮点,那么最好的方法是什么?虽然2**32适合一个单精度浮点,但它不适合尾数,最终会出现数字错误。简言之,请
uniran took   0.700139999      s to produce    134217728  PRNs
resamp took   0.737253010      s to produce    134217728  PRNs
intrin took   0.773686171      s to produce    134217728  PRNs
import numpy as np
import matplotlib.pyplot as plt

def read1dbinary(fname, xdim):
    with open(fname, 'rb') as fid:
        data = np.fromfile(file=fid, dtype=np.single)
    return data

if __name__ == '__main__':
    n = 2**27
    data_uniran = read1dbinary('uniran.out', n)
    print('uniran:')
    print('{0:.15f}'.format(max(data_uniran)))
    plt.hist(data_uniran, bins=1000)
    plt.show()

    data_resamp = read1dbinary('resamp.out', n)
    print('resample uniran:')
    print('{0:.15f}'.format(max(data_resamp)))
    plt.hist(data_resamp, bins=1000)
    plt.show()

    data_intrin = read1dbinary('intrin.out', n)
    print('intrinsic:')
    print('{0:.15f}'.format(max(data_intrin)))
    plt.hist(data_intrin, bins=1000)
    plt.show()
uniran:
0.999999880790710
resample uniran:
0.999999940395355
intrinsic:
0.999999940395355
module mod_prngtest
implicit none
integer :: iseed_i, iseed_j, iseed_k, iseed_n
integer, dimension(4) :: seed

contains

    function uniran( )
    ! Generate uniformly distributed random numbers in [0, 1) from genrand_int32
    ! New version
        integer, parameter :: dp = selected_real_kind(15, 307)
        real(dp)  ::  tmp
        real :: uniran
        tmp = 0.5_dp + 0.2328306e-9_dp * genrand_int32( )
        uniran = real(tmp)
    end function uniran

    recursive function resample_uniran( ) result(res)
    ! Generate uniformly distributed random numbers in [0, 1) from genrand_int32
    ! New version, now recursive
        integer, parameter :: dp = selected_real_kind(15, 307)
        real(dp)  ::  tmp
        real :: res
        tmp = 0.5_dp + 0.23283064365386962890625e-9_dp * genrand_int32( )
        res = real(tmp)
        if (res == 1.0) then
            res = resample_uniran()
        end if
    end function resample_uniran

    recursive subroutine both(uniran, resamp)
        integer, parameter :: dp = selected_real_kind(15, 307)
        real(dp)  ::  tmp1, tmp2
        integer :: prn
        real :: uniran, resamp

        prn = genrand_int32( )

        tmp1 = 0.5_dp + 0.2328306e-9_dp * prn
        uniran = real(tmp1)

        tmp2 = 0.5_dp + 0.23283064365386962890625e-9_dp * prn
        resamp = real(tmp2)
        if (resamp == 1.0) then
            call both(uniran, resamp)
        end if
    end subroutine both

    function genrand_real()
    ! Generate uniformly distributed random numbers in [0, 1) from genrand_int32
    ! Your version, modified by me earlier
        real genrand_real, r
        r = real(genrand_int32())
        if (r .lt. 0.0) r = r + 2.0**32
        genrand_real = r / 4294967296.0
        return
    end

    subroutine init_genrand_int32()
    ! seed the PRNG, if you don't have /dev/urandom comment out this block ...
        open(11, file='/dev/urandom', form='unformatted', access='stream')
        read(11) seed
        iseed_i=1+abs(seed( 1))
        iseed_j=1+abs(seed( 2))
        iseed_k=1+abs(seed( 3))
        iseed_n=1+abs(seed( 4))

    ! ... and use this block instead (any integer > 0)
        !iseed_i = 1253795357
        !iseed_j = 520466003
        !iseed_k = 68202083
        !iseed_n = 1964789093
    end subroutine init_genrand_int32

    function genrand_int32()
    ! From Marsaglia 1994, return pseudorandom integer over the
    ! whole range. Fortran doesn't have a function like that intrinsically.
    ! Replace this with your Mersegne twister PRNG
        implicit none
        integer :: genrand_int32
        genrand_int32=iseed_i-iseed_k
        if(genrand_int32.lt.0)genrand_int32=genrand_int32+2147483579
        iseed_i=iseed_j
        iseed_j=iseed_k
        iseed_k=genrand_int32
        iseed_n=69069*iseed_n+1013904243
        genrand_int32=genrand_int32+iseed_n
    end function genrand_int32

    subroutine init_random_seed()
        use iso_fortran_env, only: int64
        implicit none
        integer, allocatable :: seed(:)
        integer :: i, n, un, istat, dt(8), pid
        integer(int64) :: t

        call random_seed(size = n)
        allocate(seed(n))
        ! First try if the OS provides a random number generator
        open(newunit=un, file="/dev/urandom", access="stream", &
            form="unformatted", action="read", status="old", iostat=istat)
        if (istat == 0) then
            read(un) seed
            close(un)
        else
            ! Fallback to XOR:ing the current time and pid. The PID is
            ! useful in case one launches multiple instances of the same
            ! program in parallel.
            call system_clock(t)
            if (t == 0) then
                call date_and_time(values=dt)
                t = (dt(1) - 1970) * 365_int64 * 24 * 60 * 60 * 1000 &
                     + dt(2) * 31_int64 * 24 * 60 * 60 * 1000 &
                     + dt(3) * 24_int64 * 60 * 60 * 1000 &
                     + dt(5) * 60 * 60 * 1000 &
                     + dt(6) * 60 * 1000 + dt(7) * 1000 &
                     + dt(8)
            end if
            pid = getpid()
            t = ieor(t, int(pid, kind(t)))
            do i = 1, n
                seed(i) = lcg(t)
            end do
        end if
        call random_seed(put=seed)
    contains
        ! This simple PRNG might not be good enough for real work, but is
        ! sufficient for seeding a better PRNG.
        function lcg(s)
           integer :: lcg
           integer(int64) :: s
           if (s == 0) then
               s = 104729
           else
               s = mod(s, 4294967296_int64)
           end if
           s = mod(s * 279470273_int64, 4294967291_int64)
           lcg = int(mod(s, int(huge(0), int64)), kind(0))
        end function lcg
      end subroutine init_random_seed
end module mod_prngtest
function genrand_real()
  real genrand_real, r
  integer genrand_int32
  r = real(IAND(genrand_int32(), 16777215))
  genrand_real = r / 16777216.0
  return
end