2015-10-18 4 views
1

Я пытался создать довольно простой генератор псевдослучайных чисел LCG в Fortran 77 для печати 1000 случайных чисел в файл, но по какой-либо причине выход составляет всего 1000 0. Весь код довольно короткий, поэтому я много раз его расчесывал и пытался изменить некоторые вещи, но я не могу в жизни понять, что не так. У меня есть догадка, что это может быть проблема с областью (если такая концепция даже полезна в Fortran), но это действительно необоснованно.Линейный конгруэнтный генератор - выход - все 0s?

 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

Используйте основной Фортран тег, чтобы сделать ваш пост более заметен. Существует множество высококачественных генераторов случайных чисел в Fortran. Fortran 90 даже имеет собственный собственный (неуказанное качество). Кроме того, отладка в этом веке проще. Фортран и область действия, безусловно, важная концепция в Fortran. –

ответ

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> = 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 имеет тенденцию делать код часто менее читаемым ...
  • Я назвал файл программы как «test.f90» (обратите внимание на суффикс .f90), который позволяет свободно форматировать. Кроме того, для переменных полезно использовать строчные буквы.
  • [Кроме того, поскольку iseed хранит информацию о текущем состоянии генератора случайных чисел (псевдо), может быть лучше использовать какое-то другое имя переменной (например, istate и т. Д.), Чтобы напомнить, что его значение необходимо сохранить во время вызовов .]

Так что, если вы заинтересованы, пожалуйста, рассмотрите возможность использования более современную версию Fortran (а не Fortran77), что позволяет нам писать более безопасные и надежные коды :)

+1

Я в основном согласен с тем, что вы говорите, но предложение использовать функцию для PRNG меня просто слишком расстраивает. – francescalus

+0

@francescalus Я делаю рискованное кодирование с помощью «функции + PRNG» здесь ...? (Я был бы признателен, если бы вы объяснили проблему более подробно.) [Если это связано с переменной «iseed» для сохранения текущего статуса PRNG, да, это имя очень вводит в заблуждение.] Кроме того, я добавлю комментарий о DO также (да, это хорошая точка :) – roygvib

+2

Я бы не сказал «рискованный» (нужно просто знать разные аспекты), но это стиль. Изменение значения аргумента 'iseed' является массивным побочным эффектом.По общему признанию, использование таких функций, как это, я просто должен принять ... Я не против побочных эффектов в функциях (будучи прагматичным), но для PRNG они слишком вопиющие по моему вкусу. – francescalus

1

Прошло десять лет с тех пор, как я запрограммировал в Фортране, но я постараюсь помочь.

Прежде всего, IMAXINV является целочисленной переменной, так как имя начинается с I, и вы не объявляли его плавающим. Таким образом, результат разделения будет усечен до целочисленного значения 0, что объясняет ваши нулевые выходы. В любом случае ваш генератор случайных чисел должен придерживаться целочисленных операций, а не вводить операции точки затопления, как для правильности, так и для скорости.

Fortran 77 поддерживает функции, возвращающие значения, да? Это было бы более чистым и более модульным, чем сохранение результата подпрограммы в глобальной переменной.

Операции IIRC, COMMON предназначены для обмена глобальными значениями между модулями, что является рискованной вещью для частного состояния генератора случайных чисел.

У вас есть глобальная переменная COMMON с именем ISEED и формальный параметр подпрограммы с тем же именем (если я не помню, как работают объявления подпрограммы Fortran). Это будет путать вещи и должно быть исправлено. Если подпрограмма обновит свой параметр ISEED, а не глобальную переменную, она будет возвращать одно и то же значение каждый раз, когда этот цикл вызывает его. То есть, если формальный параметр не является псевдонимом вызова по ссылке для фактического аргумента - с тем же именем в этом коде. Понимаете, это сбивает с толку.

У вас есть отладчик? Если это так, один шаг через программу и просмотр переменных быстро выявит, где программа отклоняется от ваших ожиданий.

+0

У меня, к сожалению, нет отладчика, но ваш комментарий был очень полезен. Боюсь, я не вижу, где у меня есть параметр подпрограммы ISEED, если только я не понял, как объявляются параметры подпрограммы. Я определенно думаю, что это случай случайного изменения неправильной переменной, но я не уверен, как ее поймать. Я сделал тест, где я напечатал ISEED в том же файле, и похоже, что он правильно меняется. – fireballs

+1

Прохладный. IIRC, чтобы подпрограмма вернула значение, присвойте значение переменной с тем же именем, что и подпрограмма. В этом случае вам нужно либо объявить «RANDU» как функцию с возвратом целого числа, либо просто переименовать ее, например. 'IRANDOM'. – Jerry101

+1

* чтобы подпрограмма вернула значение, присвойте значение переменной с тем же именем, что и подпрограмма * Вы действительно не знаете, что Fortran у вас есть! –