ทำไมฉันถึงได้ผลลัพธ์ที่ไม่สม่ำเสมอเมื่อใช้ $ RANDOM


14

ฉันอ่านเกี่ยวกับ RNGs บนWikipediaและ$RANDOMฟังก์ชั่นบนTLDPแต่มันไม่ได้อธิบายผลลัพธ์นี้:

$ max=$((6*3600))
$ for f in {1..100000}; do echo $(($RANDOM%max/3600)); done | sort | uniq -c
  21787 0
  22114 1
  21933 2
  12157 3
  10938 4
  11071 5

ทำไมค่าด้านบนประมาณ 2 เท่ามีแนวโน้มที่จะเป็น 0, 1, 2, 3, 4, 5 แต่เมื่อฉันเปลี่ยนโมดูโลสูงสุดพวกมันเกือบจะกระจายไปทั่วทั้ง 10 ค่าเท่ากัน?

$ max=$((9*3600))
$ for f in {1..100000}; do echo $(($RANDOM%max/3600)); done | sort | uniq -c
  11940 0
  11199 1
  10898 2
  10945 3
  11239 4
  10928 5
  10875 6
  10759 7
  11217 8

9
คำตอบปกตินี้คือ reroll (ยกเลิกหมายเลขที่คุณได้รับและเลือกอีกอัน) ถ้าคุณอยู่ระหว่างค่าสูงสุดสำหรับ RANDOM และค่าสูงสุดที่เป็นไปได้ที่สามารถแบ่งเป็นโมดูโลของคุณอย่างเท่าเทียมกัน นั่นไม่ใช่เรื่องปกติสำหรับ RANDOM นั่นเป็นเรื่องปกติที่จะใช้ modulo-to-restrict-RNG- โดเมนในทุกภาษา / เครื่องมือ / ฯลฯ การนำ RNG ไปใช้ในประเภทนั้น
Charles Duffy

7
ดูบทความ 2013 ของฉันเกี่ยวกับแหล่งที่มาของความเอนเอียงนี้หากคุณต้องการกราฟที่ดีว่ามันแย่ขนาดไหน: ericlippert.com/2013/12/16/…
Eric Lippert

1
"การสร้างตัวเลขสุ่มมีความสำคัญเกินกว่าที่จะปล่อยให้เป็นไปได้" - Robert Coveyou แต่ FYI: โปรแกรมส่วนใหญ่ไม่สามารถสร้างตัวเลขสุ่มได้อย่างแท้จริง
jesse_b

@Eric Lippert ขอบคุณฉันจะอ่านมันด้วยความดีใจ!
cprn

1
โปรดทราบว่าแม้ว่าคุณจะเห็นปัญหาเนื่องจากโมดูโล$RANDOMตัวแปรนั้นไม่ได้ใช้ PRNG ที่ดีภายใน
ฟอเรสต์

คำตอบ:


36

หากต้องการขยายหัวข้อโมดูโลสูตรของคุณคือ:

max=$((6*3600))
$(($RANDOM%max/3600))

และในสูตร$RANDOMนี้เป็นค่าสุ่มในช่วง 0-32767

   RANDOM Each time this parameter is referenced, a random integer between
          0 and 32767 is generated.

ช่วยให้เห็นภาพว่าแผนที่นี้มีคุณค่าอย่างไร

0 = 0-3599
1 = 3600-7199
2 = 7200-10799
3 = 10800-14399
4 = 14400-17999
5 = 18000-21599
0 = 21600-25199
1 = 25200-28799
2 = 28800-32399
3 = 32400-32767

ดังนั้นในสูตรของคุณความน่าจะเป็นสำหรับ 0, 1, 2 คือสองเท่าของ 4, 5 และความน่าจะเป็นที่ 3 สูงกว่า 4, 5 เล็กน้อยเช่นกัน ดังนั้นผลลัพธ์ของคุณมี 0, 1, 2 ในฐานะผู้ชนะและ 4, 5 เป็นผู้แพ้

เมื่อเปลี่ยนเป็น9*3600มันจะกลายเป็น:

0 = 0-3599
1 = 3600-7199
2 = 7200-10799
3 = 10800-14399
4 = 14400-17999
5 = 18000-21599
6 = 21600-25199
7 = 25200-28799
8 = 28800-32399
0 = 32400-32767

1-8 มีความน่าจะเป็นเหมือนกัน แต่ยังคงมีอคติเล็กน้อยสำหรับ 0 และด้วยเหตุนี้ 0 จึงยังคงเป็นผู้ชนะในการทดสอบของคุณด้วยการวนซ้ำ 100,000

หากต้องการแก้ไขโมดูโล่อคติก่อนอื่นคุณควรทำให้สูตรง่ายขึ้น (ถ้าคุณต้องการ 0-5 เท่านั้นโมดูโล่คือ 6, ไม่ใช่ 3600 หรือเลขที่บ้าคลั่งยิ่งกว่านั้น) การทำให้เข้าใจง่ายเพียงอย่างเดียวนี้จะลดอคติของคุณลงได้มาก (32766 แมปเป็น 0, 32767 ต่อ 1 ให้อคติเล็กน้อยกับตัวเลขทั้งสอง)

ในการกำจัดอคติโดยรวมคุณต้อง re-roll (ตัวอย่าง) เมื่อ$RANDOMต่ำกว่า32768 % 6(กำจัดสถานะที่ไม่แมปอย่างสมบูรณ์ในช่วงสุ่มที่มีให้)

max=6
for f in {1..100000}
do
    r=$RANDOM
    while [ $r -lt $((32768 % $max)) ]; do r=$RANDOM; done
    echo $(($r%max))
done | sort | uniq -c | sort -n

ผลการทดสอบ:

  16425 5
  16515 1
  16720 0
  16769 2
  16776 4
  16795 3

ทางเลือกอื่นคือใช้แหล่งสุ่มอื่นที่ไม่มีอคติที่น่าสังเกต (คำสั่งที่มีขนาดใหญ่กว่าค่าที่เป็นไปได้เพียง 32768) แต่การใช้ตรรกะ re-roll ต่อไปจะไม่ทำร้าย (แม้ว่ามันจะไม่เกิดขึ้นก็ตาม)


คำตอบของคุณนั้นถูกต้องส่วนใหญ่ยกเว้น: "คุณจำเป็นต้องม้วนใหม่เมื่อ $ RANDOM ต่ำกว่า 32768% 6" จริง ๆ แล้วควรจะ "เท่ากับหรือมากกว่าชั้น ((RANDMAX + 1) / 6) * 6" (เช่น 32766 ) และแก้ไขรหัสเชลล์ที่เกี่ยวข้องด้านล่าง
Nayuki

@ นายูกิถ้าคุณสามารถชี้ให้เห็นข้อผิดพลาดเฉพาะ (ซึ่งนำไปใช้ภายในบริบทที่กำหนด) ฉันยินดีที่จะแก้ไข วิธีการแก้ปัญหาของฉันเป็นเพียงตัวอย่างมีหลายวิธีที่จะทำ คุณสามารถลบความลำเอียงออกจากช่วงเริ่มต้นหรือช่วงปลายหรือที่อื่นที่อยู่ตรงกลางมันไม่ได้ทำให้ความแตกต่าง คุณสามารถคำนวณได้ดีกว่า (และไม่ทำโมดูโลในการวนซ้ำทุกครั้ง) คุณสามารถจัดการกับกรณีพิเศษเช่นค่าโมดูโลสและค่า randmax เองได้เช่นกันจัดการกับ RANDMAX = INTMAX โดยที่ RANDMAX + 1 ไม่มีอยู่ แต่นั่นไม่ใช่จุดเน้นที่นี่
frostschutz

คำตอบของคุณแย่กว่าการโพสต์อย่างมาก ก่อนอื่นฉันชี้ให้เห็นอย่างชัดเจนว่าวลีของคุณไหนที่ผิดจริง ๆ โปรดทราบว่า "32768% 6" == 2 ดังนั้นคุณต้องการที่จะเรียกใหม่ทุกครั้งที่ $ RANDOM <2 หรือไม่ เกี่ยวกับอคติที่จุดเริ่มต้น / สิ้นสุด / มิดเดิ้ลโพสต์ทั้งหมดของคุณเกี่ยวกับการลบอคติเมื่อสิ้นสุดช่วงและการตอบสนองของฉันก็มีเป้าหมายเช่นกัน ประการที่สามคุณพูดถึงการจัดการ RANDMAX = INTMAX แต่ในคำตอบของคุณคุณพูดถึงค่า 32768 (= 32767 + 1) หลายครั้งซึ่งหมายความว่าคุณพอใจกับการคำนวณ RANDMAX + 1
Nayuki

1
@Nayuki รหัสของฉันลบ 0 และ 1 คุณลบ 32766 และ 32767 และฉันต้องการให้คุณทำอย่างละเอียด: มันแตกต่างกันอย่างไร ฉันเป็นมนุษย์คนเดียวฉันทำผิดพลาด แต่สิ่งที่คุณพูดจนถึงตอนนี้คือ "มันผิด" โดยไม่ต้องอธิบายหรือแสดงสาเหตุ ขอขอบคุณ.
frostschutz

1
ไม่เป็นไรคิดออก ขออภัยเกี่ยวกับการเตือนที่ผิดพลาด
Nayuki

23

นี่คืออคติแบบโมดูโล หากRANDOMสร้างขึ้นอย่างดีแต่ละค่าระหว่าง 0 ถึง 32767 จะถูกสร้างขึ้นด้วยความน่าจะเป็นที่เท่ากัน เมื่อคุณใช้ modulo คุณเปลี่ยนความน่าจะเป็น: ความน่าจะเป็นของค่าทั้งหมดข้างบน modulo นั้นจะถูกเพิ่มเข้าไปในค่าที่พวกมันจับคู่กัน

ในตัวอย่างของคุณ 6 × 3600 นั้นประมาณสองในสามของช่วงของค่า ความน่าจะเป็นของบุคคลที่สามอันดับต้นจะถูกเพิ่มเข้าไปในอันดับที่สามซึ่งหมายความว่าค่าจาก 0 ถึง 2 (โดยประมาณ) จะเพิ่มเป็นสองเท่าของโอกาสที่จะสร้างเป็นค่าจาก 3 ถึง 5 9 × 3600 เกือบ 32767 ดังนั้น modulo bias นั้นเล็กกว่ามากและมีผลกับค่าจาก 32400 ถึง 32767 เท่านั้น

เพื่อตอบคำถามหลักของคุณอย่างน้อยที่สุดใน Bash ลำดับการสุ่มสามารถคาดเดาได้อย่างสมบูรณ์หากคุณรู้จักเมล็ด ดูในintrand32variables.c

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.