$ RANDOM kullanırken neden eşit olmayan şekilde yayılmış sonuçlar alıyorum?


14

Ben RNG'ler hakkında okumak Wikipedia ve $RANDOMüzerinde fonksiyonu tldp ama gerçekten bu sonucu açıklamaz:

$ 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

Neden yaklaşık 2x'in üzerindeki değerler 0, 1, 2'den 3, 4, 5'e daha meyilli?

$ 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
Bunun olağan cevabı, RANDOM için maksimum değer ile modulo'nuza eşit olarak bölünebilecek mümkün olan en yüksek değer arasındaysanız yeniden kaydetmektir (aldığınız numarayı atın ve başka bir tane seçin). Bu her zamanki-RANDOM değil, tüm diller / araçlar / vb. İçin her zamanki gibi modulo-kısıtlamak-RNG-alanıdır . bu tip RNG'leri uygulamak.
Charles Duffy

7
Ne kadar kötü olduğuna dair güzel grafikler istiyorsanız, bu yanlılığın kaynağı hakkındaki 2013 makaleme bakın
Eric Lippert

1
"Rastgele sayıların üretilmesi şansa bırakılamayacak kadar önemlidir." - Robert Coveyou. Yine de FYI: Çoğu program gerçekten rasgele sayılar
üretemez

@Eric Lippert teşekkür ederim, memnuniyetle okuyacağım!
cprn

1
Nedeniyle modülo önyargıya sorunları görüyoruz rağmen, o Not $RANDOMdeğişken gelmez değil içten iyi PRNG kullanın.
orman

Yanıtlar:


36

Modulo önyargı konusunu genişletmek için formülünüz:

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

Ve bu formülde, $RANDOM0-32767 aralığında rastgele bir değerdir.

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

Bunun olası değerlerle nasıl eşleştiğini görselleştirmeye yardımcı olur:

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

Formülünüzde, 0, 1, 2 olasılığı 4, 5'in iki katıdır. 3 olasılığı da 4, 5'ten biraz daha yüksektir. Sonuç olarak kazananlar olarak 0, 1, 2 ve kaybedenler olarak 4, 5 elde edersiniz.

Olarak değiştirirken 9*3600, şu şekilde ortaya çıkar:

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 aynı olasılığa sahiptir, ancak 0 için hala hafif bir önyargı vardır ve bu nedenle 0, 100.000 yineleme ile testinizde hala kazanan oldu.

Modulo yanlılığını düzeltmek için, önce formülü basitleştirmelisiniz (sadece 0-5 istiyorsanız, modulo 3600, hatta daha çılgın bir sayıdır, bunun anlamı yok). Bu sadeleştirme tek başına önyargınızı çok azaltacaktır (32766, 0, 32767 ila 1 arasındadır) bu iki sayıya küçük bir önyargı verir).

Önyargıdan tamamen kurtulmak için, (örneğin) $RANDOM, daha düşük olduğunda yeniden döndürmeniz gerekir ( 32768 % 6mükemmel rasgele aralığa mükemmel şekilde eşlenmeyen durumları ortadan kaldırın).

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

Test sonucu:

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

Alternatif, fark edilebilir önyargıya sahip olmayan farklı bir rastgele kaynak kullanmak olabilir (sadece 32768 olası değerlerden daha büyük büyüklük sıraları). Ancak yine de bir roll-roll mantığı uygulamak acıtmaz (muhtemelen hiç geçmeyecek olsa bile).


Cevabınız büyük ölçüde doğrudur, ancak: "$ RANDOM% 32768'den düşük olduğunda yeniden yuvarlamanız gerekir" ) ekleyin ve ilişkili kabuk kodunu bunun altında düzeltin.
Nayuki

@Nayuki (verilen bağlamda geçerli olan) belirli bir hatayı gösterebilirseniz düzeltmekten memnuniyet duyarız. Benim çözümüm sadece bir örnek, bunu yapmanın farklı yolları var. Yanlılığı başlangıç ​​aralığından, bitiş aralığından veya ortada bir yerden kaldırabilirsiniz, bu fark etmez. Daha iyi hesaplayabilirsiniz (ve her yinelemede bir modulo yapamazsınız). Rasgele modüller ve randmax değerleri gibi özel durumları ele alabilir, ayrıca RANDMAX + 1'in bulunmadığı yerlerde RANDMAX = INTMAX'ı da işleyebilirsiniz, ancak buradaki odak noktası değildi.
frostschutz

Cevabınız gönderinizden çok daha kötü. Her şeyden önce, özellikle sizin hangi ifadenizin aslında yanlış olduğunu belirttim. "32768% 6" == 2 olduğuna göre, her seferinde $ RANDOM <2? Aralığın başlangıcı / sonu / ortasında önyargı ile ilgili olarak, tüm yayınınız aralığın sonunda önyargıyı kaldırmakla ilgilidir ve yanıtım tam olarak buna hitap eder. Üçüncüsü, RANDMAX = INTMAX kullanımı hakkında konuşuyorsunuz, ancak cevabınızda 32768 (= 32767 + 1) değerinden defalarca bahsettiniz, bu da RANDMAX + 1'i hesaplama konusunda rahat olduğunuz anlamına geliyor.
Nayuki

1
@Nayuki kodum 0 ve 1'i kaldırır, sizinki 32766 ve 32767'yi kaldırır ve ayrıntılı olarak açıklamanızı isterim: ne fark eder? Ben sadece insanım, hatalar yapıyorum, ama şimdiye kadar söylediğin tek şey nedenini açıklamadan veya göstermeden "yanlış". Teşekkür ederim.
frostschutz

1
Boş ver, anladım. Yanlış alarm için özür dilerim.
Nayuki

23

Bu modulo eğilimi. Eğer RANDOMiyi inşa edilmiş, 0 ile 32767 arasında her bir değer, eşit olasılıkla üretilir. Modulo kullandığınızda olasılıkları değiştirirsiniz: modulo üzerindeki tüm değerlerin olasılıkları eşlendikleri değerlere eklenir.

Örneğin, 6 × 3600, değer aralığının yaklaşık üçte ikisi kadardır. Bu nedenle, üst üçte birinin olasılıkları alt üçüncü üçüncüye eklenir, yani 0 ila 2 (yaklaşık) değerlerin 3 ila 5 arasındaki değerler olarak üretilme olasılığının iki katı olduğu anlamına gelir. 9 × 3600 yaklaşık 32767'dir, modulo sapması çok daha küçüktür ve sadece 32400 ila 32767 arasındaki değerleri etkiler.

Ana sorunuza cevap vermek için, en azından Bash'te, tohum biliyorsanız, rastgele sıra tamamen tahmin edilebilir. Bkz intrand32içinde variables.c.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.