Bash: Come generare un numero float casuale utilizzando $ RANDOM

È possibile generare numeri casuali reali con una precisione specifica e in un intervallo specifico utilizzando Integer Random Generatore $ RANDOM? Ad esempio, come possiamo generare un numero reale con una precisione 4 compresa tra 0 e 1?

0.1234 0.0309 0.9001 0.0000 1.0000 

Una semplice soluzione:

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

Commenti

  • Specifica cosa intendi per ” numeri casuali reali “. Stai richiedendo una fonte di numeri casuali generati da qualcosa come il decadimento delle particelle o sarai soddisfatto di un generatore pseudo-casuale? La tua applicazione di questi numeri ha un significato crittografico o scientifico o desideri semplicemente qualcosa che ” sembri casuale “.
  • … o intendi effettivamente ” float ” o ” floating numero punto?
  • Grazie per il commento. Ho bisogno di un generatore di numeri pseudo casuali per numeri in virgola mobile basati su $ RANDOM.
  • … per implementare algoritmi meta euristici in bash.

Risposta

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

Questo produrrà n numeri casuali (dieci nellesempio) nellintervallo [0,1) con quattro cifre decimali. Utilizza la funzione rand() in awk (non nello standard awk ma implementata dai più comuni awk implementazioni) che restituisce un valore casuale in quellintervallo. Il generatore di numeri casuali è seminato dalla variabile $RANDOM della shell.

Quando un programma awk ha solo BEGIN (e nessun altro blocco di codice), awk non tenterà di leggere linput dal suo flusso di input standard.

Su qualsiasi sistema OpenBSD (o sistema che ha la stessa jot utility , originariamente in 4.2BSD), quanto segue genererà 10 numeri casuali come specificato:

jot -p 4 -r 10 0 1 

Commenti

  • In senso stretto, poiché loutput di rand() è un float allinterno di [0,1), probabilmente non è ‘ t distribuito esattamente in modo uniforme quando arrotondato a quattro cifre decimali. Lo sarebbe, se il float fosse di precisione infinita, ma non è ‘ t: è probabile che ‘ sia generato da bit casuali , quindi ci sono 2 ^ N valori diversi, e non ‘ t mappano uniformemente su un insieme di 1000 valori. Ma fino a quando quei float pseudo-casuali hanno abbastanza bit e ‘ non stai facendo nulla di veramente preciso, probabilmente hai vinto ‘ t avviso.

Risposta

Come sottolineato in unaltra risposta, ci sono altre utilità che puoi utilizzare per generare numeri casuali. In questa risposta, limito le mie risorse a $RANDOM e ad alcune funzioni aritmetiche di base.

Per i numeri in virgola mobile, prova qualcosa come

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

In questo modo otterrai la migliore precisione perché $RANDOM genera solo numeri compresi tra 0 e 32767. (incluso 32767!) Ma io ” ho anche infranto la mia regola sulluso delle funzioni aritmetiche di base invocando bc.

Ma prima di andare avanti, vorrei esaminare due problemi precisione e intervallo per i numeri in virgola mobile. Dopodiché, esaminerò la generazione di un intervallo di numeri interi (e se puoi generare numeri interi, puoi dividerli in seguito per ottenere un decimale se desideri utilizzare le utilità che preferisci per farlo.)

Precisione

Adottando lapproccio di $RANDOM/32768, da $RANDOM genera valori da 0 a 32767, il risultato di $RANDOM/32768 sarà similmente un numero finito di valori. In altre parole, è ancora una variabile casuale discreta (e con un computer non sarai mai in grado di sfuggire a questo fatto). Con questo in mente, puoi ottenere un certo grado di precisione utilizzando printf.

Se desideri una copertura più fine del intervallo, potresti iniziare a pensare in base 32768. Quindi, in teoria $RANDOM + $RANDOM*32768 dovrebbe darti una distribuzione uniforme tra 0 e 1.073.741.823. Ma, dubito che la riga di comando gestirà molto bene questa precisione. Un paio di punti relativi a questo caso particolare:

  • La somma di due variabili casuali indipendenti e distribuite uniformemente non è generalmente uniforme. In questo caso, almeno in teoria (vedi terzo punto), lo sono.
  • Non pensare di poter semplificare $RANDOM + $RANDOM*32768 = $RANDOM * ( 1 + 32768 ).Le due occorrenze di $RANDOM sono in realtà due eventi diversi.
  • Non ne so abbastanza di come sia $RANDOM generato per sapere se chiamarlo due volte in questo modo genererà veramente due eventi casuali indipendenti.

Intervallo

Consideriamo solo $RANDOM/32768. Se desideri un numero in un intervallo, dì [a,b), quindi

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

ti porterà nellintervallo desiderato .

Generazione di valori interi

Innanzitutto, considera la possibilità di generare numeri casuali tra [0,b) dove b è minore di 32768. Considera il prodotto q*b, dove q è la parte intera di 32768/b. Quindi quello che puoi fare è generare un numero casuale compreso tra 0 e 32767, ma eliminare quelli che sono maggiori o uguali a q*b. Chiama il numero così generato G. Quindi G ricadrà nellintervallo da 0 a q*b e la sua distribuzione sarà uniforme. Ora, applica laritmetica modulare per ottenere questo valore ridotto nellintervallo desiderato:

G % b 

Nota, generando casualmente un numero come segue

$RANDOM % b 

non creerà una distribuzione uniforme, a meno che b non sia uno dei divisori di 32768.

Scrittura di uno script bash per questo

Calcolo q*b come descritto sopra suona come un dolore. Ma in realtà non è “t. Puoi ottenerlo come segue:

q*b = 32768 - ( 32768 % b ) 

In Bash, puoi ottenerlo con

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

Il seguente codice genererà un numero casuale nellintervallo 0..b (non comprensivo di b) . b=$1

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

Addendum

Tecnicamente, ci sono pochi motivi per lavorare con

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

Quanto segue porterà a termine la stessa cosa

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

È “molto più faticoso, ma i computer sono veloci.

Generazione di un numero intero in un intervallo più ampio

Ti lascio capire. È necessario prestare attenzione e ad un certo punto dovrai prendere in considerazione i limiti di memoria del computer nella gestione delle operazioni aritmetiche.

Nota finale

La risposta accettata non creerà un numero casuale uniformemente compreso tra 0 e 1.

Per vedere questo, prova quanto segue

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

Per una distribuzione veramente uniforme su [0,1) dovresti vedere una media vicina a 0.500.

Ma come puoi vedere eseguendo lo snippet sopra, otterrai invece qualcosa come 314.432 o 322.619. Poiché sono 1000 numeri, la media è .322. La media reale per questa sequenza di numeri generati è .316362

Puoi ottenere questa media reale utilizzando lo script perl

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

Sto aggiungendo degli interi qui per aiutarti a vedere come questo approccio di usare .$RANDOM non sta facendo quello che molto probabilmente vuoi che faccia. In altre parole, pensa a quali numeri interi vengono generati e quali vengono persi del tutto. Un numero abbastanza elevato viene ignorato; alcuni sono raddoppiati.

Risposta

Su sistemi in cui printf di shell è in grado di capire il %a (bash ksh zsh, ecc.) e quindi è in grado di eseguire un cambio di base interno (hex -> dec) (uniforme in [0,1) range da 0,00003 a 0.99997):

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

potresti persino utilizzare più cifre combinando più chiamate a $RANDOM (da 0.000000001 a 0.999999999)

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

Lalgoritmo “$ RANDOM” interno (alla shell) si basa su un registro a scorrimento a feedback lineare (LFSR). Non sono crittograficamente Generatori di numeri pseudo casuali sicuri (CSPRNG). Unopzione migliore è utilizzare i byte dal dispositivo /dev/urandom. Ciò richiederà la chiamata a ottale esterno o dump esadecimale.

$ 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 

Una soluzione molto semplice (ma non uniforme) per ottenere un float è:

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

Un modo per renderlo uniforme nellintervallo [0,1) (escluso 1):

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

Risposta

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

Sostituisci N con il numero MAX e MIN con il numero minimo che desideri generare. (N poiché MAX è esclusivo, metti N+1 per avere entrambi MAX e MIN inclusi).

Oppure puoi utilizzare $(shuf -i MIN-MAX -n 1).

da 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 

Il -n 1 in shuf qui significa generare solo un numero casuale.

Questo genererà numeri casuali compresi tra 0 ~ 9999 con zeri iniziali utilizzando printf (nel risultato, numero 1

è esclusivo).

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

Commenti

  • Questo inoltre non produrrà un vero numero casuale nellintervallo dato tranne nel caso in cui N è un divisore di 32767 (il limite superiore di $ RANDOM).

Risposta

On bash

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

dove 1/10000 è il tuo casuale precisione e 4 cifre la precisione delloutput

Risposta

zsh ha una rand48() funzione aritmetica (wrapper della erand48() funzione standard) nella sua zsh/mathfunc modulo:

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

Mentre $RANDOM è di 15 bit, pseudo-casuale e riproducibile, bash 5.1+ ha un numero intero a 32 bit più sicuro $SRANDOM, basato su fonti veramente casuali, ove disponibili. Non supporta laritmetica in virgola mobile, ma almeno puoi usarlo per seminare lo pseudo generatore di awk “(che altrimenti per impostazione predefinita usa il risultato molto prevedibile di time()):

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

(tieni presente che sono ancora solo 32 bit di entropia e awk genera una generazione pseudo-casuale deterministica basata su quel seme)

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *