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
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
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.
You can use package num-utils for simple manipulations...
For more serious maths, see this link... It describes several options, eg.
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
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.
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
0.5
and 0.06
). You'd better use a tool that already understands the decimal notation.
– Gilles 'SO- stop being evil'
Nov 16 '11 at 23:47
1.00000000000000000000000001
is greater than 2
.
– Stéphane Chazelas
Jul 23 '14 at 12:57
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
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
.
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
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
a == min(a, b)
is the same as a <= 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 for a == min(a, b) && a != max(a, b)
, in otherwords a <= b and not a >= b
– Dave
Oct 27 '14 at 10:10
Just use ksh
(ksh93
precisely) or zsh
, which both natively support floating point arithmetics:
$ cat test.ksh
#!/bin/ksh
min=12.45
val=10.35
if (( $val < $min )) ; then
min=$val
fi
echo "$min"
$ ./test.ksh
10.35
Edit: Sorry, I missed ksh93
was already suggested. Keeping my answer just to make clear the script posted in the opening question can be used with no change outside the shell switch.
Edit2: Note that ksh93
requires the variable content to be consistent with your locale, i.e. with a French locale, a comma instead of a dot must be used:
...
min=12,45
val=10,35
...
A more robust solution is to set the locale at the beginning of the script to make sure it will work regardless of the user's locale:
...
export LC_ALL=C
min=12.45
val=10.35
...
.
(so not in half the world where the decimal separator is ,
). zsh
doesn't have that issue.
– Stéphane Chazelas
Jul 23 '14 at 06:20
LC_ALL
, that also means that numbers will not be displayed (or input) in the user's preferred format. See https://unix.stackexchange.com/questions/87745/what-does-lc-all-c-do/87763#87763 for a potentially better approach.
– Stéphane Chazelas
Jul 23 '14 at 12:20
.
anyway.
– jlliagre
Jul 23 '14 at 15:20
min=$(echo "${min}sa ${val}d la <a p" | dc)
That uses the dc
calculator to s
tore the value for $min
in register a
and d
uplicates the value of $val
onto the top of its main execution stack. It then l
ists the contents of a
onto the top of the stack, at which point it looks like:
${min} ${val} ${val}
The <
pops the top two entries off of the stack and compares them. So the stack then looks like:
${val}
If the top entry was less than the second to top it pushes the contents of a
onto the top, so the stack looks like:
${min} ${val}
Else it does nothing and the stack still looks like:
${val}
Then it just p
rints the top stack entry.
So for your problem:
min=12.45
val=12.35
echo "${min}sa ${val}d la <a p" | dc
###OUTPUT
12.35
But:
min=12.45
val=12.55
echo "${min}sa ${val}d la <a p" | dc
###OUTPUT
12.45
Why not to use old, good expr
?
Example syntax:
if expr 1.09 '>' 1.1 1>/dev/null; then
echo 'not greater'
fi
For true expressions, expr exit code is 0, with string '1' sent to stdout. Reverse for false expressions.
I've checked this with GNU and FreeBSD 8 expr.
expr 1.09 '<' -1.1
will print 1
and exit with 0
(success).
– Adrian Günter
May 06 '17 at 21:10
To check if two (possibly fractional) numbers are in order, sort
is (reasonably) portable:
min=12.45
val=12.55
if { echo $min ; echo $val ; } | sort -n -c 2>/dev/null
then
echo min is smallest
else
echo val is smallest
fi
However, if you actually want to keep a minimum value updated, then you don't need an if
. Sort the numbers, and always use the first (least) one:
min=12.45
val=12.55
smallest=$({ echo $min ; echo $val ; } |
sort -n | head -n 1)
echo $smallest
min=$smallest
Usually I do similar things with embedded python code :
#!/bin/sh
min=12.45
val=10.35
python - $min $val<<EOF
if ($min > $val):
print $min
else:
print $val
EOF
$ min=12.45
$ val=10.35
$ [ "$min" \< "$val" ] && echo $val || echo $min
$ 12.45
$ val=13
$ [ "$min" \< "$val" ] && echo $val || echo $min
$ 13
$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. – Brandon Jan 04 '22 at 20:44