188
votes

I am trying hard to compare two floating point numbers within a bash script. I have to 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".

18
With "e-format" I mean the exponential notation (also called scientific notation)Jonas

18 Answers

227
votes

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.

This equally works for floats in scientific format, provided a capital letter E is employed, e.g. num1=3.44E6

110
votes

bash handles only integer maths but you can use 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 exponent sign must be uppercase

29
votes

It's better to use awk for non integer 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
26
votes

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

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.

Won't compare floats with integers (without dot).

19
votes

You can use awk combined with a bash if condition:

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

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 problem with such 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
6
votes

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.

4
votes

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, why not instead just use something readable and explicit, such as python? Your fellow coders and future self will thank you. You can use python 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
4
votes

A solution supporting 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 below $value2"
fi 
3
votes

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
3
votes

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.

3
votes

I was posting this as an answer to https://stackoverflow.com/a/56415379/1745001 when it got closed as a dup of this question so here it is as it applies here too:

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 dup 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...
2
votes

This script may help where I'm checking if installed grails version is greater than minimum required. Hope it helps.

#!/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 older version of grails."                                                   
else                                                                                                                                                                                                                       
    echo "Hurray, you have the latest version" 
fi
2
votes
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
votes

Use korn shell, 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
1
votes

How about this? =D

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
votes

Using bashj (https://sourceforge.net/projects/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 bashj bash/java hybridation offers much more...

0
votes

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

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