help-bash
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Help-bash] print float number


From: Bob Proulx
Subject: Re: [Help-bash] print float number
Date: Thu, 9 Jan 2014 11:58:05 -0700
User-agent: Mutt/1.5.21 (2010-09-15)

Greg Wooledge wrote:
> lina wrote:
> > How can I print the 0.05, 0.10 ... 1.0 out, with the step of 0.05
> 
> Floating point does weird things.

Weird at the top level that we look down on it from.  But it all makes
sense once you get to the bottom layer and look at the underlying bits
that the computer is dealing with. :-)

>   awk 'BEGIN {for (i=0.05; i<=1.0; i+=0.05) printf ("%.2f\n", i)}'
> 
> But when I test this, it doesn't print 1.0.  Why?  Because floating
> ...

Yes.  Floating point is only an approximation of reality.  It is never
suitable for use counting pennies in a dollar for which one does not
want an approximation but wants an exact counting.

> If you want to get the expected number of steps, it's better to do
> your counting with integers, and then present an altered view of
> the data as output only:
> 
>   awk 'BEGIN {for (i=1; i<=20; i++) printf ("%.2f\n", i/20.0)}'
> 
> This one gives the expected result.

That is an interesting floating point manipulation.  You are still
using floating point but it has moved to a different place in the
calculation.  Instead of doing repeated addition as floating point it
is doing a division as floating point.  (Over at "i/20.0".)  I think
there is still room for error to appear but simply a different class
of error at a different point in the calculation.  And the error
cannot accumulate which is very important.  Division is less likely to
cause any issue but multiplication there could be an issue.

To truly hold the data as a scaled integer and then print it with the
scaling it would need to be done only using integer math.  By some
method of splitting the integer up and inserting a decimal separator.
(I will use '.' with apologies to my friends who use other radixes.)
But this gets messy very quickly too.

This is one way doing pure character manipulations.

  for ((i=100; i<=110; i++)); do
    dollars=${i%%[0-9][0-9]}
    cents=$(echo $i | sed 's/.*\(..\)$/\1/')
    echo $dollars.$cents
  done

Or there is a pure integer math possibility.

  for ((i=100; i<=110; i++)); do 
    dollars=$(($i / 100))
    cents=$(($i % 100))
    printf "%d.%02d\n" $dollars $cents
  done

But dealing with scaled integers inline gets very messy very quickly
too.  One must be careful.

I am sure there is a better way to grab the last two characters from a
string.  Suggestions?  I thought only of this monstrosity.

  foo=12345
  echo ${#foo}
  5
  echo ${foo:$((${#foo} - 2))}

Making it quite an ugly and unobvious collection of modem line noise.

  for ((i=100; i<=110; i++)); do
    dollars=${i%%[0-9][0-9]}
    cents=${i:$((${#i} - 2))}
    echo $dollars.$cents
  done

> The important point here is that Bash cannot add, subtract, multiply or
> divide -- or even compare -- floating point numbers.  Any solution to
> your question is going to require an external command that CAN do these
> things (such as awk or seq).  I'd suggest awk as the go-to for this,
> because it is a standard tool.

I like using awk in shell scripts for exactly that reason.  It is a
portable standard command and available everywhere.  I almost
suggested it but frankly I was simply too lazy to work up an example
using it.  But awk is almost always a good solution.

I want to emphasize the need to avoid floating point, an
approximation, when exact values are needed.

Bob



reply via email to

[Prev in Thread] Current Thread [Next in Thread]