Bash: Sådan genereres tilfældigt float-nummer ved hjælp af $ RANDOM

Er det muligt at generere reelle tilfældige tal med en bestemt præcision og i et specifikt interval ved hjælp af Integer Random Generator $ RANDOM? For eksempel hvordan vi kan generere reelle tal med 4 præcision mellem 0 og 1?

0.1234 0.0309 0.9001 0.0000 1.0000 

En simpel løsning:

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

Kommentarer

  • Angiv venligst hvad du mener med ” reelle tilfældige tal “. Kræver du en kilde til tilfældige tal genereret af noget som partikelforfald, eller vil du være tilfreds med en pseudo-tilfældig generator? Er din anvendelse af disse antal kryptografisk eller videnskabelig betydning, eller vil du bare have noget, der ” ser tilfældigt ud “.
  • … eller mener du faktisk ” float ” eller ” flydende punkt nummer?
  • Tak for kommentaren. Jeg har brug for en pseudo tilfældig talgenerator til flydende tal baseret på $ RANDOM.
  • … til implementering af 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 sender n tilfældige tal (ti i eksemplet) i området [0,1] med fire decimaler. Det bruger rand() -funktionen i awk (ikke i standard awk men implementeret af de mest almindelige awk implementeringer), der returnerer en tilfældig værdi i dette interval. Generatoren for tilfældige tal udsås af skallen “s $RANDOM variabel.

Når et awk -program kun har BEGIN blokke (og ingen andre kodeblokke), awk forsøger ikke at læse input fra sin standardindgangsstrøm.

På ethvert OpenBSD-system (eller system, der har det samme jot værktøj , oprindeligt i 4.2BSD), følgende genererer 10 tilfældigt tal som angivet:

jot -p 4 -r 10 0 1 

Kommentarer

  • Virkelig strengt taget, da output af rand() er en svømmer inden for [0,1), er den sandsynligvis ikke ‘ t nøjagtigt jævnt fordelt, når den afrundes til fire decimaler. Hvis svømmeren var af uendelig præcision, ville det være, men det er ‘ t: det ‘ sandsynligvis genereres fra tilfældige bits , så der er 2 ^ N forskellige værdier, og de ‘ t kortes ensartet til et sæt med 1000 værdier. Men så længe disse pseudo-tilfældige flyder har nok bits, og du ‘ ikke gør noget rigtig nøjagtigt, vandt du sandsynligvis ‘ t bemærkning.

Svar

Som påpeget i et andet svar, er der andre hjælpeprogrammer, du kan bruge til at generere tilfældige tal. I dette svar begrænser jeg mine ressourcer til $RANDOM og et par grundlæggende aritmetiske funktioner.

For flydende numre, prøv noget som

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

Det giver dig den bedste præcision, fordi $RANDOM kun genererer tal mellem 0 og 32767. (inklusive 32767!) Men jeg ” har også brudt min regel om brug af grundlæggende aritmetiske funktioner ved at påkalde bc.

Men inden jeg går videre, vil jeg gerne se på to emner præcision og rækkevidde for flydende numre. Derefter vil jeg se på at generere en række heltal (og hvis du kan generere heltal, kan du senere dele dem for at få et decimal, hvis du ønsker at bruge de hjælpeprogrammer, du foretrækker for at opnå det.)

Præcision

Efter fremgangsmåden for $RANDOM/32768, da $RANDOM genererer værdier fra 0 til 32767, resultatet af $RANDOM/32768 vil ligeledes være endeligt mange værdier. Med andre ord er det stadig en diskret tilfældig variabel (og med en computer vil du aldrig være i stand til at komme væk fra det faktum). Med det i tankerne kan du opnå en vis grad af præcision ved at bruge printf.

Hvis du vil have en finere dækning af interval, kan du begynde at tænke i base 32768. Så i teorien skal $RANDOM + $RANDOM*32768 give dig en ensartet fordeling mellem 0 og 1.073.741.823. Men jeg er i tvivl om, at kommandolinjen vil håndtere den præcision meget godt. Et par punkter, der vedrører denne særlige sag:

  • Summen af to uafhængige, jævnt fordelte tilfældige variabler i ikke generelt ensartede. I dette tilfælde er de i det mindste teoretisk set (se tredje punkt).
  • Tror ikke du kan forenkle $RANDOM + $RANDOM*32768 = $RANDOM * ( 1 + 32768 ).De to forekomster af $RANDOM er virkelig to forskellige begivenheder.
  • Jeg ved ikke nok om, hvordan $RANDOM er genereret for at vide, om at kalde det to gange som dette virkelig vil generere to uafhængige tilfældige hændelser.

Område

Lad os bare overveje $RANDOM/32768. Hvis du vil have et tal i et interval, skal du sige [a,b), så

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

lander dig i det ønskede interval .

Generering af heltalværdier

Overvej først at generere tilfældige tal mellem [0,b) hvor b er mindre end 32768. Overvej produktet q*b, hvor q er den heltals del af 32768/b. Så hvad du kan gøre er at generere tilfældigt tal mellem 0 og 32767, men smide dem ud, der er større end eller lig med q*b. Ring til det således genererede nummer G. Derefter falder G i området 0 til q*b, og fordelingen vil være ensartet. Anvend nu modulær aritmetik for at få denne værdi ned i det ønskede område:

G % b 

Bemærk, generer tilfældigt et tal som følger

$RANDOM % b 

opretter ikke en ensartet fordeling, medmindre b tilfældigvis er en af delerne af 32768.

Skrivning af et bash-script til denne

Beregning q*b som beskrevet ovenfor lyder 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 tilfældigt tal i området 0..b (ikke inklusive b) . b=$1

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

Addendum

Teknisk set er der ringe grund til at arbejde med

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

Følgende vil opnå det samme

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

Det er meget mere arbejde, men computere er hurtige.

Generering af et heltal i et større område

Jeg lader dig finde ud af dette. Der skal udvises forsigtighed, og på et eller andet tidspunkt skal du tage computerens hukommelsesbegrænsninger i betragtning ved håndtering af aritmetiske operationer.

Afsluttende note

Det accepterede svar opretter ikke et tilfældigt tal ensartet over 0 til 1.

For at se dette, prøv følgende

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

For en virkelig ensartet fordeling over [0,1) skal du se et gennemsnit på tæt på 0.500.

Men som du kan se ved at køre ovenstående uddrag, får du i stedet noget som 314.432 eller 322.619. Da det er 1000 tal, er gennemsnittet af dette .322. Det sande gennemsnit for denne sekvens af genererede tal er .316362

Du kan opnå dette sande gennemsnit ved hjælp af perl-scriptet

 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 tilføjer heltal her for at hjælpe dig med at se, hvordan denne fremgangsmåde ved at bruge .$RANDOM ikke gør det, du sandsynligvis vil have det til at gøre. Med andre ord, overvej hvilke heltal der genereres, og hvilke der går glip af helt. Et stort antal springes over; en hel del fordobles.

Svar

På systemer, hvor shell “s printf er i stand til at forstå %a format (bash ksh zsh osv.) og er derfor i stand til at udføre en intern basisændring (hex -> dec) (ensartet i [0,1) fra 0,00003 til 0.99997):

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

Du kan endda bruge flere cifre ved at kombinere flere opkald til $RANDOM (fra 0.000000001 til 0.999999999)

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

Den interne (til shell) “$ RANDOM” -algoritmen er baseret i et lineært feedback-skiftregister (LFSR). Disse er ikke kryptografisk Secure Pseudo Random Number Generators (CSPRNGs). En bedre mulighed er at bruge byte fra /dev/urandom -enheden. Det kræver opkald 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 meget enkel (men ikke-ensartet) løsning til at få en float er:

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

En måde at gøre det ensartet i området [0,1) (ekskl. 1):

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

Svar

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

Udskift N med MAX-nummer og MIN med minimumantal, du vil generere. (N da MAX er eksklusiv, skal du sætte N+1 for at have både MAX, MIN inklusive).

Eller du kan bruge $(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 betyder her kun at generere et tilfældigt tal.

Dette vil generere tilfældige tal mellem 0 ~ 9999 med førende nuller ved hjælp af printf (i resultat, nummer 1

er eksklusiv).

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

Kommentarer

  • Dette producerer heller ikke et ægte tilfældigt tal i det givne interval undtagen i det tilfælde, hvor N er en skillevæg på 32767 (den øvre grænse på $ RANDOM).

Svar

På bash

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

hvor 1/10000 er din tilfældige præcision og 4 cifrer din outputpræcision

Svar

zsh har en rand48() aritmetisk funktion (indpakning til erand48() standardfunktionen) i dens zsh/mathfunc modul:

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

Mens $RANDOM er 15 bits, pseudo-tilfældig og reproducerbar, bash 5.1+ har et sikrere 32 bit heltal $SRANDOM, baseret på ægte tilfældige kilder, hvor de er tilgængelige. Det understøtter ikke aritmetik med flydende punkt, men i det mindste kan du bruge det til at så awk “s pseudo tilfældige generator (som ellers som standard bruger det meget forudsigelige resultat af time()):

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

(husk, det er stadig kun 32 bit entropi, og awk gør deterministisk pseudo-tilfældig generation baseret på det frø)

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *