Линейный конгруэнтный генератор - на выходе все 0?

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

      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

У кого-нибудь есть идеи здесь? Я только что вышел.


person gabe    schedule 18.10.2015    source источник
comment
Используйте основной тег fortran, чтобы сделать ваш пост более заметным. В Фортране доступно множество высококачественных генераторов случайных чисел. Fortran 90 даже имеет собственный внутренний (неопределенное качество). Кроме того, отладка в Фортране этого века проще, и область действия, безусловно, является важной концепцией в Фортране.   -  person Vladimir F    schedule 18.10.2015


Ответы (2)


Хорошо, теперь, чтобы дополнить ответ @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), которая позволяет нам писать более безопасные и надежные коды :)

person roygvib    schedule 18.10.2015
comment
Я во многом согласен с тем, что вы говорите, но предложение использовать функцию для PRNG меня слишком расстраивает. - person francescalus; 18.10.2015
comment
@francescalus Я занимаюсь рискованным кодированием с функцией + PRNG здесь ...? (Я был бы признателен, если бы вы объяснили проблему более подробно.) [Если это связано с переменной ised для хранения текущего состояния PRNG, да, имя очень вводит в заблуждение.] Кроме того, я также добавлю комментарий о DO также (да, это хорошая мысль :) - person roygvib; 18.10.2015
comment
Я бы не сказал, что рискованно (просто нужно знать разные аспекты), но дело в стиле. Изменение значения аргумента iseed — серьезный побочный эффект. По общему признанию, использование подобных функций - это то, что мне просто нужно принять... Я не против побочных эффектов в функциях (будучи прагматичным), но для PRNG они слишком вопиющие, на мой взгляд. - person francescalus; 18.10.2015
comment
Хм.. извините, я все еще не могу понять вашу мысль... В моем понимании текущий статус внутренних переменных в PRNG нужно где-то хранить. Если они хранятся в COMMON или в модуле, это означает, что они являются глобальными переменными, поэтому они становятся подверженными ошибкам при (поточных) параллельных вычислениях. Поэтому я подумал, что было бы лучше передать переменные состояния в качестве аргументов, чтобы функция стала чистой. Для большей удобочитаемости может быть лучше инкапсулировать все переменные состояния и связанные с ними подпрограммы в тип, но это кажется слишком далеким от Вопроса... - person roygvib; 18.10.2015
comment
Извините, приведенное выше использование pure странно, потому что функция изменяет значение аргумента. Кроме того, поскольку у меня мало опыта работы с PRNG (я обычно использую процедуру из Numerical Recipes ;-), поэтому я надеюсь, что будет добавлен более подробный ответ о потенциальных проблемах написания PRNG (при необходимости) :) - person roygvib; 18.10.2015
comment
[Внешний ресурс был бы лучше, чем мой комментарий здесь. Возможно, я смогу найти хорошо написанную вещь кем-то другим.] Я предпочитаю, скажем, call randu(rand, state), но помимо этого, да, будет некоторый статусный характер. Проблема с использованием функций с этим состоянием (и побочным эффектом) по сравнению с подпрограммами заключается в возможности использовать функции в выражении. Это приводит к беспокойству о запрещенных побочных эффектах и ​​так далее. - person francescalus; 18.10.2015
comment
Я думаю, что наконец-то понял вашу точку зрения... То есть, дело, вероятно, в том, что код становится чище и проще для понимания/поддержки, когда мы используем function только в том случае, когда все аргументы остаются нетронутыми (конечным из которых является функциональное программирование?), в противном случае используйте subroutine. Обычно я следую этому правилу, но приведенный выше случай PRNG выглядит как что-то среднее между функцией и подпрограммой, потому что функция более удобна для математических выражений. [Кстати, я обновил Приложение, чтобы использовать подпрограмму.] - person roygvib; 18.10.2015
comment
Я только что видел это, и большое спасибо. Это решило проблему, я не осознавал, что должен явно объявить IMAXINV реальным. Я все еще разбираюсь в совместном использовании переменных в Фортране, а также в том, как работают подпрограммы. Я в значительной степени согласен с вашими предложениями использовать более современный язык, но, к сожалению, это для класса научных вычислений и требует более старого языка. - person gabe; 18.10.2015
comment
@fireballs Даже в Fortran77, я думаю, ваш компилятор примет implicit none, что определенно облегчит нашу жизнь (потому что вероятность ошибки становится меньше). Что касается обмена переменными между подпрограммами, может быть полезна эта страница iprc.soest. hawaii.edu/users/furue/improve-fortran.html (где объясняется, как заменить COMMON на MODULE.) Другие учебные материалы см. на странице fortranwiki.org/fortran/show/Tutorials. - person roygvib; 20.10.2015

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

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

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

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

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

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

person Jerry101    schedule 18.10.2015
comment
К сожалению, у меня нет отладчика, но ваш комментарий до сих пор был очень полезным. Боюсь, я не вижу, где у меня есть параметр подпрограммы с именем ISEED, если только я не понял, как объявляются параметры подпрограммы. Я определенно думаю, что это случай случайного изменения неправильной переменной, но я не уверен, как это поймать. Я провел тест, распечатав ISEED в тот же файл, и похоже, что он правильно меняется. - person gabe; 18.10.2015
comment
Прохладный. IIRC, чтобы подпрограмма возвращала значение, присвойте значение переменной с тем же именем, что и подпрограмма. В этом случае вам нужно либо объявить RANDU как подпрограмму, возвращающую целое число, либо просто переименовать ее, например. IRANDOM. - person Jerry101; 18.10.2015
comment
чтобы подпрограмма возвращала значение, присвойте значение переменной с тем же именем, что и у подпрограммы Вы действительно не знаете Фортран, не так ли! - person High Performance Mark; 18.10.2015