Generator kongruensial linier - keluarannya semua 0?

Saya telah mencoba membuat generator nomor pseudorandom LCG yang cukup mendasar di Fortran 77 untuk mencetak 1000 nomor acak ke sebuah file, tetapi untuk alasan apa pun hasilnya hanya 1000 0s. Keseluruhan kodenya cukup pendek jadi saya telah menyisirnya beberapa kali dan mencoba mengubah beberapa hal, tetapi seumur hidup saya tidak dapat menemukan apa yang salah. Saya punya firasat bahwa ini bisa menjadi masalah ruang lingkup (jika konsep seperti itu berguna di Fortran), tapi itu benar-benar tidak berdasar.

      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

Adakah yang punya ide di sini? Aku baru saja keluar.


person gabe    schedule 18.10.2015    source sumber
comment
Gunakan tag fortran utama untuk membuat postingan Anda lebih terlihat. Ada banyak generator nomor acak berkualitas tinggi di Fortran yang tersedia. Fortran 90 bahkan memiliki intrinsiknya sendiri (kualitas tidak ditentukan). Selain itu, debugging lebih mudah di abad ini Fortran dan cakupan tentu saja merupakan konsep penting di Fortran.   -  person Vladimir F    schedule 18.10.2015


Jawaban (2)


Oke sekarang untuk menambah jawaban dengan @ Jerry101, saya telah menulis kode yang dimodifikasi. Di sini, masalah utamanya adalah IMAXINV tidak secara eksplisit dideklarasikan sebagai REAL, sehingga ditafsirkan sebagai INTEGER (akibatnya, IMAXINV = 1.0 / IMAX selalu menjadi 0 pada kode aslinya). Juga, saya telah menghapus ISEED dari blok COMMON (karena diteruskan sebagai argumen) dan memasukkan pernyataan COMMON lainnya ke dalam RANDU untuk berbagi variabel di antara rutinitas. Dengan modifikasi ini program tampaknya bekerja dengan benar.

      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

Seperti yang disarankan dalam Jawaban lain, kita juga bisa menggunakan FUNCTION untuk mengembalikan variabel secara langsung. Maka kita tidak perlu menggunakan COMMON, sehingga kodenya menjadi sedikit lebih bersih.

      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

Namun perhatikan bahwa ketika FUNCTION digunakan, tipe variabel kembalian harus dideklarasikan secara eksplisit dalam rutinitas pemanggilan jika nama fungsi tidak sesuai dengan aturan implisit. (Pada kode di atas, RANDU tidak dideklarasikan secara eksplisit karena diartikan sebagai REAL). Jadi, ada banyak peringatan dalam aturan pengetikan implisit di Fortran77...


Catatan tambahan:

Untuk menghindari kesalahan ini, saya sarankan menggunakan Fortran >=90 (daripada Fortran77) karena Fortran menyediakan banyak fitur untuk mencegah kesalahan tersebut. Misalnya, kode yang dimodifikasi secara minimal mungkin terlihat seperti ini:

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

Di Sini,

  • implicit none digunakan untuk menegakkan deklarasi semua variabel secara eksplisit. Hal ini berguna untuk membantu menghindari kesalahan pengetikan variabel (seperti IMAXINV pada Pertanyaan!).
  • Subrutin RANDU terkandung dalam module sehingga kompiler menyediakan antarmuka eksplisit dan banyak pemeriksaan berguna (singkatnya, module adalah sesuatu seperti namespace di C++). module juga dapat digunakan untuk mendefinisikan variabel global dengan cara yang jauh lebih aman daripada COMMON.
  • Saya menggunakan konstruksi do ... enddo untuk mengulang j daripada menambahnya secara manual dan menggunakan goto. Yang pertama sebenarnya lebih mudah digunakan, dan juga goto cenderung membuat kode sering kali kurang mudah dibaca...
  • Saya menamai file program tersebut sebagai "test.f90" (perhatikan akhiran .f90), yang memungkinkan format bebas. Selain itu, boleh saja menggunakan huruf kecil untuk variabel.
  • [Juga, karena iseed menyimpan informasi tentang status terkini dari pembuat nomor acak (semu), mungkin lebih baik menggunakan beberapa nama variabel yang berbeda (seperti isstate dll?) untuk mengingatkan bahwa nilainya perlu disimpan selama panggilan.]

Jadi jika Anda tertarik, harap pertimbangkan untuk menggunakan versi Fortran yang lebih modern (daripada Fortran77) yang memungkinkan kami menulis kode yang lebih aman dan kuat :)

person roygvib    schedule 18.10.2015
comment
Saya sangat setuju dengan apa yang Anda katakan, tetapi saran untuk menggunakan fungsi PRNG membuat saya terlalu kesal. - person francescalus; 18.10.2015
comment
@francescalus Apakah saya melakukan pengkodean berisiko dengan fungsi + PRNG di sini...? (Saya akan sangat menghargai jika Anda menjelaskan masalahnya lebih detail.) [Jika terkait dengan variabel iseed untuk menyimpan status PRNG saat ini, ya, namanya sangat menyesatkan.] Selain itu, saya juga akan menambahkan komentar tentang DO (ya, itu poin yang bagus :) - person roygvib; 18.10.2015
comment
Saya tidak akan mengatakan berisiko (kita hanya harus menyadari berbagai aspek), tetapi ini adalah masalah gaya. Mengubah nilai argumen iseed adalah efek samping yang sangat besar. Memang benar, menggunakan fungsi seperti ini adalah sesuatu yang harus saya terima... Saya tidak menentang efek samping dalam fungsi (bersikap pragmatis), tetapi untuk PRNG efek tersebut terlalu buruk untuk saya sukai. - person francescalus; 18.10.2015
comment
Hmm.. maaf, saya masih belum bisa menangkap maksud Anda... Menurut pemahaman saya, status variabel internal di PRNG saat ini perlu disimpan di suatu tempat. Jika disimpan di COMMON atau di modul berarti merupakan variabel global, sehingga rawan kesalahan terhadap perhitungan paralel (thread). Jadi saya pikir akan lebih baik untuk meneruskan variabel status sebagai argumen, sehingga suatu fungsi menjadi murni. Agar lebih mudah dibaca, mungkin lebih baik untuk merangkum semua variabel status dan rutinitas terkait dalam suatu tipe, tetapi tampaknya agak terlalu jauh dari Pertanyaan... - person roygvib; 18.10.2015
comment
Maaf, penggunaan pure di atas aneh karena fungsinya mengubah nilai argumen. Selain itu, karena saya memiliki sedikit pengalaman tentang PRNG (biasanya saya menggunakan rutinitas dari Numerical Recipes ;-), jadi saya berharap akan ditambahkan jawaban yang lebih detail tentang potensi masalah penulisan PRNG (bila perlu) :) - person roygvib; 18.10.2015
comment
[Sumber daya eksternal akan lebih baik, daripada komentar saya yang menulis di sini. Saya mungkin dapat menemukan hal yang ditulis dengan baik oleh orang lain.] Pilihan saya adalah call randu(rand, state), katakanlah, tapi lebih dari itu, ya akan ada sifat stateful. Kekhawatiran dalam menggunakan fungsi dengan status ini (dan efek sampingnya) pada subrutin, adalah kemampuan untuk menggunakan fungsi dalam ekspresi. Hal ini menyebabkan kekhawatiran tentang efek samping yang dilarang dan sebagainya. - person francescalus; 18.10.2015
comment
Saya rasa saya akhirnya mengerti maksud Anda... Yaitu, intinya mungkin kode menjadi lebih bersih dan lebih mudah dipahami/dipelihara ketika kita menggunakan function hanya dalam kasus di mana semua argumen tetap utuh (yang terakhir adalah pemrograman fungsional?), saat menggunakan subroutine sebaliknya. Saya biasanya mengikuti pedoman ini, tetapi kasus PRNG di atas terlihat seperti sesuatu di antara fungsi dan subrutin karena suatu fungsi lebih cocok untuk ekspresi matematika. [Ngomong-ngomong, saya telah memperbarui Lampiran untuk menggunakan subrutin.] - person roygvib; 18.10.2015
comment
Saya baru saja melihat ini, dan terima kasih banyak. Ini menyelesaikan masalah, saya tidak menyadari bahwa saya harus secara eksplisit menyatakan IMAXINV sebagai nyata. Saya masih terbiasa berbagi variabel di Fortran, serta cara kerja subrutin. Saya sangat setuju dengan saran Anda untuk menggunakan bahasa yang lebih modern, namun sayangnya ini untuk kelas komputasi ilmiah dan memerlukan bahasa yang lebih tua. - person gabe; 18.10.2015
comment
@fireballs Bahkan dalam Fortran77, saya kira kompiler Anda akan menerima implicit none, yang pasti akan membuat hidup kita lebih mudah (karena kemungkinan bug menjadi lebih kecil). Sedangkan untuk berbagi variabel antar rutinitas, halaman ini mungkin berguna iprc.soest. hawaii.edu/users/furue/improve-fortran.html (yang menjelaskan cara mengganti COMMON dengan MODULE.) Untuk tutorial lainnya, silakan lihat fortranwiki.org/fortran/show/Tutorials misalnya. - person roygvib; 20.10.2015

Sudah puluhan tahun sejak saya memprogram di Fortran, tapi saya akan mencoba membantu.

Pertama dan terpenting, IMAXINV adalah variabel integer karena namanya dimulai dengan I dan Anda tidak mendeklarasikannya sebagai float. Jadi hasil pembagian akan dipotong menjadi nilai integer 0, yang menjelaskan keluaran nol Anda. Bagaimanapun juga, penghasil angka acak Anda harus tetap menggunakan operasi bilangan bulat daripada memperkenalkan operasi titik banjir, baik untuk kebenaran maupun kecepatan.

Fortran 77 mendukung fungsi yang mengembalikan nilai, ya? Itu akan lebih bersih dan lebih modular daripada menyimpan hasil subrutin dalam variabel global.

IIRC, pernyataan COMMON adalah untuk berbagi nilai global antar modul, yang merupakan hal yang berisiko bagi keadaan pribadi pembuat nomor acak.

Anda memiliki variabel global COMMON bernama ISEED dan parameter formal subrutin dengan nama yang sama (kecuali saya salah mengingat cara kerja deklarasi subrutin Fortran). Itu akan membingungkan dan harus diperbaiki. Mengupdate subrutin parameternya ISEED daripada variabel global akan menyebabkan subrutin mengembalikan nilai yang sama setiap kali loop ini memanggilnya. Artinya, kecuali parameter formalnya adalah alias panggilan demi referensi ke argumen sebenarnya -- dengan nama yang sama dalam kode ini. Anda tahu, ini membingungkan.

Apakah Anda memiliki debugger? Jika demikian, sekali menelusuri program dan mengamati variabelnya akan segera mengungkapkan di mana program tersebut menyimpang dari harapan Anda.

person Jerry101    schedule 18.10.2015
comment
Sayangnya saya tidak memiliki debugger, namun komentar Anda sejauh ini sangat membantu. Saya khawatir saya tidak melihat di mana saya memiliki parameter subrutin bernama ISEED, kecuali saya salah memahami bagaimana parameter subrutin dideklarasikan. Saya yakin ini adalah kasus perubahan variabel yang salah secara tidak sengaja, tetapi saya tidak yakin bagaimana cara menangkapnya. Saya melakukan tes di mana saya mencetak ISEED ke file yang sama, dan sepertinya itu berubah dengan benar.. - person gabe; 18.10.2015
comment
Dingin. IIRC, untuk membuat subrutin mengembalikan nilai, tetapkan nilai tersebut ke variabel dengan nama yang sama dengan subrutin tersebut. Dalam hal ini, Anda harus mendeklarasikan RANDU sebagai subrutin yang mengembalikan bilangan bulat atau cukup mengganti namanya, mis. IRANDOM. - person Jerry101; 18.10.2015
comment
untuk membuat subrutin mengembalikan nilai, tetapkan nilai ke variabel dengan nama yang sama dengan subrutin Anda benar-benar tidak tahu Fortran kan! - person High Performance Mark; 18.10.2015