Floating Point Math in Bash

July 30th, 2008 by Mitch Frazier in

Your rating: None Average: 5 (2 votes)

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 language
As 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 at Linux Journal.


Special Magazine Offer -- 2 Free Trial Issues!
Receive 2 free trial issues of Linux Journal as well as instant online access to current and past issues. There's NO RISK and NO OBLIGATION to buy. CLICK HERE for offer

Linux Journal: delivering readers the advice and inspiration they need to get the most out of their Linux systems since 1994.

Sorry, offer available in the US only. International orders, click here.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Glen M.'s picture

A concise method, maybe?

On October 22nd, 2008 Glen M. (not verified) says:

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

DAR's picture

I used this techique too!

On August 14th, 2008 DAR (not verified) says:

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.

Sanjay VS's picture

script didt run

On August 19th, 2008 Sanjay VS (not verified) says:

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 ??

Mitch Frazier's picture

Bash??

On August 26th, 2008 Mitch Frazier says:

Are you sure your shell is bash? Try running it via "sh -x float.sh".

__________________________

Mitch Frazier is an Associate Editor at Linux Journal.

Paddy3118's picture

Can do. Should do?

On July 30th, 2008 Paddy3118 (not verified) says:

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.

dm's picture

sleep

On July 30th, 2008 dm says:

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?
:-)

Zealot S. Hell's picture

Also ZSH

On August 6th, 2008 Zealot S. Hell (not verified) says:

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.

Post new comment

Please note that comments may not appear immediately, so there is no need to repost your comment.
The content of this field is kept private and will not be shown publicly.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <pre> <ul> <ol> <li> <dl> <dt> <dd> <i> <b>
  • Lines and paragraphs break automatically.

More information about formatting options

Featured Videos

The November 13, 2008 edition of Linux Journal Live! Shawn Powers and special guest, Linux Journal Author Daniel Bartholomew, talk e-book readers and Daniel's Kindle, DRM, and other goodness.

From the Magazine

December 2008, #176

The Oxford English Dictionary says the word "gadget" is a placeholder name for a technical item whose precise name one can't remember. Like that book-reader thingy from Amazon...what's it called? Spindle, Gindle...Kindle, that's it. Check it out in this month's gadget issue.

Other gadgets covered include the Nokia tablets, the BlackBerry, the Neo FreeRunner, the Dash Express, the Roku Netflix Player, the Kangaroo TV, The TomTom GO 930 and the MooBella Ice Cream System. On the larger hardware front, read the reviews of the Acer Aspire One and the YDL PowerStation. On the software front, check out the articles and columns on memcached, Samba security, Mutt, desktop gadgets, bash and Puppet. To wrap it all up, read Doc's thoughts on Google and the browser platform.

Read this issue

Sign up for our Email Newsletter