Bash: How to generate random float number using $RANDOM
awk -v n=10 -v seed="$RANDOM" 'BEGIN { srand(seed); for (i=0; i<n; ++i) printf("%.4f\n", rand()) }'
This will output n
random numbers (ten in the example) in the range [0,1) with four decimal digits. It uses the rand()
function in awk
(not in standard awk
but implemented by most common awk
implementations) which returns a random value in that range. The random number generator is seeded by the shell's $RANDOM
variable.
When an awk
program only has BEGIN
blocks (and no other code blocks), awk
will not try to read input from its standard input stream.
On any OpenBSD system (or system that has the same jot
utility, originally in 4.2BSD), the following will generate 10 random number as specified:
jot -p 4 -r 10 0 1
As pointed out in another answer, there are other utilities that you can use to generate random numbers. In this answer, I limit my resources to $RANDOM
, and a few basic arithmetic functions.
For floating point numbers, try something like
printf '%s\n' $(echo "scale=8; $RANDOM/32768" | bc )
That will get you the best precision because $RANDOM
only generates numbers between 0 and 32767. (including 32767!) But, I've also broken my rule about using basic arithmetic functions by invoking bc
.
But before moving on, I'd like to look at two issues precision and range for floating point numbers. After that, I will look at generating a range of integers (and if you can generate integers, you can later divide them to get a decimal if you wish using whatever utilities you prefer to accomplish that.)
Precision
Taking the approach of $RANDOM/32768
, since $RANDOM
generates values from 0 to 32767, the result of $RANDOM/32768
will likewise be finitely many values. In other words, it's still a discrete random variable (and with a computer you'll never be able to get away from that fact). With that in mind, then you can accomplish some degree of precision by using printf
.
If you want a finer covering of the interval, you could start thinking in base 32768. So, in theory $RANDOM + $RANDOM*32768
should give you a uniform distribution between 0 and 1,073,741,823. But, I'm doubtful the command line will handle that precision very well. A couple points relating to this particular case:
- The sum of two independent, uniformly distributed random variables in not generally uniform. In this case, at least theoretically speaking (see third point), they are.
- Don't think you can simplify
$RANDOM + $RANDOM*32768 = $RANDOM * ( 1 + 32768 )
. The two occurrences of$RANDOM
are really two different events. - I don't know enough about how
$RANDOM
is generated to know whether calling it twice like this will truly generate two independent random events.
Range
Let's consider just $RANDOM/32768
. If you want a number in a range, say [a,b)
, then
$RANDOM/32768*(b-a) + a
will land you in the desired range.
Generation of integer values
First, consider generating random numbers between [0,b)
where b
is less than 32768
. Consider the product q*b
, where q
is the integer part of 32768/b
. Then what you can do is generate random number between 0 and 32767, but throw out those which are greater than or equal to q*b
. Call the number thus generated G
. Then G
will fall in the range of 0 to q*b
, and its distribution will be uniform. Now, apply modular arithmetic to get this value whittled down into the desired range:
G % b
Note, randomly generating a number as follows
$RANDOM % b
will not create a uniform distribution, unless b
just happens to be one of the divisors of 32768
.
Writing a bash script for this
Calculating q*b
as described above sounds like a pain. But it really isn't. You can get it as follows:
q*b = 32768 - ( 32768 % b )
In Bash, you can get this with
$((32768 - $((32768 % b)) ))
The following code will generate a random number in the range 0..b
(not inclusive of b
). b=$1
m=$((32768 - $((32768 % $1)) ))
a=$RANDOM
while (( $a > $m ));
do
a=$RANDOM
done
a=$(($a % $1))
printf "$a\n"
Addendum
Technically, there's little reason to work with
m=$((32768 - $((32768 % $1)) ))
The following will accomplish the same thing
a=$RANDOM
while (( $a > $1 ));
do
a=$RANDOM
done
printf "$a\n"
It's a lot more work, but computers are fast.
Generating an Integer in a larger range
I'll let you figure this out. Care needs to be taken, and at some point you'll have to take into consideration the memory limitations of the computer in handling arithmetic operations.
Final note
The accepted answer will not create a random number uniformly over 0 to 1.
To see this, try the following
$ for i in {1..1000}; do echo .$RANDOM; done | awk '{ a += $1 } END { print a }'
For a truly uniform distribution over [0,1)
you should be seeing an average of close to 0.500
.
But as you can see by running the above snippet, you will instead get something like 314.432
or 322.619
. Since it's 1000 numbers, the average of this is .322
. The true average for this sequence of generated numbers is .316362
You can obtain this true average using the perl script
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;
}'
I'm adding integers here to help you see how this approach of using .$RANDOM
isn't doing what you most likely want it to do. In other words, think about which integers are being generated and which ones are missed altogether. Quite a large number are skipped; quite a few are doubled.
On systems where shell's printf is able to understand the %a
format (bash ksh zsh , etc.) and therefore is able to perform an internal base change (hex -> dec) (uniform in [0,1)
range from 0.00003 to 0.99997):
printf '%.5f\n' "$(printf '0x0.%04xp1' $RANDOM)"
You could even use more digits by combining more calls to $RANDOM
(from 0.000000001 to 0.999999999)
printf '%.9f\n' "$(printf '0x0.%08xp2' $(( ($RANDOM<<15) + $RANDOM )))"
The internal (to the shell) "$RANDOM" algorithm is based in a linear-feedback shift register (LFSR). Those are not Cryptographically Secure Pseudo Random Number Generators (CSPRNGs). A better option is to use bytes from the /dev/urandom
device. That will require the call to external octal or hex dump.
$ 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
A very simple (but non-uniform) solution to get a float is:
printf '0.%04d\n' $RANDOM
A way to make it uniform in the range [0,1)
(not including 1):
while a=$RANDOM; ((a>29999)); do :; done; printf '0.%04d\n' "$((a%10000))"