เครื่องกำเนิดสมการเชิงเส้น - เอาต์พุตเป็น 0 ทั้งหมดหรือไม่

ฉันพยายามสร้างเครื่องกำเนิดตัวเลขสุ่มเทียม LCG ขั้นพื้นฐานใน Fortran 77 เพื่อพิมพ์ตัวเลขสุ่ม 1,000 ตัวลงในไฟล์ แต่ด้วยเหตุผลใดก็ตามเอาต์พุตจึงเป็นเพียง 1,000 0s โค้ดทั้งหมดค่อนข้างสั้น ดังนั้นฉันจึงหวีมันหลายครั้งและพยายามเปลี่ยนแปลงบางสิ่ง แต่ฉันไม่อาจเข้าใจได้ว่ามีอะไรผิดปกติไปตลอดชีวิต ฉันมีลางสังหรณ์ว่าอาจเป็นปัญหาเกี่ยวกับขอบเขต (หากแนวคิดดังกล่าวมีประโยชน์ใน Fortran ด้วยซ้ำ) แต่นั่นก็ไม่มีมูลความจริงจริงๆ

      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 Fortran 90 ยังมีคุณสมบัติในตัวของตัวเอง (คุณภาพที่ไม่ระบุ) นอกจากนี้ การดีบักยังง่ายกว่าในศตวรรษนี้ Fortran และขอบเขตเป็นแนวคิดที่สำคัญใน Fortran อย่างแน่นอน   -  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 ที่นี่หรือไม่... (ฉันจะขอบคุณมากหากคุณอธิบายปัญหาโดยละเอียดมากขึ้น) [หากเกี่ยวข้องกับตัวแปร iseed สำหรับจัดเก็บสถานะปัจจุบันของ 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 เป็นของจริง ฉันยังคงคุ้นเคยกับการแชร์ตัวแปรใน Fortran รวมถึงวิธีการทำงานของรูทีนย่อย ฉันเห็นด้วยกับข้อเสนอแนะของคุณเป็นส่วนใหญ่ในการใช้ภาษาที่ทันสมัยกว่านี้ แต่น่าเสียดายที่ภาษานี้มีไว้สำหรับชั้นเรียนคอมพิวเตอร์เชิงวิทยาศาสตร์และต้องใช้ภาษาที่เก่ากว่า - 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

เป็นเวลาหลายสิบปีแล้วที่ฉันเขียนโปรแกรมใน Fortran แต่ฉันจะพยายามช่วย

ก่อนอื่น 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
หากต้องการให้รูทีนย่อยคืนค่า ให้กำหนดค่าให้กับตัวแปรที่มีชื่อเดียวกับรูทีนย่อย คุณไม่รู้จัก Fortran จริงๆ ใช่ไหม ! - person High Performance Mark; 18.10.2015