Bash: Comment générer un nombre flottant aléatoire en utilisant $ RANDOM

Est-il possible de générer des nombres aléatoires réels avec une précision spécifique et dans une plage spécifique en utilisant Integer Random Générateur $ RANDOM? Par exemple, comment pouvons-nous générer un nombre réel avec une précision de 4 entre 0 et 1?

0.1234 0.0309 0.9001 0.0000 1.0000 

Une solution de contournement simple:

printf "%d04.%d04\n" $RANDOM $RANDOM 

Commentaires

  • Veuillez préciser ce que vous entendez par  » nombres aléatoires réels « . Avez-vous besoin dune source de nombres aléatoires générés par quelque chose comme la désintégration des particules, ou serez-vous satisfait dun générateur pseudo-aléatoire? Votre application de ces nombres a-t-elle une signification cryptographique ou scientifique, ou voulez-vous simplement quelque chose qui  » semble aléatoire « .
  • … ou voulez-vous dire  » float  » ou  » flottant numéro de point?
  • Merci pour votre commentaire. Jai besoin dun générateur de nombres pseudo aléatoires pour les nombres à virgule flottante basé sur $ RANDOM.
  • … pour implémenter des algorithmes méta heuristiques dans bash.

Réponse

awk -v n=10 -v seed="$RANDOM" "BEGIN { srand(seed); for (i=0; i<n; ++i) printf("%.4f\n", rand()) }" 

Cela affichera n nombres aléatoires (dix dans lexemple) dans la plage [0,1) avec quatre chiffres décimaux. Il utilise la fonction rand() dans awk (pas en standard awk mais implémentée par les awk implémentations) qui renvoie une valeur aléatoire dans cette plage. Le générateur de nombres aléatoires est amorcé par la variable shell « s $RANDOM.

Lorsquun programme awk na que BEGIN blocs (et aucun autre bloc de code), awk nessaiera pas de lire lentrée de son flux dentrée standard.

Sur tout système OpenBSD (ou système qui a le même jot utilitaire , à lorigine dans 4.2BSD), ce qui suit générera 10 nombres aléatoires comme spécifié:

jot -p 4 -r 10 0 1 

Commentaires

  • À proprement parler, depuis la sortie de rand() est un flottant entre [0,1), il nest probablement pas ‘ t exactement uniformément distribué lorsquil est arrondi à quatre chiffres décimaux. Ce serait le cas si le flottant était dune précision infinie, mais il nest pas ‘ t: il ‘ est susceptible dêtre généré à partir de bits aléatoires , il y a donc 2 ^ N valeurs différentes, et ils ne sont pas ‘ mappés uniformément à un ensemble de 1 000 valeurs. Mais tant que ces flottants pseudo-aléatoires ont suffisamment de bits et que vous ‘ ne faites rien de vraiment exact, vous avez probablement gagné ‘ t remarquez.

Réponse

Comme indiqué dans une autre réponse, il existe dautres utilitaires que vous pouvez utiliser pour générer nombres aléatoires. Dans cette réponse, je limite mes ressources à $RANDOM, et à quelques fonctions arithmétiques de base.

Pour les nombres à virgule flottante, essayez quelque chose comme

printf "%s\n" $(echo "scale=8; $RANDOM/32768" | bc ) 

Cela vous donnera la meilleure précision car $RANDOM ne génère que des nombres entre 0 et 32767. (dont 32767!) Mais, je  » Jai également enfreint ma règle sur lutilisation des fonctions arithmétiques de base en invoquant bc.

Mais avant de poursuivre, jaimerais examiner deux problèmes précision et range pour les nombres à virgule flottante. Après cela, je vais chercher à générer une plage dentiers (et si vous pouvez générer des entiers, vous pouvez les diviser plus tard pour obtenir une décimale si vous souhaitez utiliser les utilitaires que vous préférez pour cela.)

Précision

En adoptant lapproche de $RANDOM/32768, puisque $RANDOM génère des valeurs de 0 à 32767, le résultat de $RANDOM/32768 sera également un nombre fini de valeurs. En dautres termes, il sagit toujours dune variable aléatoire discrète (et avec un ordinateur, vous ne pourrez jamais vous éloigner de ce fait). Dans cet esprit, vous pouvez obtenir un certain degré de précision en utilisant printf.

Si vous voulez une couverture plus fine du intervalle, vous pouvez commencer à penser en base 32768. Donc, en théorie, $RANDOM + $RANDOM*32768 devrait vous donner une distribution uniforme entre 0 et 1 073 741 823. Mais, je doute que la ligne de commande gère très bien cette précision. Quelques points relatifs à ce cas particulier:

  • La somme de deux variables aléatoires indépendantes, uniformément distribuées, nest généralement pas uniforme. Dans ce cas, du moins en théorie (voir troisième point), ils le sont.
  • Ne pensez pas que vous pouvez simplifier $RANDOM + $RANDOM*32768 = $RANDOM * ( 1 + 32768 ).Les deux occurrences de $RANDOM sont en réalité deux événements différents.
  • Je ne sais pas assez comment $RANDOM est généré pour savoir si lappeler deux fois de cette manière générera réellement deux événements aléatoires indépendants.

Range

Considérons simplement $RANDOM/32768. Si vous voulez un nombre dans une plage, dites [a,b), alors

$RANDOM/32768*(b-a) + a 

vous amènera dans la plage souhaitée .

Génération de valeurs entières

Dabord, envisagez de générer des nombres aléatoires entre [0,b)b est inférieur à 32768. Considérez le produit q*b, où q est la partie entière de 32768/b. Ensuite, vous pouvez générer un nombre aléatoire entre 0 et 32767, mais rejeter ceux qui sont supérieurs ou égaux à q*b. Appelez le numéro ainsi généré G. Alors G tombera dans la plage de 0 à q*b, et sa distribution sera uniforme. Maintenant, appliquez larithmétique modulaire pour obtenir cette valeur réduite dans la plage souhaitée:

G % b 

Remarque, en générant aléatoirement un nombre comme suit

$RANDOM % b 

ne créera pas de distribution uniforme, à moins que b ne soit lun des diviseurs de 32768.

Ecrire un script bash pour ce

Calculer q*b comme décrit ci-dessus sonne comme une douleur. Mais ce nest vraiment pas « t. Vous pouvez lobtenir comme suit:

q*b = 32768 - ( 32768 % b ) 

Dans Bash, vous pouvez lobtenir avec

$((32768 - $((32768 % b)) )) 

Le code suivant générera un nombre aléatoire dans la plage 0..b (non compris de b) . b=$1

m=$((32768 - $((32768 % $1)) )) a=$RANDOM while (( $a > $m )); do a=$RANDOM done a=$(($a % $1)) printf "$a\n" 

Addendum

Techniquement, il ny a aucune raison de travailler avec

m=$((32768 - $((32768 % $1)) )) 

Ce qui suit va accomplir la même chose

a=$RANDOM while (( $a > $1 )); do a=$RANDOM done printf "$a\n" 

Cest beaucoup plus de travail, mais les ordinateurs sont rapides.

Générer un entier dans une plage plus large

Je vais vous laisser comprendre cela. Des précautions doivent être prises et, à un moment donné, vous devrez « prendre en considération les limitations de mémoire de lordinateur pour gérer les opérations arithmétiques.

Note finale

La réponse acceptée ne créera pas un nombre aléatoire uniformément sur 0 à 1.

Pour voir ceci, essayez ce qui suit

$ for i in {1..1000}; do echo .$RANDOM; done | awk "{ a += $1 } END { print a }" 

Pour une distribution vraiment uniforme sur [0,1), vous devriez voir une moyenne proche de 0.500.

Mais comme vous pouvez le voir en exécutant lextrait ci-dessus, vous obtiendrez à la place quelque chose comme 314.432 ou 322.619. Puisquil sagit de 1 000 nombres, la moyenne est de .322. La vraie moyenne pour cette séquence de nombres générés est .316362

Vous pouvez obtenir cette vraie moyenne en utilisant le script perl

 perl -e "{ $i=0; $s=0; while ( $i<=32767 ) { $j = sprintf "%.5f", ".$i"; $j =~ s/^0\.//; print "$j\n"; $s += $j; $i++ }; printf "%.5f\n", $s/32767; }" 

Jajoute des entiers ici pour vous aider à voir comment cette approche dutilisation de .$RANDOM ne fait pas ce que vous voudriez probablement quelle fasse. En dautres termes, réfléchissez aux nombres entiers générés et à ceux qui manquent complètement. Un assez grand nombre est ignoré; quelques-uns sont doublés.

Réponse

Sur les systèmes où le shell « s printf est capable de comprendre le %a format (bash ksh zsh, etc.) et peut donc effectuer un changement de base interne (hex -> dec) (uniforme dans la plage [0,1) de 0,00003 à 0.99997):

printf "%.5f\n" "$(printf "0x0.%04xp1" $RANDOM)" 

Vous pouvez même utiliser plus de chiffres en combinant plus dappels à $RANDOM (de 0.000000001 à 0.999999999)

printf "%.9f\n" "$(printf "0x0.%08xp2" $(( ($RANDOM<<15) + $RANDOM )))" 

Lalgorithme interne (au shell) « $ RANDOM » est basé sur un registre à décalage à rétroaction linéaire (LFSR). Ceux-ci ne sont pas cryptographiques Générateurs de nombres pseudo-aléatoires sécurisés (CSPRNG). Une meilleure option consiste à utiliser les octets du périphérique /dev/urandom. Cela nécessitera lappel à un vidage octal ou hexadécimal externe.

$ printf "%.19f\n" "0x0.$(od -N 8 -An -tx1 /dev/urandom | tr -d " ")" 0.7532810412812978029 $ printf "%.19f\n" "0x0.$(hexdump -n 8 -v -e ""%02x"" /dev/urandom)" 0.9453460825607180595 

Une solution très simple (mais non uniforme) pour obtenir un float est:

printf "0.%04d\n" $RANDOM 

Un moyen de le rendre uniforme dans la plage [0,1) (sans compter 1):

while a=$RANDOM; ((a>29999)); do :; done; printf "0.%04d\n" "$((a%10000))" 

Réponse

Utilisez $(( ( RANDOM % N ) + MIN ))

Remplacez N avec le nombre MAX et MIN avec le nombre minimum que vous souhaitez générer. (N car MAX est exclusif, mettez N+1 pour avoir les deux MAX, MIN inclus).

Ou vous pouvez utiliser $(shuf -i MIN-MAX -n 1) à la place.

de man shuf :

-i, --input-range=LO-HI treat each number LO through HI as an input line -n, --head-count=COUNT output at most COUNT lines 

Le -n 1 dans shuf signifie ici ne générer quun seul nombre aléatoire.

Cela générera des nombres aléatoires entre 0 ~ 9999 avec des zéros non significatifs en utilisant printf (dans le résultat, nombre 1

est exclusif).

printf "0.%04d\n" $(( RANDOM % 1000 )) 0.0215 

Commentaires

  • Ceci ne produira pas non plus un vrai nombre aléatoire dans la plage donnée, sauf dans le cas où N est un diviseur de 32767 (la limite supérieure de $ RANDOM).

Réponse

On bash

bc -l <<< "scale=4 ; $((RANDOM % 10000 ))/10000" 

1/10000 est votre aléatoire précision et 4 chiffres votre précision de sortie

Réponse

zsh a une fonction arithmétique rand48() (enveloppe de la fonction standard erand48()) dans sa zsh/mathfunc module:

zmodload zsh/mathfunc printf "%.4f\n" $((rand48())) 

Alors que $RANDOM est de 15 bits, pseudo-aléatoire et reproductible, bash 5.1+ a un entier 32 bits plus sûr $SRANDOM, basé sur des sources vraiment aléatoires lorsquelles sont disponibles. Il ne prend pas en charge larithmétique en virgule flottante, mais au moins vous pouvez lutiliser pour semer le générateur pseudo-aléatoire de awk « (qui sinon utilise par défaut le résultat très prévisible de time()):

echo "$SRANDOM" | awk " { srand($1) for (i = 0; i < 20; i++) printf "%.4f\n", rand() }" 

(gardez à lesprit que ce nest toujours que 32 bits dentropie et awk effectue une génération pseudo-aléatoire déterministe basée sur cette graine)

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *