Bash: hoe een willekeurig zwevend getal te genereren met $ RANDOM

Is het mogelijk om echte willekeurige getallen te genereren met een specifieke precisie en in een specifiek bereik met behulp van de Integer Random Generator $ RANDOM? Hoe kunnen we bijvoorbeeld een reëel getal genereren met een precisie van 4 tussen 0 en 1?

0.1234 0.0309 0.9001 0.0000 1.0000 

Een eenvoudige oplossing:

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

Reacties

  • Geef aan wat je bedoelt met ” echte willekeurige getallen “. Heb je een bron van willekeurige getallen nodig die wordt gegenereerd door zoiets als deeltjesverval, of ben je blij met een pseudo-willekeurige generator? Is uw toepassing van deze cijfers van cryptografische of wetenschappelijke betekenis, of wilt u gewoon iets dat ” er willekeurig uitziet “.
  • … of bedoel je eigenlijk ” float ” of ” zwevend puntnummer?
  • Bedankt voor je reactie. Ik heb een generator voor pseudo-willekeurige getallen nodig voor getallen met drijvende komma gebaseerd op $ RANDOM.
  • … voor het implementeren van meta-heuristische algoritmen in bash.

Antwoord

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

Dit levert n willekeurige getallen op (tien in het voorbeeld) in het bereik [0,1) met vier decimalen. Het gebruikt de rand() -functie in awk (niet in standaard awk maar geïmplementeerd door de meest voorkomende awk implementaties) die een willekeurige waarde in dat bereik retourneert. De generator voor willekeurige getallen wordt gezaaid door de shell “s $RANDOM variabele.

Wanneer een awk programma slechts BEGIN blokken (en geen andere codeblokken), awk zal niet proberen om invoer uit zijn standaard invoerstroom te lezen.

Op elk OpenBSD-systeem (of systeem dat hetzelfde jot hulpprogramma heeft, oorspronkelijk in 4.2BSD), de volgende genereert 10 willekeurige getallen zoals gespecificeerd:

jot -p 4 -r 10 0 1 

Reacties

  • Echt strikt genomen, aangezien de output van rand() is een float binnen [0,1), het is waarschijnlijk niet ‘ t precies gelijk verdeeld wanneer afgerond op vier decimalen. Het zou zijn, als de float van oneindige precisie zou zijn, maar het is niet ‘ t: het ‘ wordt waarschijnlijk gegenereerd uit willekeurige bits , dus er zijn 2 ^ N verschillende waarden, en ze ‘ kunnen niet uniform worden toegewezen aan een set van 1000 waarden. Maar zolang die pseudo-willekeurige drijvers genoeg bits hebben, en je ‘ niets echt exact doet, heb je waarschijnlijk ‘ t gewonnen opmerking.

Antwoord

Zoals aangegeven in een ander antwoord, zijn er andere hulpprogrammas die u kunt gebruiken om willekeurige nummers. In dit antwoord beperk ik mijn bronnen tot $RANDOM, en een paar elementaire rekenkundige functies.

Probeer voor getallen met drijvende komma zoiets als

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

Dat levert de beste precisie op omdat $RANDOM alleen getallen tussen 0 en 32767 genereert. (inclusief 32767!) Maar ik ” heb ook mijn regel overtreden over het gebruik van elementaire rekenkundige functies door bc aan te roepen.

Maar voordat ik verder ga, zou ik “twee kwesties willen bekijken precisie en bereik voor getallen met drijvende komma. Daarna zal ik kijken naar het genereren van een reeks gehele getallen (en als je gehele getallen kunt genereren, kun je ze later delen om een decimaal te krijgen als je wilt dat je de hulpprogrammas gebruikt die je wilt om dat te bereiken.)

Precisie

Met de benadering van $RANDOM/32768, sinds $RANDOM genereert waarden van 0 tot 32767, het resultaat van $RANDOM/32768 zal eveneens eindig veel waarden zijn. Met andere woorden, het is nog steeds een discrete willekeurige variabele (en met een computer kom je daar nooit bij vandaan). Met dat in gedachten kun je een zekere mate van precisie bereiken door printf te gebruiken.

Als je een fijnere dekking van de interval, zou je kunnen denken in basis 32768. In theorie zou $RANDOM + $RANDOM*32768 je dus een uniforme verdeling tussen 0 en 1.073.741.823 moeten geven. Maar ik betwijfel of de opdrachtregel die precisie heel goed zal verwerken. Een paar punten met betrekking tot dit specifieke geval:

  • De som van twee onafhankelijke, uniform verdeelde willekeurige variabelen is over het algemeen niet uniform. In dit geval, tenminste theoretisch gesproken (zie derde punt), zijn ze dat.
  • Denk niet dat je $RANDOM + $RANDOM*32768 = $RANDOM * ( 1 + 32768 ) kunt vereenvoudigen.De twee exemplaren van $RANDOM zijn eigenlijk twee verschillende gebeurtenissen.
  • Ik weet niet genoeg over hoe $RANDOM is gegenereerd om te weten of het twee keer op deze manier aanroepen echt twee onafhankelijke willekeurige gebeurtenissen zal genereren.

Bereik

Laten we eens kijken naar $RANDOM/32768. Als je een getal in een bereik wilt, zeg dan [a,b), en

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

brengt je in het gewenste bereik .

Genereren van gehele getallen

Overweeg eerst om willekeurige getallen te genereren tussen [0,b) waarbij b kleiner is dan 32768. Beschouw het product q*b, waarbij q het gehele deel is van 32768/b. Wat je dan kunt doen is een willekeurig getal tussen 0 en 32767 genereren, maar die groter dan of gelijk aan q*b weggooien. Bel het aldus gegenereerde nummer G. Dan valt G in het bereik van 0 tot q*b, en de distributie zal uniform zijn. Pas nu modulaire rekenkunde toe om deze waarde terug te brengen tot het gewenste bereik:

G % b 

Let op, genereer willekeurig een getal als volgt

$RANDOM % b 

zal geen uniforme distributie creëren, tenzij b toevallig een van de delers is van 32768.

Een bash-script schrijven voor dit

Berekenen q*b zoals hierboven beschreven klinkt als lastig. Maar het is echt niet “t. Je kunt het als volgt krijgen:

q*b = 32768 - ( 32768 % b ) 

In Bash kun je dit krijgen met

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

De volgende code genereert een willekeurig getal in het bereik 0..b (niet inclusief b) . b=$1

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

Addendum

Technisch gezien is er” weinig reden om mee te werken

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

Het volgende zal hetzelfde bereiken

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

Het “is veel meer werk, maar computers zijn snel.

Een geheel getal genereren in een groter bereik

Ik zal je dit laten uitzoeken. Voorzichtigheid is geboden, en op een gegeven moment moet je rekening houden met de geheugenbeperkingen van de computer bij het verwerken van rekenkundige bewerkingen.

Laatste opmerking

Het geaccepteerde antwoord zal geen gelijkmatig willekeurig getal tussen 0 en 1 creëren.

Probeer het volgende om dit te zien

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

Voor een echt uniforme verdeling over [0,1) zou je een gemiddelde moeten zien van bijna 0.500.

Maar zoals je kunt zien door het bovenstaande fragment uit te voeren, krijg je in plaats daarvan zoiets als 314.432 of 322.619. Aangezien het 1000 getallen zijn, is het gemiddelde hiervan .322. Het echte gemiddelde voor deze reeks gegenereerde getallen is .316362

Je kunt dit echte gemiddelde verkrijgen met het perl-script

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

Ik voeg hier gehele getallen toe om u te helpen zien hoe deze benadering van het gebruik van .$RANDOM niet doet wat u waarschijnlijk wilt dat het doet. Met andere woorden, bedenk welke gehele getallen worden gegenereerd en welke helemaal worden gemist. Een behoorlijk groot aantal wordt overgeslagen; er zijn er nogal wat verdubbeld.

Antwoord

Op systemen waar shell “s printf de %a formaat (bash ksh zsh, etc.) en is daarom in staat om een interne basiswijziging uit te voeren (hex -> dec) (uniform in [0,1) bereik van 0,00003 tot 0.99997):

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

Je zou zelfs meer cijfers kunnen gebruiken door meer oproepen naar $RANDOM (van 0.000000001 tot 0.999999999)

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

Het interne (naar de shell) “$ RANDOM” -algoritme is gebaseerd op een lineair feedback-schuifregister (LFSR). Die zijn niet cryptografisch Beveiligde Pseudo Random Number Generators (CSPRNGs). Een betere optie is om bytes van het /dev/urandom -apparaat te gebruiken. Daarvoor is een aanroep naar een externe octale of hex-dump vereist.

$ 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 

Een zeer eenvoudige (maar niet-uniforme) oplossing om een float te krijgen is:

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

Een manier om het uniform te maken in het bereik [0,1) (exclusief 1):

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

Antwoord

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

Vervang N met MAX nummer en MIN met minimum aantal dat u wilt genereren. (N aangezien MAX exclusief is, plaats N+1 om zowel MAX als MIN te hebben).

Of je kunt in plaats daarvan $(shuf -i MIN-MAX -n 1) gebruiken.

van 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 

De -n 1 in shuf betekent hier slechts één willekeurig getal genereren.

Dit genereert willekeurige getallen tussen 0 ~ 9999 met voorloopnullen met printf (in resultaat, nummer 1

is exclusief).

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

Reacties

  • Dit zal ook geen echt willekeurig getal produceren in het opgegeven bereik, behalve in het geval dat N een deler is van 32767 (de bovengrens van $ RANDOM).

Antwoord

On bash

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

waarbij 1/10000 jouw willekeurige precisie en 4 cijfers uw uitvoerprecisie

Antwoord

zsh heeft een rand48() rekenkundige functie (wrapper naar de erand48() standaardfunctie) in zijn zsh/mathfunc module:

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

Terwijl $RANDOM 15 bits is, pseudo-willekeurig en reproduceerbaar, bash 5.1+ heeft een veiliger 32-bits geheel getal $SRANDOM, gebaseerd op echt willekeurige bronnen, indien beschikbaar. Het ondersteunt geen drijvende-kommaberekeningen, maar je kunt het in ieder geval gebruiken om de pseudo-willekeurige generator van awk “te zaaien (die anders standaard het zeer voorspelbare resultaat van time()):

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

(houd er rekening mee dat het nog steeds maar 32 bits entropie is, en awk doet deterministische pseudo-willekeurige generatie op basis van dat zaad)

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *