Bash: Cum se generează un număr float aleatoriu utilizând $ RANDOM

Este posibil să se genereze numere aleatoare reale cu o precizie specifică și într-un anumit interval folosind Integer Random Generator $ RANDOM? De exemplu, cum putem genera un număr real cu precizie 4 între 0 și 1?

0.1234 0.0309 0.9001 0.0000 1.0000 

O soluție simplă:

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

Comentarii

  • Vă rugăm să specificați ce înțelegeți prin ” numere aleatoare reale „. Aveți nevoie de o sursă de numere aleatorii generate de ceva precum degradarea particulelor sau veți fi mulțumit de un generator pseudo-aleator? Aplicația dvs. pentru aceste numere are semnificație criptografică sau științifică sau doriți doar ceva care ” arată aleatoriu „.
  • … sau de fapt vrei să spui ” float ” sau ” plutitor numărul punctului?
  • Vă mulțumim pentru comentariu. Am nevoie de un generator de numere pseudo-aleatorii pentru numerele cu virgulă mobilă bazate pe $ RANDOM.
  • … pentru implementarea algoritmilor meta euristici în bash.

Răspuns

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

Aceasta va genera n numere aleatorii (zece în exemplu) în intervalul [0,1) cu patru cifre zecimale. Folosește funcția rand() în awk (nu în standardul awk, dar implementat de cele mai comune awk implementări) care returnează o valoare aleatorie în intervalul respectiv. Generatorul de numere aleatorii este însămânțat de variabila shell „s $RANDOM.

Când un program awk are numai BEGIN blocuri (și niciun alt bloc de cod), awk nu va încerca să citească intrarea din fluxul său standard de intrare.

Pe orice sistem OpenBSD (sau sistem care are același utilitar jot , inițial în 4.2BSD), următoarele va genera 10 numere aleatorii, după cum se specifică:

jot -p 4 -r 10 0 1 

Comentarii

  • Într-adevăr strict, din moment ce rezultatul din rand() este un float în cadrul [0,1), probabil că nu este ‘ distribuit exact în mod egal când este rotunjit la patru cifre zecimale. Ar fi, dacă float-ul ar fi de o precizie infinită, dar nu este ‘ t: ‘ este probabil să fie generat din biți aleatori , deci există 2 ^ N valori diferite și ele nu ‘ t mapează uniform la un set de 1000 de valori. Dar atâta timp cât acele flotări pseudo-aleatorii au destui biți și ‘ nu faceți nimic cu adevărat exact, probabil că ați câștigat ‘ t observație.

Răspuns

După cum sa subliniat într-un alt răspuns, există și alte utilități pe care le puteți utiliza pentru a genera numere aleatorii. În acest răspuns, îmi limitez resursele la $RANDOM și câteva funcții aritmetice de bază.

Pentru numerele cu virgulă mobilă, încercați ceva de genul

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

Aceasta vă va oferi cea mai bună precizie, deoarece $RANDOM generează numai numere între 0 și 32767. (inclusiv 32767!) Dar, eu ” De asemenea, mi-am încălcat regula cu privire la utilizarea funcțiilor aritmetice de bază invocând bc.

Dar înainte de a continua, aș vrea să analizez două aspecte precizie și interval pentru numerele cu virgulă mobilă. După aceea, mă voi uita la generarea unei game de numere întregi (și dacă puteți genera numere întregi, le puteți împărți ulterior pentru a obține o zecimală dacă doriți să folosiți orice utilitare preferați pentru a realiza acest lucru.)

Precizie

Abordarea $RANDOM/32768, deoarece $RANDOM generează valori de la 0 la 32767, rezultatul $RANDOM/32768 va fi, de asemenea, finit de multe valori. Cu alte cuvinte, este încă o variabilă discretă aleatorie (și cu un computer nu veți putea niciodată să scăpați de acest fapt). Având în vedere acest lucru, puteți realiza un anumit grad de precizie utilizând printf.

Dacă doriți o acoperire mai fină a interval, ați putea începe să vă gândiți la baza 32768. Deci, în teorie $RANDOM + $RANDOM*32768 ar trebui să vă ofere o distribuție uniformă între 0 și 1.073.741.823. Dar, mă îndoiesc că linia de comandă va rezolva foarte bine acea precizie. Câteva puncte referitoare la acest caz particular:

  • Suma a două variabile aleatoare independente, distribuite uniform, nu în general uniforme. În acest caz, cel puțin teoretic vorbind (a se vedea al treilea punct), acestea sunt.
  • Nu credeți că puteți simplifica $RANDOM + $RANDOM*32768 = $RANDOM * ( 1 + 32768 ).Cele două apariții ale $RANDOM sunt într-adevăr două evenimente diferite.
  • Nu știu suficient despre cât este $RANDOM generat pentru a ști dacă apelarea acestuia de două ori așa va genera cu adevărat două evenimente aleatorii independente.

Interval

Să considerăm doar $RANDOM/32768. Dacă doriți un număr dintr-un interval, spuneți [a,b), apoi

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

vă va ateriza în intervalul dorit .

Generarea valorilor întregi

În primul rând, luați în considerare generarea de numere aleatoare între [0,b) unde b este mai mic decât 32768. Luați în considerare produsul q*b, unde q este partea întreagă a 32768/b. Apoi, ceea ce puteți face este să generați un număr aleatoriu între 0 și 32767, dar să le aruncați pe cele care sunt mai mari sau egale cu q*b. Apelați numărul astfel generat G. Apoi G va intra în intervalul de la 0 la q*b, iar distribuția acestuia va fi uniformă. Acum, aplicați aritmetica modulară pentru a obține această valoare redusă în intervalul dorit:

G % b 

Notă, generând aleatoriu un număr după cum urmează

$RANDOM % b 

nu va crea o distribuție uniformă, cu excepția cazului în care b se întâmplă doar să fie unul dintre divizorii 32768.

Scrierea unui script bash pentru acest

Calculare q*b așa cum este descris mai sus sună ca o durere. Dar chiar nu este „t. Puteți obține acest lucru după cum urmează:

q*b = 32768 - ( 32768 % b ) 

În Bash, puteți obține acest lucru cu

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

Următorul cod va genera un număr aleatoriu în intervalul 0..b (nu include b) . b=$1

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

Addendum

Din punct de vedere tehnic, există puține motive pentru a lucra cu

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

Următoarele vor realiza același lucru

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

Este „mult mai mult de lucru, dar computerele sunt rapide.

Generarea unui întreg într-un interval mai mare

Vă voi permite să vă dați seama de acest lucru. Trebuie să aveți grijă și, la un moment dat, va trebui să luați în considerare limitările de memorie ale computerului în gestionarea operațiilor aritmetice.

Notă finală

Răspunsul acceptat nu va crea un număr aleatoriu uniform de la 0 la 1.

Pentru a vedea acest lucru, încercați următoarele

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

Pentru o distribuție cu adevărat uniformă pe [0,1) ar trebui să vedeți o medie apropiată de 0.500.

Dar după cum puteți vedea executând fragmentul de mai sus, veți obține în schimb ceva de genul 314.432 sau 322.619. Întrucât are 1000 de numere, media acestuia este .322. Media adevărată pentru această secvență de numere generate este .316362

Puteți obține această medie adevărată folosind scriptul 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; }" 

Voi adăuga numere întregi aici pentru a vă ajuta să vedeți cum această abordare a utilizării .$RANDOM nu face ceea ce doriți cel mai probabil să facă. Cu alte cuvinte, gândiți-vă care numere întregi sunt generate și care dintre ele sunt omise cu totul. Un număr destul de mare este omis; destul de multe sunt dublate.

Răspuns

Pe sistemele în care print-ul shell-ului este capabil să înțeleagă %a format (bash ksh zsh etc.) și, prin urmare, este capabil să efectueze o schimbare de bază internă (hex -> dec) (uniformă în intervalul [0,1) de la 0.00003 până la 0.99997):

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

Puteți folosi chiar și mai multe cifre combinând mai multe apeluri către $RANDOM (de la 0.000000001 la 0.999999999)

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

Algoritmul intern (către shell) „$ RANDOM” se bazează într-un registru de schimbare a feedbackului liniar (LFSR). Acestea nu sunt criptografice Secure Pseudo Random Number Generators (CSPRNGs). O opțiune mai bună este să utilizați octeți de pe dispozitivul /dev/urandom. Acest lucru va necesita apelul către o descărcare externă octală sau hexagonală.

$ 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 

O soluție foarte simplă (dar neuniformă) pentru a obține un plutitor este:

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

Un mod de a-l uniformiza în intervalul [0,1) (fără a include 1):

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

Răspuns

Utilizați $(( ( RANDOM % N ) + MIN ))

Înlocuiți N cu numărul MAX și MIN cu numărul minim pe care doriți să îl generați. (N deoarece MAX este exclusiv, puneți N+1 pentru a avea ambele MAX, MIN inclusiv).

Sau puteți utiliza în schimb $(shuf -i MIN-MAX -n 1).

din 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 

-n 1 în shuf aici înseamnă că se generează doar un număr aleatoriu.

Acest lucru va genera numere aleatorii între 0 ~ 9999 cu zerouri din partea de sus folosind printf (în rezultat, numărul 1

este exclusiv).

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

Comentarii

  • Aceasta de asemenea, nu va produce un număr aleatoriu adevărat în intervalul dat, cu excepția cazului în care N este un divizor al lui 32767 (limita superioară a lui $ RANDOM).

Răspuns

On bash

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

unde 1/10000 este aleatoriu precizie și 4 cifre precizia de ieșire

Răspuns

zsh are o funcție aritmetică rand48() (înfășurarea funcției standard erand48()) în zsh/mathfunc modul:

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

În timp ce $RANDOM este de 15 biți, pseudo-aleatoriu și reproductibil, bash 5.1+ are un număr întreg de 32 biți mai sigur $SRANDOM, bazat pe surse cu adevărat aleatorii acolo unde este disponibil. Nu acceptă aritmetica în virgulă mobilă, dar cel puțin o puteți folosi pentru a semăna awk „generatorul pseudo aleatoriu (care altfel implicit folosește rezultatul foarte previzibil al time()):

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

(rețineți că este încă doar 32 de biți de entropie și awk face o generație pseudo-aleatorie deterministă bazată pe acea sămânță)

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *