How to compare to floating point number in a shell script
Solution 1
You could check separately the integer and fractional parts:
#!/bin/bash
min=12.45
val=12.35
if (( ${val%%.*} < ${min%%.*} || ( ${val%%.*} == ${min%%.*} && ${val##*.} < ${min##*.} ) )) ; then
min=$val
fi
echo $min
As fered says in the comments, it works only if both numbers have fractional parts and both fractional parts have the same number of digits. Here's a version that works for integer or fractional and any bash operator:
#!/bin/bash
shopt -s extglob
fcomp() {
local oldIFS="$IFS" op=$2 x y digitx digity
IFS='.' x=( ${1##+([0]|[-]|[+])}) y=( ${3##+([0]|[-]|[+])}) IFS="$oldIFS"
while [[ "${x[1]}${y[1]}" =~ [^0] ]]; do
digitx=${x[1]:0:1} digity=${y[1]:0:1}
(( x[0] = x[0] * 10 + ${digitx:-0} , y[0] = y[0] * 10 + ${digity:-0} ))
x[1]=${x[1]:1} y[1]=${y[1]:1}
done
[[ ${1:0:1} == '-' ]] && (( x[0] *= -1 ))
[[ ${3:0:1} == '-' ]] && (( y[0] *= -1 ))
(( ${x:-0} $op ${y:-0} ))
}
for op in '==' '!=' '>' '<' '<=' '>='; do
fcomp $1 $op $2 && echo "$1 $op $2"
done
Solution 2
Bash does not understand floating point arithmetic. It treats numbers containing a decimal point as strings.
Use awk or bc instead.
#!/bin/bash
min=12.45
val=10.35
if [ 1 -eq "$(echo "${val} < ${min}" | bc)" ]
then
min=${val}
fi
echo "$min"
If you intend to do a lot of math operations, it's probably better to rely on python or perl.
Solution 3
You can use package num-utils for simple manipulations...
For more serious maths, see this link... It describes several options, eg.
- R / Rscript (GNU R statistical computation and graphics system)
- octave (mostly Matlab compatible)
- bc (The GNU bc arbitrary precision calculator language)
An example of numprocess
echo "123.456" | numprocess /+33.267,%2.33777/
# 67.0395291239087
A programs for dealing with numbers from the command line
The 'num-utils' are a set of programs for dealing with numbers from the
Unix command line. Much like the other Unix command line utilities like
grep, awk, sort, cut, etc. these utilities work on data from both
standard in and data from files.
Includes these programs:
* numaverage: A program for calculating the average of numbers.
* numbound: Finds the boundary numbers (min and max) of input.
* numinterval: Shows the numeric intervals between each number in a sequence.
* numnormalize: Normalizes a set of numbers between 0 and 1 by default.
* numgrep: Like normal grep, but for sets of numbers.
* numprocess: Do mathematical operations on numbers.
* numsum: Add up all the numbers.
* numrandom: Generate a random number from a given expression.
* numrange: Generate a set of numbers in a range expression.
* numround: Round each number according to its value.
Here is a bash
hack...It adds leading 0's to the integer to make a string left-to-right comparison meaningful. This particular piece of code requires that both min and val actually have a decimal point and at least one decimal digit.
min=12.45
val=10.35
MIN=0; VAL=1 # named array indexes, for clarity
IFS=.; tmp=($min $val); unset IFS
tmp=($(printf -- "%09d.%s\n" ${tmp[@]}))
[[ ${tmp[VAL]} < ${tmp[MIN]} ]] && min=$val
echo min=$min
output:
min=10.35
Solution 4
For simple calculations on floating point numbers (+-*/ and comparisons), you can use awk.
min=$(echo 12.45 10.35 | awk '{if ($1 < $2) print $1; else print $2}')
Or, if you have ksh93 or zsh (not bash), you can use your shell's built-in arithmetic, which supports floating point numbers.
if ((min>val)); then ((val=min)); fi
For more advanced floating point calculations, look up bc. It actually works on arbitrary-precision fixpoint numbers.
To work on tables of numbers, look up R (example).
Solution 5
Use numeric sort
The command sort
has an option -g
(--general-numeric-sort
) that can be used for comparisons on <
, "less than" or >
, "larger than", by finding the minimum or maximum.
These examples are finding the minimum:
$ printf '12.45\n10.35\n' | sort -g | head -1
10.35
Supports E-Notation
It works with pretty general notation of floating point numbers, like with the E-Notation
$ printf '12.45E-10\n10.35\n' | sort -g | head -1
12.45E-10
Note the E-10
, making the first number 0.000000001245
, indeed less than 10.35
.
Can compare to infinity
The floating point standard, IEEE754, defines some special values. For these comparisons, the interesting ones are INF
for infinity. There is also the negative infinity; Both are well defined values in the standard.
$ printf 'INF\n10.35\n' | sort -g | head -1
10.35
$ printf '-INF\n10.35\n' | sort -g | head -1
-INF
To find the maximum use sort -gr
instead of sort -g
, reversing the sort order:
$ printf '12.45\n10.35\n' | sort -gr | head -1
12.45
Comparison operation
To implement the <
("less than") comparison, so it can be used in if
etc, compare the minimum to one of the values. If the minimum is equal to the value, compared as text, it is less than the other value:
$ a=12.45; b=10.35
$ [ "$a" = "$(printf "$a\n$b\n" | sort -g | head -1)" ]
$ echo $?
1
$ a=12.45; b=100.35
$ [ "$a" = "$(printf "$a\n$b\n" | sort -g | head -1)" ]
$ echo $?
0
Related videos on Youtube
RIchard Williams
Updated on September 18, 2022Comments
-
RIchard Williams almost 2 years
I want to compare two floating point numbers in a shell script. The following code is not working:
#!/bin/bash min=12.45 val=10.35 if (( $val < $min )) ; then min=$val fi echo $min
-
Gilles 'SO- stop being evil' over 12 yearsThis can't be fixed without a lot of work (try comparing
0.5
and0.06
). You'd better use a tool that already understands the decimal notation. -
ata over 12 yearsThanks Gilles, updated it to work more generally than the earlier version.
-
Stéphane Chazelas almost 10 yearsNote that the above ksh93 script only works in locales where the decimal separator is
.
(so not in half the world where the decimal separator is,
).zsh
doesn't have that issue. -
Stéphane Chazelas almost 10 yearsSetting LC_NUMERIC won't work if the user has set
LC_ALL
, that also means that numbers will not be displayed (or input) in the user's preferred format. See unix.stackexchange.com/questions/87745/what-does-lc-all-c-do/… for a potentially better approach. -
Stéphane Chazelas almost 10 yearsNote that it says that
1.00000000000000000000000001
is greater than2
. -
jlliagre almost 10 years@StéphaneChazelas fixed the LC_NUMERIC issue. Given the OP script syntax, I'm assuming his preferred separator is
.
anyway. -
Stéphane Chazelas almost 10 yearsYes, but it's the locale of the script user, not the locale of the script author that matters. As a script author, you should take localisation and its side effects into account.
-
ata almost 10 yearsStéphane is right. It's so because of bit limits in the number representation of bash. Of course, if you want more suffering you could use your own representation.... :)
-
Adamski over 9 yearsGood tip! I really like your insight that checking for
a == min(a, b)
is the same asa <= b
. It's worth noting that this doesn't check for strictly less than though. If you want to do that, you need to check fora == min(a, b) && a != max(a, b)
, in otherwordsa <= b and not a >= b
-
Atul Vekariya over 7 yearsCan you please comment your answer and add some explanations
-
Adrian Günter about 7 yearsGNU expr only supports arithmetic comparison on integers. Your example uses lexicographical comparison which will fail on negative numbers. For example,
expr 1.09 '<' -1.1
will print1
and exit with0
(success). -
bballdave025 about 4 yearsIngenious. This is so simple and computationally cheap.
-
xerostomus almost 3 yearsWhat is the advantage of use ${min} instead of $min?
-
Brandon over 2 years@xerostomus,
$min
is shorthand for${min}
. Using${}
lets you do extra things selecting a substring${PATH:6:7}
, but it is much more powerful than just that.