258

I am trying hard to compare two floating point numbers within a Bash script. I have two variables, e.g.

let num1=3.17648e-22
let num2=1.5

Now, I just want do a simple comparison of these two numbers:

st=`echo "$num1 < $num2" | bc`
if [ $st -eq 1]; then
  echo -e "$num1 < $num2"
else
  echo -e "$num1 >= $num2"
fi

Unfortunately, I have some problems with the right treatment of the num1 which can be of the "e-format".

2
  • 2
    With "e-format" I mean the exponential notation (also called scientific notation) Commented Dec 28, 2011 at 9:32
  • Related: Floating-point arithmetic in UNIX shell script Commented Feb 10, 2015 at 23:39

22 Answers 22

349

More conveniently

This can be done more conveniently using Bash's numeric context:

if (( $(echo "$num1 > $num2" |bc -l) )); then
  …
fi

Explanation

Piping through the basic calculator command bc returns either 1 or 0.

The option -l is equivalent to --mathlib; it loads the standard math library.

Enclosing the whole expression between double parenthesis (( )) will translate these values to respectively true or false.

Please, ensure that the bc basic calculator package is installed.

Caveat: Exponential notation should be written as *10^; not E, nor e.

For example:

$ echo "1*10^3==1000" |bc
1

Whereas

$ echo "1E3==1000" |bc
0

Strategies to overcome this bc limitation are discussed here.

Sign up to request clarification or add additional context in comments.

11 Comments

Same issue as stackoverflow.com/questions/8654051/… e.g. $ echo "1.1+2e+02" | bc (standard_in) 1: syntax error
@MohitArora Please, ensure that you have the bc calculator package installed.
I get a 0: not found with the statement if (( $(echo "$TOP_PROCESS_PERCENTAGE > $THRESHOLD" | bc -l) )); then.
To all those who get "command not found", remember you need to enclose the bc into either backticks or $() and then into (( )) ... i.e. (( $(bc -l<<<"$a>$b") )) and not (( bc -l<<<"$a>$b" )).
3.44E6 is the same as 3.4546 according to bc -l. If you want 3.44*10^6 than use the string 3.44*10^6, or 3440000.
|
116

Bash handles only integer maths, but you can use the bc command as follows:

$ num1=3.17648E-22
$ num2=1.5
$ echo $num1'>'$num2 | bc -l
0
$ echo $num2'>'$num1 | bc -l
1

Note that the exponent sign must be uppercase.

5 Comments

yes, but to workaround incorrect calculations its needed to uppercase 'e' sign in scientific number notation and use -l flag to bc programm for predefined math routines
you should point that out in your answer then, instead of just posting a very similar solution and not mention the important differances.
It is not a very similar solution. Alrusdi's solution uses the bc tool and that is what I would recommend to any BASH programmer. BASH is typeless language. Yeah, it can do integer arithmetic, but for floating point you must use some external tool. BC is the best because that is what it is made for.
Since he is trying to use it in an if statement, I would show that. if [ $(... | bc -l) == 1 ] ; then ...
This simple solution does not work. You need to change the exponential representations as described in Serge Stroobandt's answer. In practice,bc does not understand the scientific E notation. Even worse, bc (or at least my bc version) does not prints an error when the E notation is used.
42

You can use AWK combined with a Bash if condition:

if awk "BEGIN {exit !($d1 >= $d2)}"; then
    echo "yes"
else
    echo "no"
fi

2 Comments

Using awk is great since it is able to handle floating point numbers, but I personally prefer the synthax if (( $(echo $d1 $d2 | awk '{if ($1 > $2) print 1;}') )); then echo "yes"; else echo "no"; fi
I'm having to do this in a debian-based docker image, where bc is not found. This is the cleanest awk implementation, IMO.
32

A pure Bash solution for comparing floats without exponential notation, leading or trailing zeros:

if [ ${FOO%.*} -eq ${BAR%.*} ] && [ ${FOO#*.} \> ${BAR#*.} ] || [ ${FOO%.*} -gt ${BAR%.*} ]; then
  echo "${FOO} > ${BAR}";
else
  echo "${FOO} <= ${BAR}";
fi

The order of logical operators matters. Integer parts are compared as numbers and fractional parts are intentionally compared as strings. Variables are split into integer and fractional parts using this method.

It won't compare floats with integers (without dot).

Comments

31

It's better to use AWK for noninteger mathematics. You can use this Bash utility function:

numCompare() {
   awk -v n1="$1" -v n2="$2" 'BEGIN {printf "%s " (n1<n2?"<":">=") " %s\n", n1, n2}'
}

And call it as:

numCompare 5.65 3.14e-22
5.65 >= 3.14e-22

numCompare 5.65e-23 3.14e-22
5.65e-23 < 3.14e-22

numCompare 3.145678 3.145679
3.145678 < 3.145679

14 Comments

i like this answer, people tend to shy away from awk esp beginners, they seem to think it is harder than it actually is, i think people get intimidated by the curly braces and the seemingly language mixed syntax (at a glance). And since awk is pretty much guaranteed to be present on the target system as well, just like bc (not sure which one, if any, is ever NOT installed). I love bash scripting but that no floating point, not even a meager 2 decimal places (i guess someone could write a 'fake' wrapper for that), really is annoying...
Using awk and bc in shell scripts is a standard practice since ancient time, I would say some features have never been added to shells because they are available in awk, bc and other Unix tools. No need for purity in shell scripts.
@WanderingMind One way to do that would be to pass the 0 or 1 to exit so that Awk communicates the result back to the shell in a proper, machine-readable way. if awk -v n1="123.456" -v n2="3.14159e17" 'BEGIN { exit (n1 <= n2) }' /dev/null; then echo bigger; else echo not; fi ... though note how the condition is inverted (the exit status 0 means success to the shell).
Why just python. You have perl installed by default on many Linux/Unix systems.. even php also
Trust me, they will curse me if they see me invoking a full fledge programming language from a shell script for this trivial task.
|
11

Beware when comparing numbers that are package versions, like checking if grep 2.20 is greater than version 2.6:

$ awk 'BEGIN { print (2.20 >= 2.6) ? "YES" : "NO" }'
NO

$ awk 'BEGIN { print (2.2 >= 2.6) ? "YES" : "NO" }'
NO

$ awk 'BEGIN { print (2.60 == 2.6) ? "YES" : "NO" }'
YES

I solved such problems with such a shell/AWK function:

# Get version of GNU tool
toolversion() {
    local prog="$1" operator="$2" value="$3" version

    version=$($prog --version | awk '{print $NF; exit}')

    awk -vv1="$version" -vv2="$value" 'BEGIN {
        split(v1, a, /\./); split(v2, b, /\./);
        if (a[1] == b[1]) {
            exit (a[2] '$operator' b[2]) ? 0 : 1
        }
        else {
            exit (a[1] '$operator' b[1]) ? 0 : 1
        }
    }'
}

if toolversion grep '>=' 2.6; then
   # Do something awesome
fi

3 Comments

On a Debian-based system, dpkg --compare-versions is often useful. It has the full logic for comparing Debian package versions built into it, which are more complex than just x.y.
@NeilMayhew just what I needed.
sorting versions like this is a bad idea. It won't be able to handle things like v1.5.1 or 12.1.3-a. sort -V or sort --version-sort would be much better
8

A solution that supports all possible notations, including the scientific notation with both uppercase and lowercase exponents (e.g., 12.00e4):

if (( $(bc -l <<< "${value1/e/E} < ${value2/e/E}") ))
then
    echo "$value1 is smaller than $value2"
fi 

1 Comment

Unless I have a typo in my script, I get a (standard_in) 1: syntax error
7

Of course, if you don't need really floating-point arithmetic, just arithmetic on e.g. dollar values where there are always exactly two decimal digits, you might just drop the dot (effectively multiplying by 100) and compare the resulting integers.

if [[ $((10#${num1/.})) < $((10#${num2/.})) ]]; then
    ...

This obviously requires you to be sure that both values have the same number of decimal places.

Comments

6

Please check the below edited code:

#!/bin/bash

export num1=(3.17648*e-22)
export num2=1.5

st=$((`echo "$num1 < $num2"| bc`))
if [ $st -eq 1 ]
  then
    echo -e "$num1 < $num2"
  else
    echo -e "$num1 >= $num2"
fi

This works well.

Comments

4
num1=0.555
num2=2.555


if [ `echo "$num1>$num2"|bc` -eq 1 ]; then
       echo "$num1 is greater then $num2"
else
       echo "$num2 is greater then $num1"
fi

1 Comment

An explanation would be in order. E.g., how is it different from previous answers and what is the idea/gist? Please respond by editing (changing) your answer, not here in comments (without "Edit:", "Update:", or similar - the answer should appear as if it was written today).
4

AWK and tools like it (I'm staring at you sed...) should be relegated to the dustbin of old projects, with code that everyone is too afraid to touch since it was written in a read-never language.

Or you're the relatively rare project that needs to prioritize CPU usage optimization over code maintenance optimization... in which case, carry on.

If not, though, instead just use something readable and explicit, such as Python. Your fellow coders and future self will thank you. You can use Python code inline with Bash just like all the others.

num1=3.17648E-22
num2=1.5
if python -c "exit(0 if $num1 < $num2 else 1)"; then
    echo "yes, $num1 < $num2"
else
    echo "no, $num1 >= $num2"
fi

17 Comments

@Witiko My original version was a bit snarkier.
Even more succinct: use not(...) instead of 0 if ... else 1
If you're relegating awk and sed (I'm looking at you CivFan) to the dustbin of history, you're a lousy systems administrator and you're typing too much code. (And I like and use Python, so it's not about that). -1 for misplaced snarkiness. There's a place in the systems domain for those tools, Python or no.
Interestingly, I ended up with good ol' Perl! awk '${print $5}' ptpd_log_file | perl -ne '$_ > 0.000100 && print' > /tmp/outfile. Easy peasy. Every language has its place.
Don't lump awk in with seds syntacic wackiness. Unlike python, awk is a mandatory utility on every UNIX installation and the awk equivalent of python -c "import sys; sys.exit(0 if float($num1) < float($num2) else 1)" is simply awk "BEGIN{exit ($num1 > $num2 ? 0 : 1)}".
|
3

I used the answers from here and put them in a function. You can use it like this:

is_first_floating_number_bigger 1.5 1.2
result="${__FUNCTION_RETURN}"

Once called, echo $result will be 1 in this case, otherwise 0.

The function:

is_first_floating_number_bigger () {
    number1="$1"
    number2="$2"

    [ ${number1%.*} -eq ${number2%.*} ] && [ ${number1#*.} \> ${number2#*.} ] || [ ${number1%.*} -gt ${number2%.*} ];
    result=$?
    if [ "$result" -eq 0 ]; then result=1; else result=0; fi

    __FUNCTION_RETURN="${result}"
}

Or a version with debug output:

is_first_floating_number_bigger () {
    number1="$1"
    number2="$2"

    echo "... is_first_floating_number_bigger: comparing ${number1} with ${number2} (to check if the first one is bigger)"

    [ ${number1%.*} -eq ${number2%.*} ] && [ ${number1#*.} \> ${number2#*.} ] || [ ${number1%.*} -gt ${number2%.*} ];
    result=$?
    if [ "$result" -eq 0 ]; then result=1; else result=0; fi

    echo "... is_first_floating_number_bigger: result is: ${result}"

    if [ "$result" -eq 0 ]; then
        echo "... is_first_floating_number_bigger: ${number1} is not bigger than ${number2}"
    else
        echo "... is_first_floating_number_bigger: ${number1} is bigger than ${number2}"
    fi

    __FUNCTION_RETURN="${result}"
}

Just save the function in a separated .sh file and include it like this:

. /path/to/the/new-file.sh

Comments

3

For simplicity and clarity, just use AWK for the calculations as it's a standard Unix tool and so just as likely to be present as bc and much easier to work with syntactically.

For this question:

$ cat tst.sh
#!/bin/bash

num1=3.17648e-22
num2=1.5

awk -v num1="$num1" -v num2="$num2" '
BEGIN {
    print "num1", (num1 < num2 ? "<" : ">="), "num2"
}
'

$ ./tst.sh
num1 < num2

And for that other question that was closed as a duplicate of this one:

$ cat tst.sh
#!/bin/bash

read -p "Operator: " operator
read -p "First number: " ch1
read -p "Second number: " ch2

awk -v ch1="$ch1" -v ch2="$ch2" -v op="$operator" '
BEGIN {
    if ( ( op == "/" ) && ( ch2 == 0 ) ) {
        print "Nope..."
    }
    else {
        print ch1 '"$operator"' ch2
    }
}
'

$ ./tst.sh
Operator: /
First number: 4.5
Second number: 2
2.25

$ ./tst.sh
Operator: /
First number: 4.5
Second number: 0
Nope...

I was posting this as an answer to '4.5: syntax error: invalid arithmetic operator (error token is ".5")' - but the code still seems to work. Why? when it got closed as a duplicate of this question, so here it is as it applies here too.

Comments

2

This script may help where I'm checking if the installed Grails version is greater than the minimum required.

#!/bin/bash

min=1.4
current=`echo $(grails --version | head -n 2 | awk '{print $NF}' | cut -c 1-3)`

if [ 1 -eq `echo "${current} < ${min}" | bc` ]
then
    echo "Yo, you have an older version of Grails."
else
    echo "Hurray, you have the latest version"
fi

Comments

2

A very simple perl solution:

$ num1=3.2E8
$ num2=2.5E9
$ perl -e "print $num2 > $num1? 'true' : 'false', qq(\n);"
true
$ perl -e "print $num2 < $num1? 'true' : 'false', qq(\n);"
false

This proves that perl really understands the 'E' notation for scientific numerical representation:

$ perl -e "print $num1, qq(\n);"
320000000

If you need a 'if' statment in your shell script, use exit command in perl:

$ if perl -e "exit ($num1 > $num2? 0 : 1);"; then echo true; else echo false; fi
false

Note that in shell script, a command returning 0 is a success, and passes the 'if' condition (so the if-clause is executed). Any other non-zero return values means a failure.

2 Comments

Sugguest using single quote and qq() to avoid too many \" : perl -e "print $num2 > $num1? 'true' : 'false', qq(\n);"
Thanks for the suggestions. The answer was updated.
1

Use KornShell. In Bash you may have to compare the decimal part separately:

#!/bin/ksh
X=0.2
Y=0.2
echo $X
echo $Y

if [[ $X -lt $Y ]]
then
     echo "X is less than Y"
elif [[ $X -gt $Y ]]
then
     echo "X is greater than Y"
elif [[ $X -eq $Y ]]
then
     echo "X is equal to Y"
fi

4 Comments

the problem is that many distributions do not come with ksh installed, and if your script is going to be used by others, they tend to not like having to install extra stuff, especially when its just a script that is supposed to be written in bash -one would think they didnt need ANOTHER shell to do that, which undermines the whole reason of using a bash script in the first place --sure we could ALSO go code it in C++, but why?
What are the distributions which come without ksh installed?
@piokuc for example, Ubuntu Desktop & Server. I would say it's pretty major one...
Also, question specifically asks for solution that works in bash. There might be really good reasons for that. Say, it's part of large application and migrating everything to ksh is not feasible. Or it's running on embedded platform where installing another shell is really a problem.
1

Use this:

VAL_TO_CHECK="1.00001"
if [ $(awk '{printf($1 >= $2) ? 1 : 0}' <<<" $VAL_TO_CHECK 1 ") -eq 1 ] ; then
    echo "$VAL_TO_CHECK >= 1"
else
    echo "$VAL_TO_CHECK < 1"
fi

1 Comment

The Awk script should simply exit 0 to report truth and exit 1 to return false; then you can simplify to the remarkably elegant if awk 'BEGIN { exit (ARGV[1] >= ARGV[2]) ? 0 : 1 }' "$VAL_TO_CHECK" 1; then... (more elegant still if you encapsulate the Awk script in a shell function).
1

Using bashj, a Bash mutant with Java support, you just write (and it is easy to read):

#!/usr/bin/bashj

#!java
static int doubleCompare(double a,double b) {return((a>b) ? 1 : (a<b) ? -1 : 0);}

#!bashj
num1=3.17648e-22
num2=1.5
comp=j.doubleCompare($num1,$num2)
if [ $comp == 0 ] ; then echo "Equal" ; fi
if [ $comp == 1 ] ; then echo "$num1 > $num2" ; fi
if [ $comp == -1 ] ; then echo "$num2 > $num1" ; fi

Of course, the bashj Bash/Java hybridation offers much more...

Comments

0

There's one simple approach which is a bit faster than AWK and does not require bc to be installed. It leverages sort's ability to sort float numbers:

A=1280.4
B=9.325
LOW=$(sort -n <<< "$A"$'\n'"$B" | head -1)
if [[ "$LOW" == "$A" ]]; then
    echo "A <= B"
else
    echo "A >= B"
fi

Of course, it does not work for numbers that are equal.

Comments

0

Just replace the echo with a printf (it understands floats):

st=$(  printf '%50G < %50G\n' "$num1" "$num2" | bc -l  )

1 Comment

What is printf? A built-in? External command?
0

A one-liner solution

Suppose you have two variables A and B,

echo "($A > $B) * $B + ($A < $B) * $A" | bc

Comments

0

Here's a gawk+GMP based approach to account for a broader range of potential input :

 echo " 5.65e-23 3.14e-22\n
        5.65 3.14e-2203\n
        3.145678 3.145679\n
        3.25353E+9293 325353e9288\n
        3.14159e+200000000001 3.1415899999999999999999E200000000001\n
        100000 100000.0\n
             4096 4096" \
                         \
 | gawk -v PREC=9999999 -nMbe '
  
   NF+=OFS=sprintf(" %s ",
          (+($!_=sprintf("%24s",$!_)<+$NF) \
     ? "<" \
        : (+$NF<+$!_) \
     ? ">" \
        : (int(+$!_)==(__=int(+$NF)))*\
          (__==+$NF)*index($!_,$NF  )  \
     ? "=" \
         : "\342\211\210")' | ecp 
 
                5.65e-23 < 3.14e-22
                    5.65 > 3.14e-2203
                3.145678 < 3.145679
           3.25353E+9293 ≈ 325353e9288
   3.14159e+200000000001 ≈ 3.1415899999999999999999E200000000001
                  100000 ≈ 100000.0
                    4096 = 4096
 

For more clear-cut cases, it'll give you back a definitive answer of

  • less than <,
  • greater than >, or
  • exactly equal to = (purely integer cases, for now)

When it's relatively ambiguous, it outputs the Unicode character U+2248 ≈ ALMOST EQUAL TO instead of attempting to resolve it at all cost.

Most of the time you won't need PREC of 10-million; something like PREC = 32767 is good enough for most scenarios one encounters on a typical basis.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.