5

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
  • 1
    Please specify what you mean by "real random numbers". Are you requiring a source of random numbers generated by something like particle decay, or will you be happy with a pseudo-random generator? Is your application of these numbers of cryptographic or scientific significance, or do you just want something that "looks random". – Kusalananda Feb 17 '18 at 7:28
  • ... or do you actually mean "float" or "floating point number? – Kusalananda Feb 17 '18 at 9:00
  • Thank you for comment. I need a pseudo random number generator for floating point numbers based on $RANDOM. – sci9 Feb 17 '18 at 10:29
  • ...for implementing meta heuristic algorithms in bash. – sci9 Feb 17 '18 at 10:30
8
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
  • 1
    This approach of merely prepending $RANDOM with a decimal will not create a uniform distribution between 0 and 1. – A.Ellett Aug 4 '18 at 6:50
  • @A.Ellett No particular distribution of numbers was mentioned in the question. – Kusalananda Aug 4 '18 at 6:56
  • @Kusalananda when someone requests a random number generator, then they want a uniform distribution. If they don't care or if they do want a different distribution, then they need to explicitly say so. Otherwise, they're misrepresenting what's actually happening. For example, if someone says they want a random number from within a given range, then they are expecting all values in that range to be equally likely. That's the definition of a uniform distribution. – A.Ellett Aug 4 '18 at 6:59
  • 1
    Much improved. Nice job. – A.Ellett Aug 4 '18 at 7:06
  • 1
    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. – ilkkachu Aug 4 '18 at 19:07
6

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

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.

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
  • 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). – A.Ellett Aug 4 '18 at 7:04
1

On bash

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

where 1/10000 is your random precision and 4 digits your output precision

0

zsh has a rand48() arithmetic function (wrapper to the erand48() standard function) in its zsh/mathfunc module:

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.