Bash: Slik genererer du tilfeldig flytnummer ved hjelp av $ RANDOM

Er det mulig å generere reelle tilfeldige tall med en bestemt presisjon og i et bestemt område ved hjelp av Integer Random Generator $ RANDOM? For eksempel hvordan vi kan generere reelt tall med 4 presisjon mellom 0 og 1?

0.1234 0.0309 0.9001 0.0000 1.0000 

En enkel løsning:

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

Kommentarer

  • Oppgi hva du mener med » reelle tilfeldige tall «. Krever du en kilde til tilfeldige tall generert av noe som partikkelråte, eller vil du være fornøyd med en pseudo-tilfeldig generator? Er din anvendelse av disse tallene av kryptografisk eller vitenskapelig betydning, eller vil du bare ha noe som » ser tilfeldig ut «.
  • … eller mener du egentlig » float » eller » flytende poengnummer?
  • Takk for kommentaren. Jeg trenger en pseudo tilfeldig tallgenerator for flytende tall basert på $ RANDOM.
  • … for å implementere meta heuristiske algoritmer i bash.

Svar

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

Dette vil gi n tilfeldige tall (ti i eksemplet) i området [0,1] med fire desimaltegn. Den bruker rand() -funksjonen i awk (ikke i standard awk men implementert av de vanligste awk implementeringer) som returnerer en tilfeldig verdi i det området. Tilfeldig tallgenerator blir seedet av skallet «s $RANDOM variabel.

Når et awk -program bare har BEGIN blokker (og ingen andre kodeblokker), awk vil ikke prøve å lese inngang fra sin standard inngangsstrøm.

På ethvert OpenBSD-system (eller system som har samme jot verktøy , opprinnelig i 4.2BSD), følgende vil generere 10 tilfeldige tall som spesifisert:

jot -p 4 -r 10 0 1 

Kommentarer

  • Virkelig strengt tatt, siden utdata av rand() er en flottør innenfor [0,1), er den sannsynligvis ikke ‘ t nøyaktig jevnt fordelt når den avrundes til fire desimaltegn. Hvis flottøren hadde uendelig presisjon, ville den ikke være ‘ t: den ‘ vil sannsynligvis bli generert fra tilfeldige biter , så det er 2 ^ N forskjellige verdier, og de kartlegger ikke ‘ til et sett med 1000 verdier. Men så lenge de pseudo-tilfeldige flottørene har nok biter, og du ‘ ikke gjør noe virkelig nøyaktig, vant du sannsynligvis ‘ t varsel.

Svar

Som påpekt i et annet svar, er det andre verktøy du kan bruke til å generere tilfeldige tall. I dette svaret begrenser jeg ressursene mine til $RANDOM, og noen få grunnleggende aritmetiske funksjoner.

For flytende tall, prøv noe sånt som

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

Det gir deg den beste presisjonen fordi $RANDOM bare genererer tall mellom 0 og 32767. (inkludert 32767!) Men, jeg » Jeg har også brutt min regel om bruk av grunnleggende aritmetiske funksjoner ved å påkalle bc.

Men før jeg går videre, vil jeg se på to saker presisjon og rekkevidde for flytende tall. Etter det vil jeg se på å generere en rekke heltall (og hvis du kan generere heltall, kan du senere dele dem for å få et desimal hvis du ønsker å bruke hvilke verktøy du foretrekker å oppnå det.)

Presisjon

Tar tilnærmingen til $RANDOM/32768, siden $RANDOM genererer verdier fra 0 til 32767, resultatet av $RANDOM/32768 vil også være endelig mange verdier. Med andre ord er det fortsatt en diskret tilfeldig variabel (og med en datamaskin vil du aldri kunne komme vekk fra det faktum). Med det i tankene, kan du oppnå en viss grad av presisjon ved å bruke printf.

Hvis du vil ha en finere dekning av intervall, kan du begynne å tenke i base 32768. Så i teorien skal $RANDOM + $RANDOM*32768 gi deg en jevn fordeling mellom 0 og 1 073 741 823. Men jeg er tvilende til at kommandolinjen vil takle den presisjonen veldig bra. Et par punkter knyttet til denne spesifikke saken:

  • Summen av to uavhengige, jevnt fordelte tilfeldige variabler i ikke generelt ensartede. I dette tilfellet, i det minste teoretisk sett (se tredje punkt), er de det.
  • Tror ikke du kan forenkle $RANDOM + $RANDOM*32768 = $RANDOM * ( 1 + 32768 ).De to forekomstene av $RANDOM er egentlig to forskjellige hendelser.
  • Jeg vet ikke nok om hvordan $RANDOM er generert for å vite om å kalle det to ganger slik virkelig vil generere to uavhengige tilfeldige hendelser.

Område

La oss bare vurdere $RANDOM/32768. Hvis du vil ha et tall i et område, si [a,b), så

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

lander deg i ønsket område .

Generering av heltallverdier

Vurder først å generere tilfeldige tall mellom [0,b) der b er mindre enn 32768. Tenk på produktet q*b, der q er hele delen av 32768/b. Så hva du kan gjøre er å generere tilfeldig tall mellom 0 og 32767, men kaste ut de som er større enn eller lik q*b. Ring nummeret som er generert G. Da vil G falle i området 0 til q*b, og fordelingen vil være ensartet. Bruk nå modulær aritmetikk for å få denne verdien ned i ønsket område:

G % b 

Merk, generer tilfeldig et tall som følger

$RANDOM % b 

vil ikke skape en jevn fordeling, med mindre b tilfeldigvis er en av delene til 32768.

Skrive et bash-skript for denne

Beregning q*b som beskrevet ovenfor høres ut som en smerte. Men det er virkelig ikke «t. Du kan få det som følger:

q*b = 32768 - ( 32768 % b ) 

I Bash kan du få dette med

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

Følgende kode genererer et tilfeldig tall i området 0..b (ikke inkluderer b) . b=$1

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

Tillegg

Teknisk sett er det liten grunn til å jobbe med

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

Følgende vil oppnå det samme

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

Det er mye mer arbeid, men datamaskiner er raske.

Å generere et heltall i et større område

Jeg lar deg finne ut av dette. Det må utvises forsiktighet, og på et eller annet tidspunkt må du ta hensyn til datamaskinens minnebegrensninger ved håndtering av aritmetiske operasjoner.

Avsluttende merknad

Det aksepterte svaret vil ikke opprette et tilfeldig tall jevnt over 0 til 1.

For å se dette, prøv følgende

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

For en virkelig jevn fordeling over [0,1) bør du se et gjennomsnitt på nær 0.500.

Men som du kan se ved å kjøre utdraget ovenfor, vil du i stedet få noe sånt som 314.432 eller 322.619. Siden det er 1000 tall, er gjennomsnittet av dette .322. Det sanne gjennomsnittet for denne sekvensen av genererte tall er .316362

Du kan oppnå dette sanne gjennomsnittet ved hjelp av perl-skriptet

 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; }" 

Jeg legger til heltall her for å hjelpe deg å se hvordan denne fremgangsmåten for å bruke .$RANDOM ikke gjør det du mest sannsynlig vil at den skal gjøre. Med andre ord, tenk på hvilke heltall som genereres og hvilke som blir savnet helt. Ganske mange hoppes over; ganske mange er doblet.

Svar

På systemer der shell «s printf er i stand til å forstå %a format (bash ksh zsh, etc.) og er derfor i stand til å utføre en intern basisendring (hex -> dec) (uniform i [0,1) fra 0,00003 til 0.99997):

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

Du kan til og med bruke flere sifre ved å kombinere flere samtaler til $RANDOM (fra 0.000000001 til 0.999999999)

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

Den interne (til skallet) «$ RANDOM» -algoritmen er basert i et lineært tilbakemeldingsskiftregister (LFSR). Disse er ikke kryptografisk Secure Pseudo Random Number Generators (CSPRNGs). Et bedre alternativ er å bruke byte fra /dev/urandom -enheten. Det vil kreve anrop til ekstern oktal eller hex dump.

$ 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 

En veldig enkel (men ikke-ensartet) løsning for å få en float er:

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

En måte å gjøre det ensartet i området [0,1) (ikke inkludert 1):

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

Svar

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

Erstatt N med MAX-nummer og MIN med minimum antall du vil generere. (N da MAX er eksklusivt, sett N+1 for å ha både MAX, MIN inkludert).

Eller du kan bruke $(shuf -i MIN-MAX -n 1) i stedet.

fra 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 i shuf betyr her bare å generere ett tilfeldig tall.

Dette vil generere tilfeldige tall mellom 0 ~ 9999 med ledende nuller bruker printf (i resultatet, nummer 1

er eksklusiv).

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

Kommentarer

  • Dette vil heller ikke produsere et ekte tilfeldig tall i det gitte området, bortsett fra i tilfelle der N er en divisor på 32767 (den øvre grensen på $ RANDOM).

Svar

På bash

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

der 1/10000 er din tilfeldige presisjon og 4 setter utskriftspresisjonen din

Svar

zsh har en rand48() aritmetisk funksjon (innpakning til erand48() standardfunksjon) i sin zsh/mathfunc -modul:

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

Mens $RANDOM er 15 bits, pseudo-tilfeldig og reproduserbar, bash 5.1+ har et sikrere 32-biters heltall $SRANDOM, basert på virkelig tilfeldige kilder der det er tilgjengelig. Den støtter ikke flytepunktsaritmetikk, men i det minste kan du bruke den til å så awk «s pseudo tilfeldig generator (som ellers som standard bruker det veldig forutsigbare resultatet av time()):

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

(husk at det fremdeles bare er 32 biter entropi, og awk gjør deterministisk pseudo-tilfeldig generasjon basert på det frøet)

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert. Obligatoriske felt er merket med *