Bash: Como gerar um número aleatório de float usando $ RANDOM

4

É possível gerar números aleatórios reais com uma precisão específica e em um intervalo específico usando o Gerador aleatório inteiro $ RANDOM? Por exemplo, como podemos gerar um número real com precisão entre 0 e 1?

0.1234
0.0309
0.9001
0.0000
1.0000

Uma solução simples:

printf "%d04.%d04\n" $RANDOM $RANDOM
    
por sci9 17.02.2018 / 04:59

5 respostas

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

Isso gerará n números aleatórios (dez no exemplo) no intervalo [0,1) com quatro dígitos decimais. Ele usa a função rand() em awk (não no padrão awk , mas implementada pelas implementações mais comuns de awk ), que retorna um valor aleatório nesse intervalo. O gerador de números aleatórios é propagado pela variável $RANDOM do shell.

Quando um programa awk tiver apenas BEGIN blocos (e nenhum outro bloco de código), awk não tentará ler a entrada de seu fluxo de entrada padrão.

Em qualquer sistema OpenBSD (ou sistema que tenha o mesmo jot utility , originalmente em 4.2BSD), o seguinte gerar 10 números aleatórios conforme especificado:

jot -p 4 -r 10 0 1
    
por 17.02.2018 / 11:51
5

Em sistemas onde o printf do shell é capaz de entender o formato %a (bash ksh zsh, etc.) e, portanto, é capaz de realizar uma mudança interna de base (hex - > dec) (uniforme em [0,1) 0,00003 a 0,99997):

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

Você pode até usar mais dígitos combinando mais chamadas para $RANDOM (de 0,000000001 a 0,9999999999)

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

O algoritmo "$ RANDOM" interno (para o shell) é baseado em um registrador de deslocamento de feedback linear (LFSR). Esses não são Geradores de Números Pseudo-Aleatórios Criptograficamente Seguros (CSPRNGs). Uma opção melhor é usar bytes do dispositivo /dev/urandom . Isso exigirá a chamada para octal externo ou dump hexadecimal.

$ 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

Uma solução muito simples (mas não uniforme) para obter um float é:

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

Uma maneira de torná-lo uniforme no intervalo [0,1) (não incluindo 1):

while a=$RANDOM; ((a>29999)); do :; done; printf '0.%04d\n' "$((a%10000))"
    
por 04.08.2018 / 10:47
4

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

Substitua N pelo número MAX e MIN pelo número mínimo que você deseja gerar. ( N como MAX é exclusivo, coloque N+1 para ter MAX, MIN inclusive).

Ou você pode usar $(shuf -i MIN-MAX -n 1) .

de 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

O -n 1 em shuf aqui significa gerar apenas um número aleatório.

Isso gerará números aleatórios entre 0 ~ 9999 com zeros à esquerda usando printf (em resultado, número 1 é exclusivo).

printf "0.%04d\n" $(( RANDOM % 1000 ))
0.0215
    
por 17.02.2018 / 05:05
4

Como apontado em outra resposta, existem outros utilitários que você pode usar para gerar números aleatórios. Nessa resposta, limito meus recursos a $RANDOM e algumas funções aritméticas básicas.

Para números de ponto flutuante, tente algo como

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

Isso vai te dar a melhor precisão porque $RANDOM só gera números entre 0 e 32767. (incluindo 32767!) Mas, eu também quebrei minha regra sobre o uso de funções aritméticas básicas invocando bc .

Mas antes de prosseguir, gostaria de analisar duas questões precisão e intervalo para números de ponto flutuante. Depois disso, eu vou olhar para gerar um intervalo de inteiros (e se você pode gerar inteiros, você pode depois dividi-los para obter um decimal, se você quiser usar quaisquer utilitários que você preferir para realizar isso.)

Precisão

Tomando a abordagem de $RANDOM/32768 , uma vez que $RANDOM gera valores de 0 a 32767, o resultado de $RANDOM/32768 também será finitamente muitos valores. Em outras palavras, ainda é uma variável aleatória discreta (e com um computador você nunca conseguirá se afastar desse fato). Com isso em mente, você pode obter algum grau de precisão usando printf .

Se você quer uma cobertura mais fina do intervalo, você poderia começar a pensar na base 32768. Então, teoricamente, $RANDOM + $RANDOM*32768 deve fornecer uma distribuição uniforme entre 0 e 1.073.741.823. Mas tenho dúvidas de que a linha de comando irá lidar com essa precisão muito bem. Alguns pontos relacionados a este caso particular:

  • A soma de duas variáveis aleatórias independentes, uniformemente distribuídas, geralmente não uniformes. Neste caso, pelo menos teoricamente falando (ver terceiro ponto), eles são.
  • Não pense que você pode simplificar $RANDOM + $RANDOM*32768 = $RANDOM * ( 1 + 32768 ) . As duas ocorrências de $RANDOM são realmente dois eventos diferentes.
  • Eu não sei o suficiente sobre como $RANDOM é gerado para saber se chamá-lo duas vezes como isto irá realmente gerar dois eventos aleatórios independentes.

Intervalo

Vamos considerar apenas $RANDOM/32768 . Se você quiser um número em um intervalo, digamos [a,b) , então

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

vai pousar na faixa desejada.

Geração de valores inteiros

Primeiro, considere gerar números aleatórios entre [0,b) , em que b é menor que 32768 . Considere o produto q*b , em que q é a parte inteira de 32768/b . Então, o que você pode fazer é gerar um número aleatório entre 0 e 32767, mas descartar aqueles que são maiores ou iguais a q*b . Ligue para o número assim gerado G . Então G cairá no intervalo de 0 a q*b e sua distribuição será uniforme. Agora, aplique aritmética modular para obter esse valor reduzido no intervalo desejado:

G % b

Note, gerando aleatoriamente um número da seguinte forma

$RANDOM % b

não criará uma distribuição uniforme, a menos que b simplesmente seja um dos divisores de 32768 .

Escrevendo um script bash para isso

Calcular q*b como descrito acima parece uma dor. Mas realmente não é. Você pode obtê-lo da seguinte maneira:

q*b = 32768 - ( 32768 % b )

No Bash, você pode conseguir isso com

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

O código a seguir gerará um número aleatório no intervalo 0..b (não incluindo b ). b=$1

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

Adendo

Tecnicamente, há poucos motivos para trabalhar com

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

O seguinte irá realizar a mesma coisa

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

É muito mais trabalho, mas os computadores são rápidos.

Gerando um Integer em um intervalo maior

Eu vou deixar você descobrir isso. Cuidados devem ser tomados, e em algum momento você terá que levar em consideração as limitações de memória do computador no manuseio de operações aritméticas.

Nota final

A resposta aceita não criará um número aleatório uniformemente entre 0 e 1.

Para ver isso, tente o seguinte

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

Para uma distribuição verdadeiramente uniforme sobre [0,1) , você deve ver uma média próxima a 0.500 .

Mas, como você pode ver, executando o snippet acima, você receberá algo como 314.432 ou 322.619 . Como são 1000 números, a média disso é .322 . A média real para essa sequência de números gerados é .316362

Você pode obter essa média real usando o 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; 
           }' 

Estou adicionando inteiros aqui para ajudá-lo a ver como essa abordagem de usar .$RANDOM não está fazendo o que você mais deseja. Em outras palavras, pense em quais inteiros estão sendo gerados e quais são perdidos por completo. Um grande número é ignorado; alguns são duplicados.

    
por 04.08.2018 / 08:49
0

zsh tem uma função aritmética rand48() (wrapper para a função erand48() standard) em seu módulo zsh/mathfunc :

zmodload zsh/mathfunc
printf '%.4f\n' $((rand48()))
    
por 05.08.2018 / 09:51