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.