Bash: Hur man genererar slumpmässigt flytnummer med $ RANDOM

Är det möjligt att generera riktiga slumptal med en specifik precision och i ett visst intervall med hjälp av Integer Random Generator $ RANDOM? Till exempel hur vi kan generera reellt tal med 4 precision mellan 0 och 1?

0.1234 0.0309 0.9001 0.0000 1.0000 

En enkel lösning:

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

Kommentarer

  • Ange vad du menar med ” verkliga slumptal ”. Kräver du en slumpmässig källa som genereras av något som partikelförfall eller kommer du att vara nöjd med en pseudoslumpgenerator? Är din tillämpning av dessa antal av kryptografisk eller vetenskaplig betydelse, eller vill du bara ha något som ” ser slumpmässigt ut ”.
  • … eller menar du faktiskt ” float ” eller ” flytande poängnummer?
  • Tack för din kommentar. Jag behöver en pseudo slumptalsgenerator för flytande siffror baserat på $ RANDOM.
  • … för att implementera meta heuristiska algoritmer i bash.

Svar

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

Detta kommer att mata ut n slumptal (tio i exemplet) i intervallet [0,1] med fyra decimaler. Det använder rand() -funktionen i awk (inte i standard awk men implementeras av de vanligaste awk implementeringar) som returnerar ett slumpmässigt värde i det intervallet. Slumpgeneratorns sådd av skalet ”s $RANDOM variabel.

När ett awk -program bara har BEGIN block (och inga andra kodblock), awk försöker inte läsa ingång från sin standardinmatningsström.

På alla OpenBSD-system (eller system som har samma jot verktyg , ursprungligen i 4.2BSD), följande kommer att generera 10 slumpmässigt nummer som specificerat:

jot -p 4 -r 10 0 1 

Kommentarer

  • Verkligen strängt taget, eftersom utdata av rand() är en flottör inom [0,1), är den troligen inte ’ t exakt jämnt fördelad när den avrundas till fyra decimaler. Om flottören var av oändlig precision, men det är ’ t: det ’ kommer sannolikt att genereras från slumpmässiga bitar , så det finns 2 ^ N olika värden, och de

t mappar enhetligt till en uppsättning med 1000 värden. Men så länge de pseudo-slumpmässiga flottörerna har tillräckligt med bitar och du ’ inte gör någonting riktigt exakt, vann du förmodligen ’ t meddelande.

Svar

Som påpekats i ett annat svar finns det andra verktyg som du kan använda för att skapa slumpmässiga siffror. I det här svaret begränsar jag mina resurser till $RANDOM och några grundläggande aritmetiska funktioner.

För flytande siffror kan du prova något som

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

Det ger dig bästa precision eftersom $RANDOM bara genererar siffror mellan 0 och 32767. (inklusive 32767!) Men jag ” Jag har också brutit mot min regel om att använda grundläggande aritmetiska funktioner genom att åberopa bc.

Men innan jag går vidare vill jag titta på två frågor precision och intervall för flytande nummer. Efter det kommer jag att titta på att generera ett antal heltal (och om du kan generera heltal kan du senare dela dem för att få ett decimal om du vill använda de verktyg du föredrar för att uppnå det.)

Precision

Tar tillvägagångssättet för $RANDOM/32768, eftersom $RANDOM genererar värden från 0 till 32767, resultatet av $RANDOM/32768 kommer också att vara ändligt många värden. Med andra ord är det fortfarande en diskret slumpmässig variabel (och med en dator kommer du aldrig att kunna komma ifrån det faktum). Med det i åtanke kan du åstadkomma en viss grad av precision genom att använda printf.

Om du vill ha en finare täckning av intervall kan du börja tänka i bas 32768. Så, i teorin $RANDOM + $RANDOM*32768 borde ge dig en enhetlig fördelning mellan 0 och 1 073 741 823. Men jag är tveksam till att kommandoraden kommer att hantera den precisionen mycket bra. Ett par punkter som rör detta speciella fall:

  • Summan av två oberoende, jämnt fördelade slumpmässiga variabler i vanligtvis inte enhetliga. I det här fallet är de åtminstone teoretiskt sett (se tredje punkten).
  • Tror inte att du kan förenkla $RANDOM + $RANDOM*32768 = $RANDOM * ( 1 + 32768 ).De två förekomsterna av $RANDOM är egentligen två olika händelser.
  • Jag vet inte tillräckligt om hur $RANDOM är genereras för att veta om det kallas två gånger så här verkligen genererar två oberoende slumpmässiga händelser.

Område

Låt oss bara ta hänsyn till $RANDOM/32768. Om du vill ha ett nummer i ett intervall, säg [a,b), då

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

landar dig i önskat intervall .

Generering av helvärden

Överväg först att skapa slumptal mellan [0,b) där b är mindre än 32768. Tänk på produkten q*b, där q är den fullständiga delen av 32768/b. Vad du kan göra är att generera slumpmässigt tal mellan 0 och 32767, men kasta ut dem som är större än eller lika med q*b. Ring det så genererade numret G. Då G faller inom intervallet 0 till q*b, och dess fördelning blir enhetlig. Tillämpa nu modulär aritmetik för att få detta värde ned i önskat intervall:

G % b 

Obs! Slumpmässigt generera ett nummer enligt följande

$RANDOM % b 

skapar inte en enhetlig fördelning, såvida inte b råkar vara en av delarna av 32768.

Skriva ett bash-skript för denna

Beräknar q*b som beskrivs ovan låter som en smärta. Men det är verkligen inte. Du kan få det på följande sätt:

q*b = 32768 - ( 32768 % b ) 

I Bash kan du få detta med

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

Följande kod genererar ett slumpmässigt tal i intervallet 0..b (ingår inte b) . b=$1

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

Tillägg

Tekniskt sett finns det liten anledning att arbeta med

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

Följande kommer att åstadkomma samma sak

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

Det är mycket mer arbete, men datorer är snabba.

Om du genererar ett heltal i ett större intervall

Jag kan låta dig ta reda på det här. Försiktighet måste iakttas, och någon gång måste du ta hänsyn till datorns minnesbegränsningar vid hantering av aritmetiska operationer.

Slutlig anmärkning

Det accepterade svaret skapar inte ett slumpmässigt tal jämnt över 0 till 1.

För att se detta, prova följande

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

För en verkligt enhetlig fördelning över [0,1) bör du se ett genomsnitt på nära 0.500.

Men som du kan se genom att köra kodavsnittet ovan får du istället något som 314.432 eller 322.619. Eftersom det är 1000 nummer är genomsnittet av detta .322. Det verkliga genomsnittet för denna sekvens av genererade nummer är .316362

Du kan få detta sanna medelvärde med 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; }" 

Jag lägger till heltal här för att hjälpa dig se hur det här sättet att använda .$RANDOM inte gör vad du troligen vill att det ska göra. Med andra ord, tänk på vilka heltal som genereras och vilka som missas helt. Ganska stort antal hoppas över; en hel del fördubblas.

Svar

På system där skalets printf kan förstå %a format (bash ksh zsh, etc.) och kan därför utföra en intern basändring (hex -> dec) (enhetlig i [0,1) intervall från 0,00003 till 0.99997):

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

Du kan till och med använda fler siffror genom att kombinera fler samtal till $RANDOM (från 0.000000001 till 0.999999999)

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

Den interna (till skalet) ”$ RANDOM” -algoritmen är baserad i ett linjärt återkopplingsskiftregister (LFSR). Dessa är inte kryptografiskt Secure Pseudo Random Number Generators (CSPRNGs). Ett bättre alternativ är att använda byte från /dev/urandom -enheten. Det kräver anrop till extern 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 mycket enkel (men icke-enhetlig) lösning för att få en float är:

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

Ett sätt att göra det enhetligt i intervallet [0,1) (exklusive 1):

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

Svar

Använd $(( ( RANDOM % N ) + MIN ))

Ersätt N med MAX-nummer och MIN med minsta antal du vill generera. (N eftersom MAX är exklusivt, sätt N+1 för att ha både MAX, MIN inklusive).

Eller så kan du använda $(shuf -i MIN-MAX -n 1) istället.

från 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 här bara generera ett slumpmässigt tal.

Detta genererar slumpmässiga tal mellan 0 ~ 9999 med ledande nollor använder printf (i resultat, nummer 1

är exklusivt).

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

Kommentarer

  • Detta kommer inte heller att ge ett riktigt slumpmässigt tal i det angivna intervallet förutom i fallet där N är en delare på 32767 (den övre gränsen på $ RANDOM).

Svar

På bash

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

där 1/10000 är ditt slumpmässiga precision och 4 räknar utmatningsprecisionen

Svar

zsh har en rand48() aritmetisk funktion (omslag till erand48() standardfunktion) i sin zsh/mathfunc -modul:

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

Medan $RANDOM är 15 bitar, pseudoslumpmässigt och reproducerbart, bash 5.1+ har ett säkrare 32-bitars heltal $SRANDOM, baserat på verkligt slumpmässiga källor där sådana finns. Den stöder inte flytpunktsräkning, men åtminstone kan du använda den för att så awk ”s pseudo-slumpgenerator (som annars som standard använder det mycket förutsägbara resultatet av time()):

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

(kom ihåg att det fortfarande är bara 32 bitar entropi och awk gör deterministisk pseudoslumpmässig generation baserad på det fröet)

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *