Почему я получаю неравномерно распределенные результаты при использовании $ RANDOM?

14

Я читал о ГСЧ в Википедии и о $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, но когда я изменяю max по модулю, они почти одинаково распределены по всем 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
CpRn
источник
9
Обычный ответ на этот вопрос заключается в том, чтобы перебросить (отбросить полученный номер и выбрать другой), если вы находитесь между максимальным значением для СЛУЧАЙНОГО и максимально возможным значением, которое можно равномерно разделить на ваш модуль. Это не обычный случайный случай, это обычный способ использования домена по модулю для ограничения RNG для всех языков / инструментов / и т. Д. реализации ГСЧ этого типа.
Чарльз Даффи
7
Посмотрите мою статью 2013 года об источнике этой предвзятости, если вы хотите получить хорошие графики того, насколько плохо это получается: ericlippert.com/2013/12/16/…
Эрик Липперт
1
«Генерация случайных чисел слишком важна, чтобы оставлять ее на волю случая». Роберт Ковей. К вашему сведению: большинство программ не могут генерировать действительно случайные числа
jesse_b
@ Эрик Липперт спасибо, я с удовольствием прочитаю!
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 возможных значений). Но реализация логики перекатывания в любом случае не повредит (даже если это, вероятно, никогда не осуществится).

frostschutz
источник
Ваш ответ в значительной степени правильный, за исключением того, что «вам нужно перебрасывать, когда $ RANDOM меньше 32768% 6» должно быть «равно или больше, чем пол ((RANDMAX + 1) / 6) * 6» (т.е. 32766 ) и исправьте соответствующий код оболочки ниже.
Наюки,
@ Наюки, если вы можете указать на конкретную ошибку (которая применяется в данном контексте), я буду рад исправить ее. Мое решение - просто пример, есть разные способы сделать это. Вы можете удалить смещение из начального диапазона, или конечного диапазона, или где-то посередине, это не имеет значения. Вы можете вычислить это лучше (и не делать по модулю в каждой итерации). Вы можете обрабатывать особые случаи, такие как произвольные значения по модулю и randmax, а также обрабатывать RANDMAX = INTMAX, где RANDMAX + 1 не существует, но это не было фокусом здесь.
frostschutz
Ваш ответ значительно хуже, чем ваш пост. Прежде всего, я указал конкретно, какая ваша фраза на самом деле неверна. Обратите внимание, что «32768% 6» == 2, поэтому вы хотите перебрасывать каждый раз, когда $ RANDOM <2? Что касается смещения в начале / конце / середине диапазона, то весь ваш пост посвящен устранению смещения в конце диапазона, и мой ответ учитывает именно это. В-третьих, вы говорите об обработке RANDMAX = INTMAX, но в своем ответе вы неоднократно упоминали значение 32768 (= 32767 + 1), что означает, что вам удобно работать с RANDMAX + 1.
Наюки,
1
@ Наюки мой код удаляет 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.

Стивен Китт
источник