adding floating point

A

Amer Neely

I've read the FAQ 'Why am I getting long decimals (eg, 19.9499999999999)
instead of the numbers I should be getting (eg, 19.95)?' and other
references, but am still confused by the results I'm getting.

#! /usr/bin/perl

BEGIN
{
open (STDERR,">>$0-err.txt");
print STDERR "\n",scalar localtime,"\n$0\n";
}

use strict;
use warnings;

unless ($#ARGV == 1)
{
print "Usage: calc subtotal shipping\n";
exit;
}

my $subtotal=$ARGV[0];
my $shipping=$ARGV[1];
#my $fuelfee = .1;
my $fuelfee = 10;

printf "%8.2f\n",$subtotal;
printf "%8.2f\n",$shipping;
printf "%8.2f\n",($shipping / $fuelfee); # when $fuelfee = 10
printf "%8.2f\n",($shipping / $fuelfee) + $shipping + $subtotal; # when
$fuelfee = 10
#printf "%8.2f\n",($shipping * $fuelfee); # when $fuelfee = .1;
#printf "%8.2f\n",(shipping * $fuelfee) + $shipping + $subtotal; # when
$fuelfee = .1

__END__
Subtotal: $ 185.63
Shipping: $ 24.15
Fuel Fee: $ 2.42
Total: $ 212.19

If you add the numbers above in your head or a calculator, you get 212.2.

So I'm confused why perl is out by a penny.

Is it related to the FAQ?

$fuelfee is being rounded up by perl from 2.415 to 2.42. But it appears
to be using 2.415 in the addition, to get 212.195 but then not rounding
that to 212.2.
--
Amer Neely
w: www.softouch.on.ca/
b: www.softouch.on.ca/blog/
Perl | MySQL programming for all data entry forms.
"We make web sites work!"
 
A

Amer Neely

Amer said:
I've read the FAQ 'Why am I getting long decimals (eg, 19.9499999999999)
instead of the numbers I should be getting (eg, 19.95)?' and other
references, but am still confused by the results I'm getting.

#! /usr/bin/perl

BEGIN
{
open (STDERR,">>$0-err.txt");
print STDERR "\n",scalar localtime,"\n$0\n";
}

use strict;
use warnings;

unless ($#ARGV == 1)
{
print "Usage: calc subtotal shipping\n";
exit;
}

my $subtotal=$ARGV[0];
my $shipping=$ARGV[1];
#my $fuelfee = .1;
my $fuelfee = 10;

printf "%8.2f\n",$subtotal;
printf "%8.2f\n",$shipping;
printf "%8.2f\n",($shipping / $fuelfee); # when $fuelfee = 10
printf "%8.2f\n",($shipping / $fuelfee) + $shipping + $subtotal; # when
$fuelfee = 10
#printf "%8.2f\n",($shipping * $fuelfee); # when $fuelfee = .1;
#printf "%8.2f\n",(shipping * $fuelfee) + $shipping + $subtotal; # when

There is a typo in my pasted code above. 'shipping' should obviously be
'$shipping'.

--
Amer Neely
w: www.softouch.on.ca/
b: www.softouch.on.ca/blog/
Perl | MySQL programming for all data entry forms.
"We make web sites work!"
 
J

Jürgen Exner

Amer said:
I've read the FAQ 'Why am I getting long decimals (eg,
19.9499999999999) instead of the numbers I should be getting (eg,
19.95)?' and other references, but am still confused by the results [...]
Total: $ 212.19

If you add the numbers above in your head or a calculator, you get
212.2.
So I'm confused why perl is out by a penny.

Is it related to the FAQ?

Yes. You must have missed "Introduction to Computer Numerics".
This has nothing to do with Perl but everything with how decimal numbers are
represented internally in a computer. You would get the same result in any
other programming language unless it is a language that is specifically
designed to handle numerical problems.

jue
 
A

Amer Neely

Bob said:
Amer said:
Amer said:
I've read the FAQ 'Why am I getting long decimals (eg,
19.9499999999999) instead of the numbers I should be getting (eg,
19.95)?' and other references, but am still confused by the results
I'm getting.

#! /usr/bin/perl

BEGIN
{
open (STDERR,">>$0-err.txt");
print STDERR "\n",scalar localtime,"\n$0\n";
}

use strict;
use warnings;

unless ($#ARGV == 1)
{
print "Usage: calc subtotal shipping\n";
exit;
}

my $subtotal=$ARGV[0];
my $shipping=$ARGV[1];
#my $fuelfee = .1;
my $fuelfee = 10;

printf "%8.2f\n",$subtotal;
printf "%8.2f\n",$shipping;
printf "%8.2f\n",($shipping / $fuelfee); # when $fuelfee = 10
printf "%8.2f\n",($shipping / $fuelfee) + $shipping + $subtotal; #
when $fuelfee = 10
#printf "%8.2f\n",($shipping * $fuelfee); # when $fuelfee = .1;
#printf "%8.2f\n",(shipping * $fuelfee) + $shipping + $subtotal; # when

There is a typo in my pasted code above. 'shipping' should obviously
be '$shipping'.
That's why you should always copy/paste your real working tested code
into newsgroup notes, as suggested by the posting guidelines, rather
than retyping it.

You missed the part where I said '...pasted code'. I *did* paste it in,
but the original was wrong as well.
I'm not sure how much more clear a statement could be made than the FAQ
on floating point rounding. Note that this is not an issue with Perl or
any other computing language, but an issue with the computer hardware
which performs floating point arithmetic: IT IS APPROXIMATE. IT IS NOT
EXACT. ROUNDING MAY NOT HAPPEN THE WAY YOU EXPECT OR WISH. RESULTS MAY
DIFFER ON DIFFERENT HARDWARE PLATFORMS AND FLOATING POINT INSTRUCTION SETS.

Numbers such as the decimal number 0.1 are not represented exactly.
Print it out with:

D:\JUNK>perl -e "print '%50.30f',0.1";
0.100000000000000010000000000000

if you don't believe me.

If you perform "exact" decimal arithmetic on your formula above with
your example numbers, you get 212.195 -- and which way to round it to
two places is up to the user (yeah, some folks may have preferences, but
then someone else will prefer the other way). With floating point
arithmetic rather than exact decimal arithmetic, you may get something
like 212.194999999999 (which will round to 212.19) or 212.195000000001
(which will round to 212.20) -- with which one depending on your
hardware platform -- my PC generates 212.194999999999999000000... if I
print it with %50.30f for a format string, and 212.19 with %.2f as a
format string. If you want exact arithmetic, then you have to use the
facilities of your computer which accomplish exact arithmetic:
integers. In Perl, that is:

use integer;

Note that it is tricky business to correctly perform exact arithmetic,
particularly on financial computations where someone is auditing every
penny. Best to leave such to the pros.

HTH

Thanks for the clarification. I'll try running a round routine on it.
--
Amer Neely
w: www.softouch.on.ca/
b: www.softouch.on.ca/blog/
Perl | MySQL programming for all data entry forms.
"We make web sites work!"
 
T

Tad McClellan

Amer Neely said:
Amer Neely wrote:

There is a typo in my pasted code above. 'shipping' should obviously be
'$shipping'.


A typo in a comment will not have any effect on program execution. :)
 
T

Tad McClellan

Amer Neely said:
I've read the FAQ 'Why am I getting long decimals (eg, 19.9499999999999)
instead of the numbers I should be getting (eg, 19.95)?' and other
references, but am still confused by the results I'm getting.


If you can arrange to use integer data rather than floating point data,
then you wouldn't really need to understand the number theory issues
of accuracy and precision. See below.

unless ($#ARGV == 1)


That means "if there are not 2 command line arguments".

A more natural what of writing that (to me anyway) would be:

if ( @ARGV != 2 )

Total: $ 212.19
So I'm confused why perl is out by a penny.


Since your data appears to be in units of (floating point) dollars,
you should consider instead using units of (integer) cents in
all of your calculations, then convert to dollars only for
final output.

If you need it to be dead-accurate (as is often the case when the
data is money :), then take care to perform only integer arithmetic
(eg. watch for division operations).
 
A

Amer Neely

Abigail said:
Amer Neely ([email protected]) wrote on MMMMDCCCXCVII September
MCMXCIII in <URL:## I've read the FAQ 'Why am I getting long decimals (eg, 19.9499999999999)
## instead of the numbers I should be getting (eg, 19.95)?' and other
## references, but am still confused by the results I'm getting.
##
## #! /usr/bin/perl
##
## BEGIN
## {
## open (STDERR,">>$0-err.txt");
## print STDERR "\n",scalar localtime,"\n$0\n";
## }
##
## use strict;
## use warnings;
##
## unless ($#ARGV == 1)
## {
## print "Usage: calc subtotal shipping\n";
## exit;
## }
##
## my $subtotal=$ARGV[0];
## my $shipping=$ARGV[1];
## #my $fuelfee = .1;
## my $fuelfee = 10;
##
## printf "%8.2f\n",$subtotal;
## printf "%8.2f\n",$shipping;
## printf "%8.2f\n",($shipping / $fuelfee); # when $fuelfee = 10
## printf "%8.2f\n",($shipping / $fuelfee) + $shipping + $subtotal; # when
## $fuelfee = 10
## #printf "%8.2f\n",($shipping * $fuelfee); # when $fuelfee = .1;
## #printf "%8.2f\n",(shipping * $fuelfee) + $shipping + $subtotal; # when
## $fuelfee = .1
##
## __END__
## Subtotal: $ 185.63
## Shipping: $ 24.15
## Fuel Fee: $ 2.42
## Total: $ 212.19
##
## If you add the numbers above in your head or a calculator, you get 212.2.

Yes.

## So I'm confused why perl is out by a penny.
##
## Is it related to the FAQ?

Not really.

## $fuelfee is being rounded up by perl from 2.415 to 2.42.

No, it's not. $fuelfee = 10 and doesn't change. But Perl also doesn't
round $shipping / $fuelfee to 2.42. What it does is round what it
*displays*.

## But it appears
## to be using 2.415 in the addition, to get 212.195 but then not rounding
## that to 212.2.

That's the difference between rounding intermediate results and rounding
the final results. If you want to get 212.20 as an answer, you must round
the intermediate values as well:

printf "%8.2f" => sprintf ("%8.2f" => $shipping / $fuelfee)
+ $shipping + $subtotal;


In general, the following is not true:

f(a) op f(b) = f(a op b)

and that's what you were doing, with 'op' addition, and 'f' printf's
rounding functionality.


Abigail

OK, this is making more sense now. I was confusing the 'display' value
with the underlying real one (no pun intended). I've managed to get it
working by applying a rounding operation to each value along the way.
But if I understand, printf (or sprintf) to each value would achieve the
same? I'll try that next. Thanks :)

--
Amer Neely
w: www.softouch.on.ca/
b: www.softouch.on.ca/blog/
Perl | MySQL programming for all data entry forms.
"We make web sites work!"
 
A

Amer Neely

Tad said:
If you can arrange to use integer data rather than floating point data,
then you wouldn't really need to understand the number theory issues
of accuracy and precision. See below.

Hmm. Yes, I can see that would work. I may give that a try.
That means "if there are not 2 command line arguments".

A more natural what of writing that (to me anyway) would be:

if ( @ARGV != 2 )





Since your data appears to be in units of (floating point) dollars,
you should consider instead using units of (integer) cents in
all of your calculations, then convert to dollars only for
final output.

If you need it to be dead-accurate (as is often the case when the
data is money :), then take care to perform only integer arithmetic
(eg. watch for division operations).

I've since applied a rounding function to each operation along the way,
instead of the final result as pointed out by Abigail, and got it to
work (at least for this example). I'll test a few more examples and see
what happens. Thanks for clearing up the mud :)

--
Amer Neely
w: www.softouch.on.ca/
b: www.softouch.on.ca/blog/
Perl | MySQL programming for all data entry forms.
"We make web sites work!"
 
J

Jürgen Exner

Amer said:
OK, this is making more sense now. I was confusing the 'display' value
with the underlying real one (no pun intended). I've managed to get it
working by applying a rounding operation to each value along the way.

That is not a good idea. You are replacing one place of introducing
inaccuracy with numerous places of introducing inaccuracy.

There are two options:
- Live with 'real' numbers being inaccurate
- use a different representation for your values, e.g. compute your prices
in pennies instead of in dollars. And convert them to dollar for display
only. Natural numbers can be represented accurately without approximation.

jue
 
A

Amer Neely

Jürgen Exner said:
That is not a good idea. You are replacing one place of introducing
inaccuracy with numerous places of introducing inaccuracy.

There are two options:
- Live with 'real' numbers being inaccurate
- use a different representation for your values, e.g. compute your prices
in pennies instead of in dollars. And convert them to dollar for display
only. Natural numbers can be represented accurately without approximation.

jue

I understand what you are saying, but I don't think this application
requires the degree of accuracy you are considering. I only need to
reflect what someone with a calculator, or using their head, would
arrive at, applying standard rounding.

Now, if this were a financial investment application, I wouldn't even
consider doing it myself.

--
Amer Neely
w: www.softouch.on.ca/
b: www.softouch.on.ca/blog/
Perl | MySQL programming for all data entry forms.
"We make web sites work!"
 
A

Amer Neely

Abigail said:
Amer Neely ([email protected]) wrote on MMMMDCCCXCVII September
MCMXCIII in <URL:~~
~~ OK, this is making more sense now. I was confusing the 'display' value
~~ with the underlying real one (no pun intended). I've managed to get it
~~ working by applying a rounding operation to each value along the way.
~~ But if I understand, printf (or sprintf) to each value would achieve the
~~ same? I'll try that next. Thanks :)


No, I think you're making a mistake. Using a variable as argument to
printf (or sprintf) doesn't change the value of that variable. printf
prints a *different* value (2.42 instead of 2.415 in this case).



Abigail

Yes, which is what I would expect from a rounding function.

I've used some of the suggestions and am now getting a 'more' correct
answer. 2.42 is what I would expect to see on an invoice, not 2.415,
even though that is the mathematically correct value. But in most
day-to-day financial transactions, we round up or down, and this is the
'right' answer as far as I can see - the addition is now correct.

#! /usr/bin/perl

BEGIN
{
open (STDERR,">>$0-err.txt");
print STDERR "\n",scalar localtime,"\n$0\n";
}

use strict;
use warnings;
unless ($#ARGV == 1)
{
print "Usage: calc subtotal shipping\n";
exit;
}
my $subtotal=$ARGV[0];
my $shipping=$ARGV[1];
my $fuelfee=($shipping / 10);
printf "%8.2f\n",$subtotal;
printf "%8.2f\n",$shipping;
printf "%8.2f\n",$fuelfee;
my $newtotal=round( $subtotal + $shipping + ($shipping / 10) ,2);
print "=" x 8,"\n";
## from Abigail
printf qq{%8.2f} => sprintf (qq{%8.2f} => 24.25 / 10) + 24.15 + 185.63;

sub round
{
my ($n, $p) = @_;
$p ||= 0;
int($n * 10**$p + .5 * ($n < 0 ? -1 : 1)) / 10**$p;
}


--
Amer Neely
w: www.softouch.on.ca/
b: www.softouch.on.ca/blog/
Perl | MySQL programming for all data entry forms.
"We make web sites work!"
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,051
Latest member
CarleyMcCr

Latest Threads

Top