web-dev-qa-db-fra.com

Pourquoi est-ce que j'obtiens des résultats inégalement répartis lorsque j'utilise $ RANDOM?

J'ai lu sur les RNG sur Wikipedia et $RANDOM fonction sur TLDP mais cela n'explique pas vraiment ce résultat:

$ 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

Pourquoi les valeurs ci-dessus environ 2x sont-elles plus enclines à être 0, 1, 2 que 3, 4, 5 mais quand je change le module max, elles sont presque également réparties sur les 10 valeurs?

$ 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
15
cprn

Pour développer le sujet du biais modulo, votre formule est la suivante:

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

Et dans cette formule, $RANDOM est une valeur aléatoire comprise entre 0 et 32 ​​767.

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

Il aide à visualiser comment cela correspond aux valeurs possibles:

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

Donc, dans votre formule, la probabilité de 0, 1, 2 est le double de 4, 5. Et la probabilité de 3 est légèrement supérieure à 4, 5 également. D'où votre résultat avec 0, 1, 2 comme gagnants et 4, 5 comme perdants.

Lors du passage à 9*3600, il s'avère que:

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 ont la même probabilité, mais il y a toujours un léger biais pour 0, et donc 0 était toujours le gagnant de votre test avec 100'000 itérations.

Pour corriger le biais du modulo, vous devez d'abord simplifier la formule (si vous ne voulez que 0-5, alors le modulo est 6, pas 3600 ou un nombre encore plus fou, cela n'a aucun sens). À elle seule, cette simplification réduira considérablement votre biais (32766 correspond à 0, 32767 à 1, ce qui donne un léger biais à ces deux nombres).

Pour éliminer complètement les biais, vous devez relancer (par exemple) lorsque $RANDOM est inférieur à 32768 % 6 (éliminer les états qui ne correspondent pas parfaitement à la plage aléatoire disponible).

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

Résultat du test:

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

L'alternative serait d'utiliser une source aléatoire différente qui n'a pas de biais notable (ordres de grandeur supérieurs à seulement 32 768 valeurs possibles). Mais l'implémentation d'une logique de relance de toute façon ne fait pas de mal (même si cela n'arrive probablement jamais).

37
frostschutz

Il s'agit d'un biais modulo. Si RANDOM est bien construit, chaque valeur entre 0 et 32767 est produite avec une probabilité égale. Lorsque vous utilisez modulo, vous modifiez les probabilités: les probabilités de toutes les valeurs au-dessus du modulo sont ajoutées aux valeurs auxquelles elles correspondent.

Dans votre exemple, 6 × 3600 représente environ les deux tiers de la plage de valeurs. Les probabilités du tiers supérieur s'ajoutent donc à celles du tiers inférieur, ce qui signifie que des valeurs de 0 à 2 (environ) sont deux fois plus susceptibles d'être produites que des valeurs de 3 à 5. 9 × 3600 est près de 32767, donc le le biais modulo est beaucoup plus petit et n'affecte que les valeurs de 32400 à 32767.

Pour répondre à votre question principale, au moins dans Bash, la séquence aléatoire est entièrement prévisible si vous connaissez la graine. Voir intrand32 dans variables.c .

23
Stephen Kitt