Damon Cortesi's blog

Musings of an entrepreneur.

Decimal Math in Shell Scripts

| Comments

I was reading about inserting random delays in cron jobs and was surprised when I saw this code to get a random number:

# Grab a random value between 0-240.
while [ $value -gt 240 ] ; do

I thought, there’s got to be a better way to get a random number within a specific range. I mean, that kind of seems like a waste of CPU cycles, particularly when $RANDOM can be anywhere in between 0 and 32767. And a quick test verified that was exactly the case. On average (out of 500 samples), using that method to get a random number below 240 requires 134 attempts. Not a horrid performance impact, but enough to get my anal retentive brain in a twist.

My first thought was to use the expr command to do some simple math to properly adjust the $RANDOM variable into the desired range. But soon enough, I realized the expr only deals with integers…bummer. Looking around to see what else I could use for decimal math, I eventually came across the dc command - a reverse-polish desk calculator:

echo -e "6\nk\n$RANDOM\n32767\n/\n1\nk\n240\n*\np" | dc | cut -f1 -d.

This definitely seems to be faster.

time echo -e “6\nk\n$RANDOM\n32767\n/\n1\nk\n240\n*\np” | dc | cut -f1 -d. 104 real 0m0.003s user 0m0.003s sys 0m0.002s COUNT=0; time while [ $RANDOM -gt 240 ]; do COUNT=`expr $COUNT + 1`; done; echo $COUNT real 0m0.445s user 0m0.158s sys 0m0.297s 295

Update: After pondering the comment below, I realized this can be done in bash by using factors of 100 to resolve the integer math issue. There are several ways to do this, but here are a couple options:

RAND1=`expr $RANDOM \* 100`; FACTOR=`expr $RAND1 \/ 32767`; DIV=`expr $FACTOR \* 240`; RESULT=`expr $DIV \/ 100`;
expr $(expr $(expr $(expr 9264 \* 100) \/ 32767) \* 240) \/ 100

They appear to take about the same time, around 0.050s real time and 0.015s sys time.