Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Compare decimal numbers in a shell script?

350 views
Skip to first unread message

Ian Gil

unread,
Jun 19, 2003, 5:40:20 AM6/19/03
to
I need to compare decimal numbers in a script to have something like

if $NUMBER -lt .04 echo Lower

When $NUMBER is a decimal I get a "integer expression expected" error.

Ian

Chris F.A. Johnson

unread,
Jun 19, 2003, 8:31:22 AM6/19/03
to

Most shells can only deal with integers (ksh93, available from
kornshell.com, is an exception).

Sometimes, comparing just the integer portion is accurate enough,
If that's the case:

[ ${NUMBER1%.*} -lt ${NUMBER2%.*} ] && echo Lower


If a comparison of the decimal portion is important, use a tool
that understands decimal fractions, such as awk or bc; or, these
shell functions will do the job.

int - return the integer portion of a decimal number
return 0 if there is no integer portion

frac - return the decimal portion of a decimal number
return 0 if there is no decimal portion

fpcompare - if the first argument is greater than the second, return 1
if the first argument is less than the second, return -1
if the two arguments are equal, return 0

The result of each function is placed in a variable whose name is
the name of the function converted to upper case and preceded by an
underscore (e.g., int stores its result in _INT).

The result is echoed if _SILENT_FUNCS is not set to 1.


### the functions:
int() {
_INT=0
case $1 in
.*|"") _INT=0 ;;
*.*) _INT=${1%.*} ;;
*) _INT=$1 ;;
esac
[ "$_SILENT_FUNCS" = 1 ] || echo ${_INT}
}

dec() {
_DEC=0
case $1 in
.*) _DEC=${1#?} ;;
*.) _DEC=0 ;;
*.*) _DEC=${1#*.} ;;
esac
[ "$_SILENT_FUNCS" = 1 ] || echo ${_DEC}
}

fpcompare() {
[ $# = 2 ] || { _FPCOMPARE=5; return 5; }
neg1=
neg2=
case $1 in -*) neg1=-; num1=${1#-} ;; esac
case $2 in -*) neg2=-; num2=${2#-} ;; esac
_FPCOMPARE=0
_SILENT_FUNCS=1 int $num1
int1=$_INT
_SILENT_FUNCS=1 int $num2
int2=$_INT
if [ "$neg1$int1" -gt "$neg2$int2" ]
then
_FPCOMPARE=1
elif [ "$neg1$int1" -lt "$neg2$int2" ]
then
_FPCOMPARE=-1
else
_SILENT_FUNCS=1 dec $1
dec1=$_DEC
_SILENT_FUNCS=1 dec $2
dec2=$_DEC
while [ ${#dec1} -ne ${#dec2} ]
do
[ ${#dec1} -gt ${#dec2} ] && dec2=${dec2}0
[ ${#dec2} -gt ${#dec1} ] && dec1=${dec1}0
done
if [ "$neg1$dec1" -gt "$neg2$dec2" ]
then
_FPCOMPARE=1
elif [ "$neg1$dec1" -lt "$neg2$dec2" ]
then
_FPCOMPARE=-1
fi
fi
[ "$_SILENT_FUNCS" = 1 ] || echo ${_FPCOMPARE}
}

--
Chris F.A. Johnson http://cfaj.freeshell.org
===================================================================
My code (if any) in this post is copyright 2003, Chris F.A. Johnson
and may be copied under the terms of the GNU General Public License

Peter T. Breuer

unread,
Jun 19, 2003, 9:23:40 AM6/19/03
to
Ian Gil <i...@nospamallowed.com> wrote:
> I need to compare decimal numbers in a script to have something like

Bash does not do floating point arithmetic. Use bc or something.

> if $NUMBER -lt .04 echo Lower

Peter

ne...@roaima.freeserve.co.uk

unread,
Jun 19, 2003, 9:52:33 AM6/19/03
to
Chris F.A. Johnson <c.f.a....@rogers.com> wrote:
> fpcompare - if the first argument is greater than the second, return 1
> if the first argument is less than the second, return -1
> if the two arguments are equal, return 0

[interesting samples deleted]

Often you can use the power of other utilities, rather than being
restricted to pure shell builtins:

fpcompare() {
_FPCOMPARE=`echo $1 $2 - p | dc`
if test 0 == $_FPCOMPARE
then
echo 0
else
echo X $_FPCOMPARE | grep -- - >/dev/null && echo -1 || echo 1
fi
}

For the purposes of the illustration my code ignores CFAJ's _SILENT_FUNCS
flag, which would be trivial to incorporate if required.

Regards,
Chris
--
@s=split(//,"Je,\nhn ersloak rcet thuarP");$k=$l=@s;for(;$k;$k--){$i=($i+1)%$l
until$s[$i];$c=$s[$i];print$c;undef$s[$i];$i=($i+(ord$c))%$l}

Jean-David Beyer

unread,
Jun 19, 2003, 10:31:19 AM6/19/03
to

Depending on your requirements, you might do

if ($NUMBER * 100) -lt 4) echo Lower

I assume earlier your shell script says something like:

declare -i NUMBER

--
.~. Jean-David Beyer Registered Linux User 85642.
/V\ Registered Machine 73926.
/( )\ Shrewsbury, New Jersey http://counter.li.org
^^-^^ 10:25am up 1 day, 13:00, 3 users, load average: 2.13, 2.14, 2.12

Chris F.A. Johnson

unread,
Jun 19, 2003, 10:05:36 PM6/19/03
to
On Thu, 19 Jun 2003 at 13:52 GMT, ne...@roaima.freeserve.co.uk wrote:
> Chris F.A. Johnson <c.f.a....@rogers.com> wrote:

>> fpcompare - if the first argument is greater than the second, return 1
>> if the first argument is less than the second, return -1
>> if the two arguments are equal, return 0
>
> [interesting samples deleted]

Also deleted was:

|| If a comparison of the decimal portion is important, use a tool

|| that understands decimal fractions, such as awk or bc; ....

> Often you can use the power of other utilities, rather than being
> restricted to pure shell builtins:
>
> fpcompare() {
> _FPCOMPARE=`echo $1 $2 - p | dc`
> if test 0 == $_FPCOMPARE
> then
> echo 0
> else
> echo X $_FPCOMPARE | grep -- - >/dev/null && echo -1 || echo 1
> fi
> }

$ fpcompare -12.14244 -12.33
dc: stack empty
dc: stack empty
-1

man dc:

To enter a negative number, begin the number with ``_''.
``-'' cannot be used for this, as it is a binary operator for
subtraction instead.


Try:

_FPCOMPARE=`echo "${1/-/_} ${2/-/_} - p" | dc`

Or use bc:

_FPCOMPARE=`echo $1 - $2 | bc`


And there's no need for grep:

fpcompare() {
case `echo $1 - $2 | bc` in
-*) _FPCOMPARE=-1 ;;
0) _FPCOMPARE=0 ;;
*) _FPCOMPARE=1 ;;
esac
[ "$_SILENT_FUNCS" = 1 ] || echo ${_FPCOMPARE}
}


The fastest external command for this is awk:

fpcompare() {
_FPCOMPARE=`awk "BEGIN { x = $1 - $2
if ( x == 0 ) print 0
else if ( x < 0 ) print -1
else print 1
}"`
[ "$_SILENT_FUNCS" = 1 ] || echo $_FPCOMPARE
}

> For the purposes of the illustration my code ignores CFAJ's _SILENT_FUNCS
> flag, which would be trivial to incorporate if required.

ne...@roaima.freeserve.co.uk

unread,
Jun 20, 2003, 11:35:28 AM6/20/03
to
Chris F.A. Johnson <c.f.a....@rogers.com> wrote:
> || If a comparison of the decimal portion is important, use a tool
> || that understands decimal fractions, such as awk or bc; ....

Yes... I should probably have used bc in this case. However, bc only
works to the number of decimal places provided in its input, and I've
been bitten by echo 1 / 3 | bc ==> 0 type problems so many times I have
an anathema to using it in anything!

> To enter a negative number, begin the number with ``_''.
> ``-'' cannot be used for this, as it is a binary operator for
> subtraction instead.

Forgot that bit :-)

> _FPCOMPARE=`echo "${1/-/_} ${2/-/_} - p" | dc`

Ah. I don't generally use bash-isms: I end up using bash, ksh, sh on
different *IX platforms, so I tend to operate at sh level when writing
scripts. (Yes, I know this is coLm.)

Regards

David Cook

unread,
Jun 20, 2003, 12:57:33 PM6/20/03
to
In article <07mbs-...@moldev.cmagroup.co.uk>,

<chris-...@roaima.co.uk> wrote:
>Chris F.A. Johnson <c.f.a....@rogers.com> wrote:
>> || If a comparison of the decimal portion is important, use a tool
>> || that understands decimal fractions, such as awk or bc; ....
>
>Yes... I should probably have used bc in this case. However, bc only
>works to the number of decimal places provided in its input, and I've
>been bitten by echo 1 / 3 | bc ==> 0 type problems so many times I have
>an anathema to using it in anything!

I used to be bitten by that behaviour, until I discovered the "-l" option :

$ echo "1 / 3" | bc
0

$ echo "1 / 3" | bc -l
.33333333333333333333

(I've seen this on HP-UX too, so it's not just a GNU-ism)

David.


--
(david.cook at pobox.com)
(in Melbourne, Australia)

Chris F.A. Johnson

unread,
Jun 20, 2003, 11:02:49 PM6/20/03
to
On Fri, 20 Jun 2003 at 15:35 GMT, ne...@roaima.freeserve.co.uk wrote:
> Chris F.A. Johnson <c.f.a....@rogers.com> wrote:
>> || If a comparison of the decimal portion is important, use a tool
>> || that understands decimal fractions, such as awk or bc; ....
>
> Yes... I should probably have used bc in this case. However, bc only
> works to the number of decimal places provided in its input, and I've
> been bitten by echo 1 / 3 | bc ==> 0 type problems so many times I have
> an anathema to using it in anything!

Right; my bc example should have been:

_FPCOMPARE=`echo $1 - $2 | bc -l`

>> To enter a negative number, begin the number with ``_''.
>> ``-'' cannot be used for this, as it is a binary operator for
>> subtraction instead.
>
> Forgot that bit :-)
>
>> _FPCOMPARE=`echo "${1/-/_} ${2/-/_} - p" | dc`
>
> Ah. I don't generally use bash-isms: I end up using bash, ksh, sh on
> different *IX platforms, so I tend to operate at sh level when writing
> scripts. (Yes, I know this is coLm.)

I generally do use POSIX extensions, but restrict those to
arithmetic and variable expansion. Unless I am writing a script
that really cannot be written in a Bourne, or at most POSIX,
shell, I avoid bashisms.

Anonymous

unread,
Jun 23, 2003, 7:20:44 PM6/23/03
to
"IG" == Ian Gil <i...@NOSPAMALLOWED.com>:
IG> I need to compare decimal numbers in a script to have something like
IG>
IG> if $NUMBER -lt .04 echo Lower
IG>
IG> When $NUMBER is a decimal I get a "integer expression expected" error.

Use 'awk':

$ NUMBER=1; ! awk -v n=$NUMBER 'BEGIN{exit (n < .04)}' && echo Lower
$ NUMBER=0.01;! awk -v n=$NUMBER 'BEGIN{exit (n < .04)}' && echo Lower
Lower

Ian Gil

unread,
Jun 30, 2003, 1:17:07 AM6/30/03
to
>> I need to compare decimal numbers in a script to have something like
>>
>> if $NUMBER -lt .04 echo Lower
>>
>> When $NUMBER is a decimal I get a "integer expression expected" error.
>
> Use 'awk':
>
> $ NUMBER=1; ! awk -v n=$NUMBER 'BEGIN{exit (n < .04)}' && echo Lower
> $ NUMBER=0.01;! awk -v n=$NUMBER 'BEGIN{exit (n < .04)}' && echo Lower
> Lower

Nice.

What is the ! for? It doesn't work without it.


Thanks,

Ian

Chris F.A. Johnson

unread,
Jun 30, 2003, 2:26:36 AM6/30/03
to

It returns the logical NOT of the exit status value of the
following command (or last command if it precedes a pipeline).

It can be written without it:

NUMBER=0.01; awk -v n=$NUMBER 'BEGIN{exit (n < .04)}' || echo Lower

Or, less verbosely:

NUMBER=0.01; awk "BEGIN{exit ($NUMBER < .04)}" || echo Lower

Or:

NUMBER=0.01; awk "BEGIN {if ($NUMBER < .04) print \"Lower\"}"

Ed Murphy

unread,
Jun 30, 2003, 2:31:31 AM6/30/03
to
On Mon, 30 Jun 2003 05:17:07 +0000, Ian Gil wrote:

>> $ NUMBER=1; ! awk -v n=$NUMBER 'BEGIN{exit (n < .04)}' && echo Lower
>> $ NUMBER=0.01;! awk -v n=$NUMBER 'BEGIN{exit (n < .04)}' && echo Lower
>> Lower

> What is the ! for? It doesn't work without it.

a) Apparently, "exit(v)" causes awk to return an exit status of v. In
particular, v = 1 if (n < .04) is true, or 0 if it is false.

b) In "x && y", the shell executes y if x has a successful (i.e. zero)
exit status, or skips y if x is unsuccessful (non-zero).

c) The result of a) is precisely opposite to what you want for b), so ! is
used to reverse it. Basically, b) becomes "(not x) && y", or - if
you're familiar with C syntax - "!x && y".

0 new messages