Floating Point Math in Bash
When you think about it, it's surprising how many programming tasks don't require the use of floating point numbers. If you're an embedded systems programmer, you'd probably get fired for using "double" in a C program. If you write PHP or JavaScript, quick, do they even support floating point? One language that doesn't support it is Bash, but let's not let that stop us...
The obvious candidate for adding floating point capabilities to bash is bc. bc is, to quote the man page:
An arbitrary precision calculator languageAs an aside, take note of the last word in that quote: "language". That's right bc is actually a programming language, it contains if statements and while loops among others. I say as an aside because it's largely irrelevant to what we want to do today, not completely irrelevant but largely.
To use bc in our bash scripts we'll package it up into a couple of functions:
float_eval EXPRESSION
and
float_cond CONDITIONAL-EXPRESSION
Both functions expect a single floating point expression,
float_eval writes the result of the expression evaluation to standard out,
float_cond assumes the expression is a conditional expression
and sets the return/status code to zero if the expression is true
and one if it's false.
Usage is quite simple:
float_eval '12.0 / 3.0'
if float_cond '10.0 > 9.0'; then
echo 'As expected, 10.0 is greater than 9.0'
fi
a=12.0
b=3.0
c=$(float_eval "$a / $b")
The code for the functions follows:
#!/bin/bash
#
# Floating point number functions.
#####################################################################
# Default scale used by float functions.
float_scale=2
#####################################################################
# Evaluate a floating point number expression.
function float_eval()
{
local stat=0
local result=0.0
if [[ $# -gt 0 ]]; then
result=$(echo "scale=$float_scale; $*" | bc -q 2>/dev/null)
stat=$?
if [[ $stat -eq 0 && -z "$result" ]]; then stat=1; fi
fi
echo $result
return $stat
}
#####################################################################
# Evaluate a floating point number conditional expression.
function float_cond()
{
local cond=0
if [[ $# -gt 0 ]]; then
cond=$(echo "$*" | bc -q 2>/dev/null)
if [[ -z "$cond" ]]; then cond=0; fi
if [[ "$cond" != 0 && "$cond" != 1 ]]; then cond=0; fi
fi
local stat=$((cond == 0))
return $stat
}
# Test code if invoked directly.
if [[ $(basename $0 .sh) == 'float' ]]; then
# Use command line arguments if there are any.
if [[ $# -gt 0 ]]; then
echo $(float_eval $*)
else
# Turn off pathname expansion so * doesn't get expanded
set -f
e="12.5 / 3.2"
echo $e is $(float_eval "$e")
e="100.4 / 4.2 + 3.2 * 6.5"
echo $e is $(float_eval "$e")
if float_cond '10.0 > 9.3'; then
echo "10.0 is greater than 9.3"
fi
if float_cond '10.0 < 9.3'; then
echo "Oops"
else
echo "10.0 is not less than 9.3"
fi
a=12.0
b=3.0
c=$(float_eval "$a / $b")
echo "$a / $b" is $c
set +f
fi
fi
# vim: tabstop=4: shiftwidth=4: noexpandtab:
# kate: tab-width 4; indent-width 4; replace-tabs false;
The work of the functions is done by feeding the arguments to bc:
result=$(echo "scale=$float_scale; $*" | bc -q 2>/dev/null)By default bc outputs its result with no digits to the right of the decimal point and without a decimal point. To change this you have to change one of bc's builtin variables: scale. This is where the "language" features of bc are relevant, in bc as in C statements are separated by semi-colons. We set bc's scale variable by preceding the expression that we pass to bc with scale=$float_scale;. This sets the scale in bc to the value of the bash global variable float_scale, which is by default set to two (near the top of the script).
The main gotcha here has to do with the fact that "*", "<", and ">" have other meanings in bash. You can eliminate the problem of "<" and ">" by quoting your expressions, but this only works with "*" if you use single quotes and that would mean you couldn't include bash variables in the expression. The other option is to bracket your code with "set -f" and "set +f" to turn off pathname/wildcard expansion.
If you save the script as float.sh and run it directly it will execute the test code at the bottom:
$ sh float.sh 12.5 / 3.2 is 3.90 100.4 / 4.2 + 3.2 * 6.5 is 44.70 10.0 is greater than 9.3 10.0 is not less than 9.3 12.0 / 3.0 is 4.00
The one unaswered question you may have is: "and why would I want to do this?" Next time around I'll show you one place you can put this to real world use.
Mitch Frazier is an Associate Editor for Linux Journal.
Trending Topics
| You Need A Budget | Feb 10, 2012 |
| The Linux powered LAN Gaming House | Feb 08, 2012 |
| Creating a vDSO: the Colonel's Other Chicken | Feb 06, 2012 |
| Your CMS Is Not Your Web Site | Feb 01, 2012 |
| Casper, the Friendly (and Persistent) Ghost | Jan 31, 2012 |
| Razor-qt 0.4 - Qt based Desktop Environment | Jan 30, 2012 |
- Fun with ethtool
- Parallel Programming with NVIDIA CUDA
- Readers' Choice Awards 2011
- 100% disappointed with the decision to go all digital.
- Linux-Based X Terminals with XDMCP
- Validate an E-Mail Address with PHP, the Right Way
- You Need A Budget
- The Linux powered LAN Gaming House
- Why Python?
- Python for Android
- BeOS was the best
1 hour 42 min ago - I use Wireshark on a daily
6 hours 13 min ago - buena información
11 hours 20 min ago - One important "bucket" that I didn't note (désolé si qqun deja d
12 hours 20 min ago - Gnome3 is such a POS. No one
21 hours 48 min ago - Gnome 3 is the biggest POS
21 hours 58 min ago - I didn't knew this thing by
1 day 4 hours ago - Author's reply
1 day 7 hours ago - Link to modlys
1 day 8 hours ago - I use YNAB because of the
1 day 8 hours ago





Comments
Code License?
Mitch, Thanks for the awesome floating point functions!
May I reuse this code within other scripts and projects? Which license does it carry (if any)? Thanks, JadeStar.
GPLv2
Yes, you may reuse it. License == GPLv2.
Mitch Frazier is an Associate Editor for Linux Journal.
A concise method, maybe?
This is actually my first bash script and I'm not an experienced programmer, so feel free to criticize. Interestingly, since I'm not used to programming in bash, I found a use for floating point math in my first bash script, because I didn't know it wasn't natively supported. Nevertheless, I like my solution, so far, but since I'm not my own best critic when it comes to coding, please feel free to give me some pointers. Anyway, below is the essence of the code. I'm using my floats as a counter variable, so I end up using bc both in a condition and in a calculation. (oh and thanks Mitch for giving me the essentials, so that I could strike out on my own.)
#!/bin/bash
$decrement=.1
$upperLimit=15
$lowerLimit=10
#.........omitted code.........
while (( `echo "$upperLimit >= $lowerLimit" | bc` ))
do
echo "$upperLimit" #...........altered code.........
upperLimit=`echo "$upperLimit - $decrement" | bc`
done
exit 0
Presumably not your final version
I assume this is an early version since
the following statements are not even syntactically valid:
(You don't use the $ when assigning variables).
But beyond that it looks like you might be getting there.
Mitch Frazier is an Associate Editor for Linux Journal.
I used this techique too!
I actually used this exact technique (of piping a floating point comparison into bc) pretty recently (and before reading about it here). I wanted a cron script that would monitor the CPU temp on my home server and notify me if it went too high. So I wrote a script that grabbed the output from "sensors" (from the lm-sensors package), "grep"ped for the line containing CPU temp, "cut" away all but the raw temperature value, and then piped a simple floating point comparison string into bc to compare the CPU temp vs. the threshold temp. If bc returns a 1 (true) result, I have the script spit out a message, which cron then emails to root (which is aliased to my email address). Nice little unix-esque solution of assembling a bunch of small, simple things together so as to achieve a more complex piece of behavior.
script didt run
The script simply didnt run.
ssanjayv@localhost:~/bin/my_scripts> sh float.sh
: command not found
: command not found
: command not found
: command not found
: command not found
'loat.sh: line 14: syntax error near unexpected token `
'loat.sh: line 14: `function float_eval()
any idea whats causing the problem ??
Bash??
Are you sure your shell is bash? Try running it via "sh -x float.sh".
Mitch Frazier is an Associate Editor for Linux Journal.
Can do. Should do?
If you have to do the odd bit of floating point then you might look at writing the whole script in some scripting language that supports floating point natively. It just seems error prone and fragile to pass floats as strings between bash and bc.
AWK, TCL, Python, Perl, Lua, Pike, Ruby, ... These might all be better solutions, and if your running on a striped-down, ultra small, unix platform, then it might be best to include something small but with more features like Pike or Lua rather than bc.
- Paddy.
sleep
One place I'd want them is for "sleep".
Occasionally it can be useful to sleep for a fraction of a second
(for instance when you are automatically terminating spawned processes
if they run for more than pre-specified amount of time...
see timeout.sh script in Bash examples.)
Floating points are supported by Korn shell (Bash == Korn + readline)
For instance:
echo $((1.2 + 2.9))
4.1
Why reinvent the ol' good Korn?
:-)
Also ZSH
Zsh does floating point also. I use zsh over korn since it has better line editor, cycle-tab completion (cmd.exe style), and many other nice features. But yeah, why anybody uses bash is beyond me.