Bash: How to generate random float number using $RANDOM

14,514

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

Share:
14,514

Related videos on Youtube

sci9
Author by

sci9

Updated on September 18, 2022

Comments

  • sci9
    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
    A.Ellett almost 6 years
    This 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
    ilkkachu almost 6 years
    Really 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
    four43 over 2 years
    When 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
    Andrea993 over 2 years
    I'm retring now and it works for me
  • four43
    four43 over 2 years
    Oh odd. I wonder if I accidentally assigned it or something. A fresh shell session works works.