Чому я отримую результати з нерівномірним розподілом при використанні $ RANDOM?


14

Я читав про RNG у Вікіпедії та $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
Звичайна відповідь на це - прокрутити (відкинути отримане число та вибрати інше), якщо ви знаходитесь між максимальним значенням RANDOM та найвищим можливим значенням, яке може рівномірно ділитися на ваш модуль. Це не звичайно для RANDOM, це звичайне використання модуля-обмеження-RNG-домену для всіх мов / інструментів / тощо. реалізація РНГ цього типу.
Чарльз Даффі

7
Дивіться мою статтю 2013 року про джерело цього упередження, якщо ви хочете отримати хороші графіки того, як це погано: ericlippert.com/2013/12/16/…
Ерік Ліпперт

1
"Генерація випадкових чисел є надто важливою, щоб залишити випадковість". - Роберт Ковейо. 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, що надасть крихітні зміщення цим двом числам).

Щоб взагалі позбутися від упередженості, вам потрібно перезапустити (наприклад), коли $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 можливих значень). Але реалізація логіки перезавантаження все одно не зашкодить (навіть якщо це, швидше за все, ніколи не здійсниться).


Ваша відповідь в основному правильна, за винятком випадків: "вам потрібно перекинути, коли $ 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.
Наюкі

1
@Nayuki мій код видаляє 0 і 1, ваш видаляє 32766 і 32767, і я хотів би, щоб ви докладно пояснили: у чому різниця? Я лише людина, я допускаю помилки, але все, що ви сказали до цього часу, - це "неправильно", не пояснюючи і не показуючи чому. Дякую.
frostschutz

1
Неважливо, зрозумів це. Вибачте за помилкову тривогу.
Наюкі

23

Це модульне зміщення. Якщо RANDOMдобре побудовано, кожне значення між 0 і 32767 виробляється з однаковою ймовірністю. Використовуючи модуль, ви змінюєте ймовірності: ймовірності всіх значень вище модуля додаються до значень, на які вони відображаються.

У вашому прикладі 6 × 3600 - це приблизно дві третини діапазону значень. Тому ймовірності верхньої третини додаються до рівнях нижньої третини, це означає, що значення від 0 до 2 (приблизно) вдвічі частіше отримують, ніж значення від 3 до 5. 9 × 3600 - майже 32767, тому модульне зміщення набагато менше і впливає лише на значення від 32400 до 32767.

Щоб відповісти на ваше головне запитання, принаймні у Bash випадкова послідовність цілком передбачувана, якщо ви знаєте насіння. Дивіться intrand32в variables.c.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.