Come generare in modo efficiente interi casuali grandi, distribuiti uniformemente in bash?


30

Mi chiedevo quale sarebbe il modo migliore per ottenere una buona casualità in bash, cioè quale sarebbe una procedura per ottenere un intero positivo casuale tra MINe MAXtale che

  1. L'intervallo può essere arbitrariamente ampio (o almeno, diciamo, fino a 2 32 -1);
  2. I valori sono distribuiti uniformemente (ovvero, nessuna distorsione);
  3. È efficiente.

Un modo efficace per ottenere casualità in bash è usare la $RANDOMvariabile. Tuttavia, questo campiona solo un valore tra 0 e 2 15 -1, che potrebbe non essere abbastanza grande per tutti gli scopi. Le persone in genere utilizzano un modulo per inserirlo nell'intervallo desiderato, ad es.

MIN=0
MAX=12345
rnd=$(( $RANDOM % ($MAX + 1 - $MIN) + $MIN ))

Questo, inoltre, crea una distorsione a meno che non $MAXcapiti di dividere 2 15 -1 = 32767. Ad esempio, se $MINè 0 e $MAXè 9, quindi i valori da 0 a 7 sono leggermente più probabili dei valori 8 e 9, poiché $RANDOMnon saranno mai 32768 o 32769. Questo pregiudizio peggiora all'aumentare dell'intervallo, ad esempio, se $MINè 0 ed $MAXè 9999, allora i numeri da 0 a 2767 hanno una probabilità di 4 / 32767 , mentre i numeri 2768 attraverso 9999 hanno soltanto una probabilità di 3 / 32767 .

Pertanto, mentre il metodo sopra soddisfa la condizione 3, non soddisfa le condizioni 1 e 2.

Il metodo migliore che ho trovato finora nel tentativo di soddisfare le condizioni 1 e 2 era quello di utilizzare /dev/urandomcome segue:

MIN=0
MAX=1234567890
while
  rnd=$(cat /dev/urandom | tr -dc 0-9 | fold -w${#MAX} | head -1 | sed 's/^0*//;')
  [ -z $rnd ] && rnd=0
  (( $rnd < $MIN || $rnd > $MAX ))
do :
done

Fondamentalmente, basta raccogliere la casualità da /dev/urandom(potrebbe considerare di utilizzare /dev/randominvece se si desidera un generatore di numeri pseudocasuali crittograficamente forte e se si ha molto tempo, o forse un generatore di numeri casuali hardware), eliminare ogni carattere che non sia una cifra decimale, piega l'output alla lunghezza di $MAXe taglia 0 iniziali. Se ci è capitato di ottenere solo 0, allora $rndè vuoto, quindi in questo caso impostato rndsu 0. Controlla se il risultato è al di fuori del nostro intervallo e in tal caso, quindi ripeti. Ho forzato il "corpo" dell'anello while nella guardia qui in modo da forzare l'esecuzione del corpo almeno una volta, nello spirito di emulare un do ... whileanello, poiché all'inizio rndnon è definito.

Penso di aver soddisfatto le condizioni 1 e 2 qui, ma ora ho rovinato la condizione 3. È un po 'lento. Richiede circa un secondo (circa un decimo di secondo quando sono fortunato). In realtà, il loop non è nemmeno garantito per terminare (anche se la probabilità di terminazione converge a 1 all'aumentare del tempo).

Esiste un modo efficiente per ottenere interi casuali imparziali, entro un intervallo predefinito e potenzialmente ampio, in bash? (Continuerò a indagare quando il tempo lo consente, ma nel frattempo ho pensato che qualcuno qui potesse avere una bella idea!)

Tabella delle risposte

  1. L'idea più semplice (e quindi portatile) è quella di generare una stringa di bit casuale abbastanza a lungo. Esistono diversi modi per generare una stringa di bit casuale, sia usando la $RANDOMvariabile incorporata di bash sia usando ode /dev/urandom(o /dev/random). Se il numero casuale è maggiore di $MAX, ricominciare da capo.

  2. In alternativa, è possibile utilizzare strumenti esterni.

    • La soluzione Perl
      • Pro: abbastanza portatile, semplice, flessibile
      • Contro: non per numeri molto grandi sopra 2 32 -1
    • La soluzione Python
      • Pro: semplice, flessibile, funziona anche per grandi numeri
      • Contro: meno portatile
    • La soluzione zsh
      • Pro: buono per le persone che usano zsh comunque
      • Contro: probabilmente anche meno portatile

Perché scegliere solo numeri interi, anziché codificare in base64 i bit casuali, quindi convertire un certo numero di caratteri (a seconda dell'intervallo necessario) dal modulo codificato in base10 da base64?
Muru

Ha bisogno di essere bash? Qualcosa del genere rand=$(command)farebbe se commandrestituisse un iteger che soddisfa i tuoi requisiti?
terdon

@muru In realtà è una bella idea. Avevo riflettuto su un'idea simile, usando dd if=/dev/urandom 2>/dev/nulle convogliando tutto od -t d(evita la deviazione attraverso base64), ma non mi è chiaro come avvenga la conversione e se sia effettivamente imparziale. Se riesci ad espandere la tua idea in uno script efficiente e funzionante e spiegare perché non ci sono pregiudizi, sarebbe un'ottima risposta. :)
Malte Skoruppa,

@terdon Preferirei bash. Voglio dire, ovviamente puoi invocare pythono perlo la tua lingua preferita, ma questa non è disponibile ovunque. Preferirei qualcosa di più portatile. Beh, awkla funzione casuale andrebbe bene, immagino. Ma più è portatile, meglio è :)
Malte Skoruppa,

2
Sì, stavo pensando sulla falsariga di perl -e 'print int(rand(2**32-1))');. È dannatamente portatile e sarà molto veloce. Awk non lo taglierà poiché la maggior parte delle implementazioni inizia dallo stesso seed. Quindi ottieni lo stesso numero casuale nelle corse successive. Cambia solo all'interno della stessa corsa.
terdon

Risposte:


17

Vedo un altro metodo interessante da qui .

rand=$(openssl rand 4 | od -DAn)

Anche questa sembra essere una buona opzione. Legge 4 byte dal dispositivo casuale e li formatta come numero intero senza segno tra 0e 2^32-1.

rand=$(od -N 4 -t uL -An /dev/urandom | tr -d " ")

7
dovresti usare /dev/urandomse non sai di aver bisogno/dev/random ; /dev/randomblocchi su Linux.
jfs,

perché i odcomandi sono diversi? Entrambi stampano solo numeri interi senza segno a 4 byte: 1 ° - da openssl, 2 ° - da /dev/random.
jfs,

1
@Ramesh ho modificato per usare /dev/urandominvece di /dev/random- Non vedo alcun motivo per usare /dev/random, e può essere molto costoso / lento o rallentare altre parti del sistema. (Sentiti libero modifica indietro e spiega se è davvero necessario.)
Volker Siegel,

1
Non preoccuparti, è davvero sorprendente che questa semplice differenza abbia effetti così complicati. Ecco perché ho insistito per cambiare l'esempio con quello giusto: le persone imparano dagli esempi.
Volker Siegel,

1
@MalteSkoruppa: l' Iacronimo sizeof(int)potrebbe essere inferiore a quello 4di principio. a proposito, od -DAnfallisce (2**32-1)ma od -N4 -tu4 -Ancontinua a funzionare.
jfs,

8

Grazie a tutti per tutte le vostre grandi risposte. Ho finito con la seguente soluzione, che vorrei condividere.

Prima di entrare in ulteriori dettagli sui perché e come, ecco il tl; dr : il mio nuovo brillante script :-)

#!/usr/bin/env bash
#
# Generates a random integer in a given range

# computes the ceiling of log2
# i.e., for parameter x returns the lowest integer l such that 2**l >= x
log2() {
  local x=$1 n=1 l=0
  while (( x>n && n>0 ))
  do
    let n*=2 l++
  done
  echo $l
}

# uses $RANDOM to generate an n-bit random bitstring uniformly at random
#  (if we assume $RANDOM is uniformly distributed)
# takes the length n of the bitstring as parameter, n can be up to 60 bits
get_n_rand_bits() {
  local n=$1 rnd=$RANDOM rnd_bitlen=15
  while (( rnd_bitlen < n ))
  do
    rnd=$(( rnd<<15|$RANDOM ))
    let rnd_bitlen+=15
  done
  echo $(( rnd>>(rnd_bitlen-n) ))
}

# alternative implementation of get_n_rand_bits:
# uses /dev/urandom to generate an n-bit random bitstring uniformly at random
#  (if we assume /dev/urandom is uniformly distributed)
# takes the length n of the bitstring as parameter, n can be up to 56 bits
get_n_rand_bits_alt() {
  local n=$1
  local nb_bytes=$(( (n+7)/8 ))
  local rnd=$(od --read-bytes=$nb_bytes --address-radix=n --format=uL /dev/urandom | tr --delete " ")
  echo $(( rnd>>(nb_bytes*8-n) ))
}

# for parameter max, generates an integer in the range {0..max} uniformly at random
# max can be an arbitrary integer, needs not be a power of 2
rand() {
  local rnd max=$1
  # get number of bits needed to represent $max
  local bitlen=$(log2 $((max+1)))
  while
    # could use get_n_rand_bits_alt instead if /dev/urandom is preferred over $RANDOM
    rnd=$(get_n_rand_bits $bitlen)
    (( rnd > max ))
  do :
  done
  echo $rnd
}

# MAIN SCRIPT

# check number of parameters
if (( $# != 1 && $# != 2 ))
then
  cat <<EOF 1>&2
Usage: $(basename $0) [min] max

Returns an integer distributed uniformly at random in the range {min..max}
min defaults to 0
(max - min) can be up to 2**60-1  
EOF
  exit 1
fi

# If we have one parameter, set min to 0 and max to $1
# If we have two parameters, set min to $1 and max to $2
max=0
while (( $# > 0 ))
do
  min=$max
  max=$1
  shift
done

# ensure that min <= max
if (( min > max ))
then
  echo "$(basename $0): error: min is greater than max" 1>&2
  exit 1
fi

# need absolute value of diff since min (and also max) may be negative
diff=$((max-min)) && diff=${diff#-}

echo $(( $(rand $diff) + min ))

Salvalo in ~/bin/rande avrai a tua disposizione una dolce funzione casuale in bash che può campionare un numero intero in un dato intervallo arbitrario. L'intervallo può contenere numeri interi negativi e positivi e può avere una lunghezza massima di 2 60 -1:

$ rand 
Usage: rand [min] max

Returns an integer distributed uniformly at random in the range {min..max}
min defaults to 0
(max - min) can be up to 2**60-1  
$ rand 1 10
9
$ rand -43543 -124
-15757
$ rand -3 3
1
$ for i in {0..9}; do rand $((2**60-1)); done
777148045699177620
456074454250332606
95080022501817128
993412753202315192
527158971491831964
336543936737015986
1034537273675883580
127413814010621078
758532158881427336
924637728863691573

Tutte le idee degli altri rispondenti erano fantastiche. Le risposte di terdon , JF Sebastian e jimmij hanno utilizzato strumenti esterni per svolgere il compito in modo semplice ed efficiente. Tuttavia, ho preferito una vera soluzione bash per la massima portabilità, e forse un po ', semplicemente per amore di bash;)

Le risposte di Ramesh e l0b0 utilizzate /dev/urandomo /dev/randomin combinazione con od. Va bene, tuttavia, i loro approcci hanno lo svantaggio di poter campionare interi casuali nell'intervallo da 0 a 2 8n -1 per alcuni n, poiché questo metodo campiona byte, ovvero stringhe di bit di lunghezza 8. Questi sono salti piuttosto grandi con crescente n.

Infine, la risposta di Falco descrive l'idea generale di come ciò possa essere fatto per intervalli arbitrari (non solo poteri di due). Fondamentalmente, per un dato intervallo {0..max}, possiamo determinare quale sia la potenza successiva di due, cioè esattamente quanti bit sono richiesti per rappresentare maxcome stringa di bit . Quindi possiamo campionare solo tanti bit e vedere se questo bistring, come numero intero, è maggiore di max. In tal caso, ripetere. Poiché campioniamo tutti i bit necessari per rappresentare max, ogni iterazione ha una probabilità maggiore o uguale al 50% del successo (50% nel peggiore dei casi, 100% nel migliore dei casi). Quindi questo è molto efficiente.

La mia sceneggiatura è fondamentalmente un'implementazione concreta della risposta di Falco, scritta in puro bash e altamente efficiente poiché utilizza le operazioni bit per bit incorporate di bash per campionare bittring della lunghezza desiderata. Onora inoltre un'idea di Eliah Kagan che suggerisce di usare la $RANDOMvariabile incorporata concatenando stringhe di bit risultanti da ripetute invocazioni di $RANDOM. In realtà ho implementato entrambe le possibilità di utilizzo /dev/urandome $RANDOM. Per impostazione predefinita, utilizza lo script sopra $RANDOM. (E ok, se si utilizza /dev/urandomabbiamo bisogno di od e tr , ma questi sono supportati da POSIX.)

Quindi, come funziona?

Prima di entrare in questo, due osservazioni:

  1. Risulta che bash non può gestire numeri interi maggiori di 2 63 -1. Vedi tu stesso:

    $ echo $((2**63-1))
    9223372036854775807
    $ echo $((2**63))
    -9223372036854775808

    Sembrerebbe che bash usi internamente numeri interi a 64 bit con segno per memorizzare numeri interi. Quindi, a 2 63 "si avvolge" e otteniamo un numero intero negativo. Quindi non possiamo sperare di ottenere un intervallo maggiore di 2 63 -1 con qualsiasi funzione casuale che utilizziamo. Bash semplicemente non può gestirlo.

  2. Ogni volta che vogliamo campionare un valore in un intervallo arbitrario tra mine maxcon possibilmente min != 0, possiamo semplicemente campionare un valore tra 0e max-mininvece e quindi aggiungere minal risultato finale. Funziona anche se mine forse anche maxè negativo , ma dobbiamo fare attenzione a campionare un valore compreso tra 0e il valore assoluto di max-min . Quindi, possiamo concentrarci su come campionare un valore casuale tra 0e un intero positivo arbitrario max. Il resto è facile.

Passaggio 1: determinare quanti bit sono necessari per rappresentare un numero intero (il logaritmo)

Quindi, per un dato valore max, vogliamo sapere solo quanti bit sono necessari per rappresentarlo come una stringa di bit. Questo è così che in seguito possiamo campionare casualmente solo tutti i bit necessari, il che rende lo script così efficiente.

Vediamo. Dato che con i nbit, possiamo rappresentare fino al valore 2 n -1, quindi il numero ndi bit necessari per rappresentare un valore arbitrario xè soffitto (log 2 (x + 1)). Quindi, abbiamo bisogno di una funzione per calcolare il massimale di un logaritmo alla base 2. È piuttosto autoesplicativo:

log2() {
  local x=$1 n=1 l=0
  while (( x>n && n>0 ))
  do
    let n*=2 l++
  done
  echo $l
}

Abbiamo bisogno della condizione, n>0quindi se diventa troppo grande, si avvolge e diventa negativo, il loop è garantito per terminare.

Passaggio 2: campionare una stringa di bit casuale di lunghezza n

Le idee più portatili sono o usare /dev/urandom(o anche /dev/randomse c'è una ragione forte) o la $RANDOMvariabile integrata di bash . Diamo un'occhiata a come farlo $RANDOMprima.

Opzione A: utilizzo $RANDOM

Questo utilizza l' idea menzionata da Eliah Kagan. Fondamentalmente, poiché $RANDOMcampiona un numero intero a 15 bit, possiamo usare $((RANDOM<<15|RANDOM))per campionare un numero intero a 30 bit. Ciò significa che sposta una prima invocazione di $RANDOM15 bit a sinistra e applica una bit a bit o con una seconda invocazione di $RANDOM, concatenando efficacemente due stringhe di bit campionate in modo indipendente (o almeno indipendenti come va incorporato in bash $RANDOM).

Possiamo ripetere questo per ottenere un numero intero a 45 o 60 bit. Dopo che bash non può più gestirlo, ma ciò significa che possiamo facilmente campionare un valore casuale tra 0 e 2 60 -1. Quindi, per campionare un numero intero n-bit, ripetiamo la procedura fino a quando la nostra stringa di bit casuale, la cui lunghezza cresce con incrementi di 15 bit, ha una lunghezza maggiore o uguale a n. Infine, tagliamo i bit che sono troppo spostando opportunamente bit a bit verso destra e finiamo con un intero casuale n-bit.

get_n_rand_bits() {
  local n=$1 rnd=$RANDOM rnd_bitlen=15
  while (( rnd_bitlen < n ))
  do
    rnd=$(( rnd<<15|$RANDOM ))
    let rnd_bitlen+=15
  done
  echo $(( rnd>>(rnd_bitlen-n) ))
}

Opzione B: utilizzo /dev/urandom

In alternativa, possiamo usare ode /dev/urandomcampionare un numero intero n-bit. odleggerà byte, ovvero stringhe di bit di lunghezza 8. Analogamente al metodo precedente, campioniamo così tanti byte che il numero equivalente di bit campionati è maggiore o uguale a n e tagliamo i bit che sono troppo.

Il numero più basso di byte necessario per ottenere almeno n bit è il multiplo più basso di 8 che è maggiore o uguale a n, ovvero floor ((n + 7) / 8).

Funziona solo con numeri interi a 56 bit. Il campionamento di un altro byte ci darebbe un numero intero a 64 bit, ovvero un valore fino a 2 64 -1, che bash non può gestire.

get_n_rand_bits_alt() {
  local n=$1
  local nb_bytes=$(( (n+7)/8 ))
  local rnd=$(od --read-bytes=$nb_bytes --address-radix=n --format=uL /dev/urandom | tr --delete " ")
  echo $(( rnd>>(nb_bytes*8-n) ))
}

Mettere insieme i pezzi: ottenere interi casuali in intervalli arbitrari

Ora possiamo campionare nbit-string a bit, ma vogliamo campionare numeri interi in un intervallo da 0a max, uniformemente a caso , dove maxpuò essere arbitrario, non necessariamente una potenza di due. (Non possiamo usare il modulo in quanto crea un pregiudizio.)

Il motivo per cui abbiamo provato così tanto a campionare tutti i bit necessari per rappresentare il valore max, è che ora possiamo usare in modo sicuro (ed efficiente) un loop per campionare ripetutamente una nstringa di bit a bit fino a quando campioniamo un valore più basso o uguale a max. Nel peggiore dei casi ( maxè una potenza di due), ogni iterazione termina con una probabilità del 50% e, nel migliore dei casi ( maxè una potenza di due meno uno), la prima iterazione termina con certezza.

rand() {
  local rnd max=$1
  # get number of bits needed to represent $max
  local bitlen=$(log2 $((max+1)))
  while
    # could use get_n_rand_bits_alt instead if /dev/urandom is preferred over $RANDOM
    rnd=$(get_n_rand_bits $bitlen)
    (( rnd > max ))
  do :
  done
  echo $rnd
}

Avvolgere le cose

Infine, vogliamo campionare numeri interi tra mine max, dove mine maxpuò essere arbitrario, anche negativo. Come accennato in precedenza, questo è ora banale.

Mettiamo tutto in uno script bash. Esegui un po 'di argomenti per analizzare le cose ... Vogliamo due argomenti mine max, o solo un argomento max, minper impostazione predefinita 0.

# check number of parameters
if (( $# != 1 && $# != 2 ))
then
  cat <<EOF 1>&2
Usage: $(basename $0) [min] max

Returns an integer distributed uniformly at random in the range {min..max}
min defaults to 0
(max - min) can be up to 2**60-1  
EOF
  exit 1
fi

# If we have one parameter, set min to 0 and max to $1
# If we have two parameters, set min to $1 and max to $2
max=0
while (( $# > 0 ))
do
  min=$max
  max=$1
  shift
done

# ensure that min <= max
if (( min > max ))
then
  echo "$(basename $0): error: min is greater than max" 1>&2
  exit 1
fi

... e, infine, per campionare uniformemente a caso un valore compreso tra mine max, campioniamo un numero intero casuale compreso tra 0il valore assoluto di max-mine aggiungiamo minal risultato finale. :-)

diff=$((max-min)) && diff=${diff#-}

echo $(( $(rand $diff) + min ))

Ispirato da questo , potrei provare a usare dieharder per testare e confrontare questo PRNG, e mettere qui le mie scoperte. :-)


la tua soluzione presuppone che sizeof(int) == 8(64 bit) a causa di--format=u
jfs il

1
la tua soluzione mi ricorda come è scritto random.py. random.Randomla classe usa 53 bit? generatore per restituire grandi numeri casuali arbitrari (invocazioni multiple), random.SystemRandomfa lo stesso usando os.urandom()che può essere implementato usando /dev/urandom.
jfs,

uL implica sizeof (long)> = 8 per l'intervallo. Non è garantito Potresti usare u8 per affermare che la piattaforma ha tale numero intero.
jfs,

@JFSebastian Stavo pensando che fino ad ora il mio script non ha codificato alcun presupposto sulla dimensione di un int lungo. Potenzialmente, funzionerebbe anche se la dimensione di un int con segno lungo fosse maggiore (o inferiore) di 64 bit, ad esempio 128 bit. Tuttavia, se uso, --format=u8codifico il presupposto sizeof(int)==8. D'altra parte, se uso --format=uLnon c'è nessun problema: non penso che ci sia una piattaforma che ha numeri interi a 64 bit ma definisce ancora i long in come qualcosa di più basso. Quindi, in sostanza, direi che --format=uLconsente una maggiore flessibilità. Quali sono i tuoi pensieri?
Malte Skoruppa,

c'è long longche può essere 64 bit mentre int = long = 32 bit su alcune piattaforme. Non dovresti rivendicare un range 0..2 ** 60 se non puoi garantirlo su tutte le piattaforme. D'altra parte bash potrebbe non supportare questo intervallo stesso su tali piattaforme (non lo so, forse utilizza maxint_t e quindi u8 è più corretto se si desidera affermare l'intervallo fisso ( odnon supporta specificare maxint se il proprio intervallo è qualunque sia la gamma di bash dipendente dalla piattaforma?) Se la gamma di bash dipende dalla dimensione di long allora uL potrebbe essere più appropriato). Vuoi l'intera gamma supportata da bash su tutti i sistemi operativi o un intervallo fisso?
jfs,

6

Può essere zsh?

max=1000
integer rnd=$(( $(( rand48() )) * $max ))

Potresti voler usare anche seed con rand48(seed). Vedi man zshmodulese man 3 erand48per una descrizione dettagliata se interessati.


Personalmente non uso zsh, ma questa è un'ottima aggiunta :)
Malte Skoruppa

5
$ python -c 'import random as R; print(R.randint(-3, 5**1234))'

python è disponibile su Redhat, sistemi basati su Debian.


+1 Ah, insieme alla soluzione perl ci doveva essere solo la soluzione Python. Grazie :)
Malte Skoruppa,

5

Se vuoi un numero compreso tra 0 e (2 ^ n) -1 in cui n mod 8 = 0 puoi semplicemente ottenere n / 8 byte da /dev/random. Ad esempio, per ottenere la rappresentazione decimale di un casuale intè possibile:

od --read-bytes=4 --address-radix=n --format=u4 /dev/random | awk '{print $1}'

Se si desidera prendere solo n bit, è possibile innanzitutto prendere i byte del soffitto (n / 8) e spostarsi a destra nella quantità desiderata. Ad esempio, se si desidera 15 bit:

echo $(($(od --read-bytes=2 --address-radix=n --format=u4 /dev/random | awk '{print $1}') >> 1))

Se sei assolutamente sicuro di non preoccuparti della qualità della casualità e desideri garantire un tempo di esecuzione minimo che puoi utilizzare al /dev/urandomposto di /dev/random. Assicurati di sapere cosa stai facendo prima di utilizzare /dev/urandom!


Grazie. Quindi, ottenere nbyte casuali da /dev/urandome formattare utilizzando od. Simile nello spirito come questa risposta . Entrambi sono ugualmente buoni :) Sebbene entrambi abbiano lo svantaggio di avere un intervallo fisso compreso tra 0 e 2 ^ (n * 8) -1 bit, dove n è il numero di byte. Io preferirei un metodo per un arbitrario gamma, fino a 2 ^ 32-1, ma anche qualcosa di abbassare. Questo crea difficoltà di bias.
Malte Skoruppa,

Modificato per l'uso /dev/urandomanziché /dev/random- Non vedo alcun motivo per l'uso /dev/randome può essere molto costoso / lento o rallentare altre parti del sistema. (Sentiti libero modifica indietro e spiega se è davvero necessario.)
Volker Siegel,

Dovrebbe essere esattamente l'opposto: usa / dev / urandom a meno che tu non sappia di aver bisogno di / dev / random . Non è corretto supporre che i /dev/urandomrisultati siano molto peggiori di quelli /dev/randomche l'urandom non è utilizzabile nella maggior parte dei casi. Una volta /dev/urandominizializzato (all'inizio del sistema); i suoi risultati sono buoni come /dev/randomper quasi tutte le applicazioni su Linux. Su alcuni sistemi random e urandom sono uguali.
jfs,

1
--format=udovrebbe essere sostituito con --format=u4perché sizeof(int)potrebbe essere inferiore a quello 4teorico.
jfs,

@JFSebastian Questo articolo ha una discussione molto interessante su questo argomento. La loro conclusione sembra essere che sia /dev/randome /dev/urandomnon sono soddisfacenti, e che "Linux dovrebbe aggiungere un RNG sicuro che blocca fino a quando non ha raccolto adeguata entropia seme e si comporta da allora in poi come urandom".
l0b0,

3

Supponendo che non ti opponga all'utilizzo di strumenti esterni, questo dovrebbe soddisfare i tuoi requisiti:

rand=$(perl -e 'print int(rand(2**32-1))'); 

Sta usando la randfunzione di perl che accetta un limite superiore come parametro. Puoi impostarlo come preferisci. Quanto questo sia vicino alla vera casualità nella definizione matematica astratta va oltre lo scopo di questo sito, ma dovrebbe andare bene a meno che non sia necessario per una crittografia estremamente sensibile o simili. Forse anche lì, ma non mi avventurerò un'opinione.


questo si interrompe per grandi numeri , ad es. 5 ** 1234
jfs

1
@JFSebastian sì. L'ho pubblicato da quando è stato specificato l'OP, 1^32-1ma è necessario modificarlo per numeri più grandi.
terdon

2

Dovresti ottenere il più vicino (2 ^ X) -1 uguale o maggiore del massimo desiderato e ottenere il numero di bit. Quindi chiama / dev / random più volte e aggiungi tutti i bit insieme fino a quando non ne hai abbastanza, troncando tutti i bit che sono troppo. Se il numero risultante è maggiore della tua ripetizione massima. Nel caso peggiore hai una probabilità superiore al 50% di ottenere un numero casuale inferiore al tuo massimo, quindi (per questo caso peggiore) risponderai a due chiamate in media.


Questa è in realtà una buona idea per migliorare l'efficienza. La risposta di Ramesh e la risposta di l0b0 ottengono entrambe sostanzialmente da bit casuali /dev/urandom, ma in entrambe le risposte è sempre un multiplo di 8 bit. Troncare i bit che sono troppo per intervalli inferiori prima della formattazione in decimale odè una buona idea per migliorare l'efficienza, poiché il ciclo ha solo un numero previsto di 2 iterazioni, come ben spiegato. Questo, combinato con una delle risposte menzionate, è probabilmente la strada da percorrere.
Malte Skoruppa,

0

La tua risposta è interessante ma piuttosto lunga.

Se vuoi numeri arbitrariamente grandi, puoi unire più numeri casuali in un aiutante:

# $1 - number of 'digits' of size base
function random_helper()
{
  base=32768
  random=0
  for((i=0; i<$1; ++i)); do
    let "random+=$RANDOM*($base**$i)"
  done
  echo $random
}

Se il problema è distorto, rimuoverlo.

# $1 - min value wanted
# $2 - max value wanted
function random()
{
  MAX=32767
  min=$1
  max=$(($2+1))
  size=$((max-min))
  bias_range=$((MAX/size))
  while
    random=$RANDOM
  [ $((random/size)) -eq $bias_range ]; do :; done
  echo $((random%size+min))
}

Unendo queste funzioni insieme

# $1 - min value wanted
# $2 - max value wanted
# $3 - number of 'digits' of size base
function random()
{
  base=32768
  MAX=$((base**$3-1))
  min=$1
  max=$(($2+1))
  size=$((max-min))
  bias_range=$((MAX/size))
  while
    random=$(random_helper)
  [ $((random/size)) -eq $bias_range ]; do :; done
  echo $((random%size+min))
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.