How can I compare two floating point numbers in Bash?
BashFloating PointComparisonNumbersBash Problem Overview
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".
Bash Solutions
Solution 1 - Bash
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.
Solution 2 - Bash
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.
Solution 3 - Bash
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
Solution 4 - Bash
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).
Solution 5 - Bash
You can use AWK combined with a Bash if condition:
if awk "BEGIN {exit !($d1 >= $d2)}"; then
echo "yes"
else
echo "no"
fi
Solution 6 - Bash
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
Solution 7 - Bash
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.
Solution 8 - Bash
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
Solution 9 - Bash
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.
Solution 10 - Bash
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
Solution 11 - Bash
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
Solution 12 - Bash
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 https://stackoverflow.com/questions/56415121/ when it got closed as a duplicate of this question, so here it is as it applies here too.
Solution 13 - Bash
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
Solution 14 - Bash
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
Solution 15 - Bash
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
Solution 16 - Bash
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
Solution 17 - Bash
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...
Solution 18 - Bash
Just replace the echo
with a printf (it understands floats):
st=$( printf '%50G < %50G\n' "$num1" "$num2" | bc -l )
Solution 19 - Bash
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.
Solution 20 - Bash
A one-liner solution
Suppose you have two variables A
and B
,
echo "($A > $B) * $B + ($A < $B) * $A" | bc
Solution 21 - Bash
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.