Search code examples
fortranfortran90accumulator

Accumulator within a DO loop


My goal is to create 10,000 randomly generated numbers between 0 and 1, organize them into ten bins evenly spaced between 0 and 1, and compute a frequency for each bin. This is my code so far.

program listrand
implicit none
integer :: n,p
integer :: a,b,c,d,e,f,g,h,i,j = 0
real :: xran
!real, dimension(10,2) :: bin_and_freq -- list of bins and frequency

do n = 1,10000
    call random_number(xran)
            if (xran <  0.1) then
                a = a + 1
            elseif (xran>0.1 .and. xran<0.2) then
                b = b + 1
            elseif (xran>0.2 .and. xran<0.3) then
                c = c+1
            elseif (xran>0.3 .and. xran<0.4) then
                d = d+1
            elseif (xran>0.4 .and. xran<0.5) then
                e = e + 1
            elseif (xran>0.5 .and. xran<0.6) then
                f = f+1
            elseif (xran>0.6 .and. xran<0.7) then
                g = g+1
            elseif (xran>0.7 .and. xran<0.8) then
                h=h+1
            elseif (xran>0.8 .and. xran<0.9) then
                i=i+1
            else
                j = j+1
            endif
enddo
print *, a,b,c,d,e,f,g,h,i,j
end program listrand

I am getting an unexpected output: 988 1036 133225987 1004 934 986 1040 33770 1406729616 1052.

Why are c,h, and i so large? Also, is there a more efficient way of going about this than using the unwieldy IF/ELSEIF block I have?


Solution

  • In your long

    integer :: a,b,c,d,e,f,g,h,i,j = 0
    

    You are only initialising j to be 0, all others have random numbers in them. If you add

    a = 0
    b = 0
    c = 0
    d = 0
    e = 0
    f = 0
    g = 0
    h = 0
    i = 0
    j = 0
    

    before your loop, everything works well.

    As for how to simplify it:

    Here is my version of the program:

    program listrand
    implicit none
    integer, parameter :: nbins = 10
    integer :: n, bin
    integer :: bin_hits(nbins)   ! Number of bin hits
    real :: xran
    real :: bin_lower(nbins)     ! Lower edge of bins
                                 ! bin_lower(1) == 0.0
    
    bin_hits = 0
    
    ! Set up equidistant bins
    bin_lower = [ (real(n-1) / nbins, n = 1, size(bin_lower)) ]
    
    do n = 1,10000
        call random_number(xran)
        bin = count(bin_lower <= xran) 
        bin_hits(bin) = bin_hits(bin)+1
    enddo
    do n = 1, nbins-1
        print '(2(F6.2), I6)' bin_lower(n), bin_lower(n+1), bin_hits(n)
    end do
    print '(2(F6.2), I6)' bin_lower(nbins), 1.0, bin_hits(nbins)
    end program listrand
    

    For the index of which bin_hits element to increment, I'm counting the number of values in bin_lower that are actually lower than xran.

    EDIT

    I'd like to also point to the answer from High Performance Mark a bit further down, who instead of calling RANDOM_NUMBER for each value individually uses it to generate a whole array of random numbers.

    Additionally, he's using the fact that the bins are fixed and equidistant to calculate the bin number directly from the random value instead of comparing it to each bin as in my version.

    Both of these make the program faster.