Bash: How to generate random float number using $RANDOM
Solution 1
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
Solution 2
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.
Solution 3
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))"
Solution 4
Use $(( ( RANDOM % N ) + MIN ))
Replace N
with MAX number and MIN with minimum number you want to generate.(N
as MAX is exclusive, put N+1
to have both MAX, MIN inclusive).
Or you can use $(shuf -i MIN-MAX -n 1)
instead.
from 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
The -n 1
in shuf
here means only generate one random Number.
This will generate random numbers between 0~9999 with leading zeros using printf
(in result, number 1 is exclusive).
printf "0.%04d\n" $(( RANDOM % 1000 ))
0.0215
Solution 5
On bash
bc -l <<< "scale=4 ; $((RANDOM % 10000 ))/10000"
where 1/10000
is your random precision and 4
digits your output precision
Related videos on Youtube
sci9
Updated on September 18, 2022Comments
-
sci9 over 1 year
Is it possible to generate real random numbers with a specific precision and in a specific range using the Integer Random Generator $RANDOM? For example how we can generate real number with 4 precision between 0 and 1?
0.1234 0.0309 0.9001 0.0000 1.0000
A simple workaround:
printf "%d04.%d04\n" $RANDOM $RANDOM
-
A.Ellett almost 6 yearsThis also will not produce a true random number in the given range except in the case where N is a divisor of 32767 (the upper limit of $RANDOM).
-
ilkkachu almost 6 yearsReally strictly speaking, since the output of
rand()
is a float within [0,1), it probably isn't exactly evenly distributed when rounded off to four decimal digits. It would be, if the float were of infinite precision, but it isn't: it's likely to be generated from random bits, so there are 2^N different values, and they don't map uniformly to a set of 1000 values. But as long as those pseudo-random floats have enough bits, and you're not doing anything really exact, you probably won't notice. -
four43 over 2 yearsWhen I re-run this I get the same answer over and over again. Any idea why that happens? If I just
echo $RANDOM
I get different results. -
Andrea993 over 2 yearsI'm retring now and it works for me
-
four43 over 2 yearsOh odd. I wonder if I accidentally assigned it or something. A fresh shell session works works.