LDP/LDP/guide/docbook/abs-guide/random-between.sh

185 lines
5.8 KiB
Bash
Raw Normal View History

2003-11-03 16:26:45 +00:00
#!/bin/bash
# random-between.sh
# Random number between two specified values.
# Script by Bill Gradwohl, with minor modifications by the document author.
# Used with permission.
randomBetween() {
# Generates a positive or negative random number
#+ between $min and $max
#+ and divisible by $divisibleBy.
# Gives a "reasonably random" distribution of return values.
#
# Bill Gradwohl - Oct 1, 2003
syntax() {
# Function embedded within function.
echo
echo "Syntax: randomBetween [min] [max] [multiple]"
echo
echo "Expects up to 3 passed parameters, but all are completely optional."
echo "min is the minimum value"
echo "max is the maximum value"
echo "multiple specifies that the answer must be a multiple of this value."
echo " i.e. answer must be evenly divisible by this number."
echo
echo "If any value is missing, defaults area supplied as: 0 32767 1"
echo "Successful completion returns 0, unsuccessful completion returns"
echo "function syntax and 1."
echo "The answer is returned in the global variable randomBetweenAnswer"
echo "Negative values for any passed parameter are handled correctly."
}
local min=${1:-0}
local max=${2:-32767}
local divisibleBy=${3:-1}
# Default values assigned, in case parameters not passed to function.
local x
local spread
# Let's make sure the divisibleBy value is positive.
[ ${divisibleBy} -lt 0 ] && divisibleBy=$((0-divisibleBy))
# Sanity check.
if [ $# -gt 3 -o ${divisibleBy} -eq 0 -o ${min} -eq ${max} ]; then
syntax
return 1
fi
# See if the min and max are reversed.
if [ ${min} -gt ${max} ]; then
# Swap them.
x=${min}
min=${max}
max=${x}
fi
# If min is itself not evenly divisible by $divisibleBy,
#+ then fix the min to be within range.
if [ $((min/divisibleBy*divisibleBy)) -ne ${min} ]; then
if [ ${min} -lt 0 ]; then
min=$((min/divisibleBy*divisibleBy))
else
min=$((((min/divisibleBy)+1)*divisibleBy))
fi
fi
# If max is itself not evenly divisible by $divisibleBy,
#+ then fix the max to be within range.
if [ $((max/divisibleBy*divisibleBy)) -ne ${max} ]; then
if [ ${max} -lt 0 ]; then
max=$((((max/divisibleBy)-1)*divisibleBy))
else
max=$((max/divisibleBy*divisibleBy))
fi
fi
# ---------------------------------------------------------------------
2005-05-08 20:09:31 +00:00
# Now, to do the real work.
2003-11-03 16:26:45 +00:00
2005-05-08 20:09:31 +00:00
# Note that to get a proper distribution for the end points,
#+ the range of random values has to be allowed to go between
#+ 0 and abs(max-min)+divisibleBy, not just abs(max-min)+1.
2003-11-03 16:26:45 +00:00
# The slight increase will produce the proper distribution for the
#+ end points.
# Changing the formula to use abs(max-min)+1 will still produce
#+ correct answers, but the randomness of those answers is faulty in
#+ that the number of times the end points ($min and $max) are returned
#+ is considerably lower than when the correct formula is used.
# ---------------------------------------------------------------------
spread=$((max-min))
2006-06-22 14:28:13 +00:00
# Omair Eshkenazi points out that this test is unnecessary,
#+ since max and min have already been switched around.
2003-11-03 16:26:45 +00:00
[ ${spread} -lt 0 ] && spread=$((0-spread))
let spread+=divisibleBy
randomBetweenAnswer=$(((RANDOM%spread)/divisibleBy*divisibleBy+min))
return 0
2004-01-26 00:03:37 +00:00
# However, Paulo Marcel Coelho Aragao points out that
#+ when $max and $min are not divisible by $divisibleBy,
#+ the formula fails.
#
# He suggests instead the following formula:
# rnumber = $(((RANDOM%(max-min+1)+min)/divisibleBy*divisibleBy))
2003-11-03 16:26:45 +00:00
}
# Let's test the function.
min=-14
max=20
divisibleBy=3
# Generate an array of expected answers and check to make sure we get
#+ at least one of each answer if we loop long enough.
declare -a answer
minimum=${min}
maximum=${max}
if [ $((minimum/divisibleBy*divisibleBy)) -ne ${minimum} ]; then
if [ ${minimum} -lt 0 ]; then
minimum=$((minimum/divisibleBy*divisibleBy))
else
minimum=$((((minimum/divisibleBy)+1)*divisibleBy))
fi
fi
# If max is itself not evenly divisible by $divisibleBy,
#+ then fix the max to be within range.
if [ $((maximum/divisibleBy*divisibleBy)) -ne ${maximum} ]; then
if [ ${maximum} -lt 0 ]; then
maximum=$((((maximum/divisibleBy)-1)*divisibleBy))
else
maximum=$((maximum/divisibleBy*divisibleBy))
fi
fi
# We need to generate only positive array subscripts,
#+ so we need a displacement that that will guarantee
#+ positive results.
displacement=$((0-minimum))
for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do
answer[i+displacement]=0
done
# Now loop a large number of times to see what we get.
loopIt=1000 # The script author suggests 100000,
#+ but that takes a good long while.
for ((i=0; i<${loopIt}; ++i)); do
# Note that we are specifying min and max in reversed order here to
#+ make the function correct for this case.
randomBetween ${max} ${min} ${divisibleBy}
# Report an error if an answer is unexpected.
[ ${randomBetweenAnswer} -lt ${min} -o ${randomBetweenAnswer} -gt ${max} ] && echo MIN or MAX error - ${randomBetweenAnswer}!
[ $((randomBetweenAnswer%${divisibleBy})) -ne 0 ] && echo DIVISIBLE BY error - ${randomBetweenAnswer}!
# Store the answer away statistically.
answer[randomBetweenAnswer+displacement]=$((answer[randomBetweenAnswer+displacement]+1))
done
# Let's check the results
for ((i=${minimum}; i<=${maximum}; i+=divisibleBy)); do
[ ${answer[i+displacement]} -eq 0 ] && echo "We never got an answer of $i." || echo "${i} occurred ${answer[i+displacement]} times."
done
exit 0