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

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

0.1234 0.0309 0.9001 0.0000 1.0000 

Uma solução simples:

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

Comentários

  • Especifique o que você quer dizer com ” números aleatórios reais “. Você está exigindo uma fonte de números aleatórios gerados por algo como o decaimento de partículas ou ficará feliz com um gerador pseudo-aleatório? A sua aplicação desses números tem significado criptográfico ou científico ou você apenas quer algo que ” pareça aleatório “.
  • … ou você realmente quer dizer ” float ” ou ” flutuante número do ponto?
  • Obrigado por comentar. Preciso de um gerador de números pseudoaleatórios para números de ponto flutuante com base em $ RANDOM.
  • … para implementar algoritmos meta heurísticos em bash.

Resposta

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

Isso produzirá 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 implementado pelos mais comuns awk implementações) que retorna um valor aleatório nesse intervalo. O gerador de números aleatórios é propagado pela variável shell “s $RANDOM.

Quando um programa awk tem 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 utilitário , originalmente em 4.2BSD), o seguinte irá gerar 10 números aleatórios conforme especificado:

jot -p 4 -r 10 0 1 

Comentários

  • Estritamente falando, desde a saída de rand() é um float dentro de [0,1), provavelmente não é ‘ t exatamente distribuído uniformemente quando arredondado para quatro dígitos decimais. Seria, se o flutuante tivesse precisão infinita, mas não é ‘ t: é ‘ provável que seja gerado a partir de bits aleatórios , portanto, há 2 ^ N valores diferentes e eles não ‘ t mapeiam uniformemente para um conjunto de 1000 valores. Mas, desde que essas flutuações pseudoaleatórias tenham bits suficientes e você ‘ não estiver fazendo nada realmente exato, você provavelmente ganhou ‘ t aviso.

Resposta

Como apontado em outra resposta, existem outros utilitários que você pode usar para gerar Números aleatórios. Nesta resposta, eu limitei 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 lhe dará a melhor precisão porque $RANDOM só gera números entre 0 e 32.767. (incluindo 32.767!) 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 examinar duas questões precisão e intervalo para números de ponto flutuante. Depois disso, examinarei a geração de um intervalo de inteiros (e se você pode gerar inteiros, poderá dividi-los posteriormente para obter um decimal se desejar usar os utilitários de sua preferência para fazer isso.)

Precisão

Tomando a abordagem de $RANDOM/32768, desde $RANDOM gera valores de 0 a 32767, o resultado de $RANDOM/32768 da mesma forma terá muitos valores finitos. Em outras palavras, ainda é uma variável aleatória discreta (e com um computador você nunca conseguirá fugir desse fato). Com isso em mente, você pode obter algum grau de precisão usando printf.

Se você quiser uma cobertura mais fina do intervalo, você poderia começar a pensar na base 32768. Portanto, em teoria $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 lidará com essa precisão muito bem. Alguns pontos relacionados a este caso particular:

  • A soma de duas variáveis aleatórias independentes e uniformemente distribuídas não é geralmente uniforme. Neste caso, pelo menos teoricamente falando (veja o 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.
  • Não sei o suficiente sobre como $RANDOM é gerado para saber se chamá-lo duas vezes desta forma realmente gerará dois eventos aleatórios independentes.

Intervalo

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

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

o levará ao intervalo desejado .

Geração de valores inteiros

Primeiro, considere gerar números aleatórios entre [0,b) onde b é menor que 32768. Considere o produto q*b, onde q é a parte inteira de 32768/b. Então o que você pode fazer é gerar um número aleatório entre 0 e 32.767, mas descartar aqueles que são maiores ou iguais a q*b. Ligue para o número gerado assim 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 

Observe, gerando aleatoriamente um número como segue

$RANDOM % b 

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

Escrevendo um script bash para este

Calculando q*b conforme descrito acima, parece uma dor de cabeça. Mas realmente não é. Você pode obtê-lo da seguinte maneira:

q*b = 32768 - ( 32768 % b ) 

No Bash, você pode obter isso com

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

O código a seguir irá gerar um número aleatório no intervalo 0..b (não inclui 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 resultará na mesma coisa

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

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

Gerando um número inteiro em um intervalo maior

Vou deixar você descobrir isso. É preciso ter cuidado e, em algum momento, você “terá que levar em consideração as limitações de memória do computador ao lidar com operações aritméticas.

Nota final

A resposta aceita não criará um número aleatório uniformemente acima de 0 a 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 em [0,1), você deve ver uma média próxima a 0.500.

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

Você pode obter essa média verdadeira 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ê provavelmente deseja. Em outras palavras, pense em quais números inteiros estão sendo gerados e quais foram perdidos. Um grande número é ignorado; alguns são duplicados.

Resposta

Em sistemas em que shell “s printf é capaz de entender o (bash ksh zsh, etc.) e, portanto, é capaz de realizar uma mudança de base interna (hex -> dec) (uniforme no intervalo [0,1) de 0,00003 para 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,999999999)

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

O algoritmo interno (para o shell) “$ RANDOM” é baseado em um registro de deslocamento de feedback linear (LFSR). Eles não são criptograficamente Geradores de números pseudo-aleatórios seguros (CSPRNGs). Uma opção melhor é usar bytes do dispositivo /dev/urandom. Isso exigirá a chamada para dump octal ou hexadecimal externo.

$ 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))" 

Resposta

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

Substitua N com o número MAX e MIN com o 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) em vez disso.

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 irá gerar números aleatórios entre 0 ~ 9999 com zeros à esquerda usando printf (no resultado, número 1

é exclusivo).

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

Comentários

  • Este também não produzirá um número aleatório verdadeiro no intervalo dado, exceto no caso em que N é um divisor de 32767 (o limite superior de $ RANDOM).

Resposta

No bash

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

onde 1/10000 é o seu aleatório precisão e 4 dígitos sua precisão de saída

Resposta

zsh tem uma rand48() função aritmética (wrapper para a erand48() função padrão) em sua :

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

Enquanto $RANDOM tem 15 bits, é pseudo-aleatório e reproduzível, bash 5.1+ tem um número inteiro de 32 bits mais seguro $SRANDOM, baseado em fontes verdadeiramente aleatórias quando disponíveis. Ele não suporta aritmética de ponto flutuante, mas pelo menos você pode usá-lo para propagar o awk “gerador pseudoaleatório (que de outra forma, por padrão, usa o resultado muito previsível de time()):

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

(tenha em mente que ainda é apenas 32 bits de entropia e awk faz geração pseudo-aleatória determinística com base nessa semente)

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *