Rounding issue

M

Marten Lehmann

Hello,

I don't understand why

perl -e 'print sprintf("%.0f", 1.5). "\n"'

returns 2 as expected, but

perl -e 'print sprintf("%.1f", 0.15). "\n"'

returns 0.1, whereby I expected to get 0.2.

What am I doing wrong? What do I have to do to get 0.2?

Kind regards
Marten Lehmann
 
J

Jim Gibson

Marten Lehmann said:
Hello,

I don't understand why

perl -e 'print sprintf("%.0f", 1.5). "\n"'

returns 2 as expected, but

perl -e 'print sprintf("%.1f", 0.15). "\n"'

returns 0.1, whereby I expected to get 0.2.

What am I doing wrong? What do I have to do to get 0.2?

See the advice in 'perldoc -q round'

Observe that

perl -e '$x=0.15; printf "%.20f\n", $x;'

produces:

0.14999999999999999445

so should round to 0.1.
 
J

Jürgen Exner

Marten Lehmann said:
I don't understand why

perl -e 'print sprintf("%.0f", 1.5). "\n"'

returns 2 as expected, but

perl -e 'print sprintf("%.1f", 0.15). "\n"'

returns 0.1, whereby I expected to get 0.2.

What am I doing wrong? What do I have to do to get 0.2?

Print both numbers with 20 digits and note the difference.

For further information please see 'perldoc -q 999' or revisit your
notes on your "Basics of Computer Numerics" class.

jue

PS: you could argue that 1.5 should have been rounded down to 1 because
the 1.5 is exactly 5 with all following digits being 0, but alas, that's
not how it's implemented.

jue
 
M

Marten Lehmann

Hello,
~% perl -E'say sprintf "%.40f", 0.15'
0.1499999999999999944488848768742172978818

why isn't it stored as 0.150000000000000 internally?
This is less than 0.15, so it rounds to 0.1.

This is explains why 0.149 doesn't round to 0.2, but it doesn't explain
why 1.5 works different than 0.15 wuth just one floating point digit
difference.
Where is this number coming from? Why do you need it to round to 0.2?

In this specific case I hardcoded it into the source. But in the real
application it is coming from a database and then some tax calculations
are done and in accounting every cent difference causes headaches.

Why does

perl -e 'print sprintf("%.1f", 0.10 + 0.05). "\n"'

work fine?

Kind regards
Marten
 
J

Jürgen Exner

Marten Lehmann said:
why isn't it stored as 0.150000000000000 internally?


This is explains why 0.149 doesn't round to 0.2, but it doesn't explain
why 1.5 works different than 0.15 wuth just one floating point digit
difference.

Please re-read your notes from "Basics of Computer Numerics".

Hint: Typical computers are binary, they don't use the decimal system.
In this specific case I hardcoded it into the source. But in the real
application it is coming from a database and then some tax calculations
are done and in accounting every cent difference causes headaches.

That is why using floating point numbers for accounting is A VERY Bad
Idea(TM) and very strongly discouraged. Didn't your computer numerics
teacher tell you so?
Why does
perl -e 'print sprintf("%.1f", 0.10 + 0.05). "\n"'
work fine?

Again: print the result of that addition with 20 digits and you will see
why.

jue
 
J

Jens Thoms Toerring

why isn't it stored as 0.150000000000000 internally?
This is explains why 0.149 doesn't round to 0.2, but it doesn't explain
why 1.5 works different than 0.15 wuth just one floating point digit
difference.

Floating point numbers are stored as binary numbers. And, just
like you can't write a lot of numbers in a 10 based system
(e.g. one third) with a limited number of digits, you can't
store a lot of numbers in a binary system with a limited
number of bits. Thus the overwhelming majority of floating
point numbers are only approximations. For example 0.1 looks
like a simple number when written in a 10 based number system,
but when you would try to write it in a 2 based system you
would need an infinite number of bits. On the other hand,
a number like 1.0/3.0 would require an infinite number of
digits when written in a 10 based system but would be a
very "simple" number when you would write in base 3. (In
base 10 all rational numbers that are a fraction with the
denominator being the product of a power of 2 and a power
of 5 can be written with a limited number of digits, in
base 2 only those that have a power of 2 as the denominator.)

Thus, 1.5 can be stored precisely in base 2 with the limited
number of bits you have for a floating point number while
0.15 can't. Thus 0.15 can only be represented by an appro-
ximation which then leads to the problem you observed.
In this specific case I hardcoded it into the source. But in the real
application it is coming from a database and then some tax calculations
are done and in accounting every cent difference causes headaches.
perl -e 'print sprintf("%.1f", 0.10 + 0.05). "\n"'

Here a further problem with using floating point numbers
is at work. Neither 0.1 nor 0.05 can be stored precisely.
So you are already starting of with imprecise numbers. And
when you add those imprecise numbers the errors can add up!
And the more calculations you do the larger the error can
become.

In your example one result is that 0.15 isn't equal to
0.1 + 0.05. Thus a rule of thumb is never to try to
compare floating point numbers for equality since the
result hardly ever is what you expect.

But even with numbers that can be represented exactly there
can be problems. On my machine I get

jens@cm:~$ perl -e 'print sprintf("%.1f", 0.25). "\n"'
0.2
jens@cm:~$ perl -e 'print sprintf("%.1f", 0.75). "\n"'
0.8

While the 0.8 result for 0.75 is what one would expect,
the 0.2 for 0.25 isn't. The explanation is probably that
it's the result of rounding errors introduced when the
digits to be displayed are calculated.

If you want the precision you're looking for you probably
shouldn't use floating point numbers at all! If you want
e.g. 2 digits after the decimal point then "scale" your
calculations by a factor of 100, so that everything can
be done with integers.

Welcome to the wonderful world of floating point calculations;-)

Regards, Jens
 
M

Marten Lehmann

Hello,
Please re-read your notes from "Basics of Computer Numerics".

although I know about the difference between an internal representation
and the number itself, I don't want to care about that the whole day.
All I'm expecting is the programming language to remember the precision
I used when a value was stored. 1.5 is not an infinite value as 1/3.

Kind regards
Marten
 
R

Randal L. Schwartz

Marten> All I'm expecting is the programming language to remember the
Marten> precision I used when a value was stored. 1.5 is not an infinite value
Marten> as 1/3.

But it is. The moment 0.15 is "stored", it's a truncation of an infinite
binary value, and is therefore not precisely 0.15 any more.

(And actually, 1.5 *can* be stored precisely, so you must've gotten mixed up
in this thread.)

print "Just another Perl hacker,"; # the original
 
P

Peter J. Holzer

although I know about the difference between an internal representation
and the number itself, I don't want to care about that the whole day.

Then you shouldn't do numeric work.
All I'm expecting is the programming language to remember the precision
I used when a value was stored.

You'll be disappointed. Almost no programming language does this. At
least not for basic numeric types. Some programming languages may offer
a "rational" type (Perl has "BigRat").
1.5 is not an infinite value as 1/3.

You mean "periodic", not "infinite". Infinite is for example the number
of natural numbers.

1/3 is a periodic number in base 10 or base 2.
It is not a periodic number in base 3.

1/10 is a periodic number in base 2 or base 3.
It is not a periodic number in base 10.

1/2 is a periodic number in base 3.
It is not a periodic number in base 2 or 10.

hp
 
J

Jens Thoms Toerring

Marten Lehmann said:
although I know about the difference between an internal representation
and the number itself, I don't want to care about that the whole day.

Then you better don't use floating point numbers;-)
All I'm expecting is the programming language to remember the precision
I used when a value was stored. 1.5 is not an infinite value as 1/3.

And you don't have the problem with 1.5, you have it with 0.15,
which is a number that requires an infinite number of bits to
be represented exactly in binary...

It's not a question of the programming language - you will have
the same problem in all programming languages that allow you
to use floating point numbers. The first thing is that the same
problems also appears if you represent numbers in base 10 -
1/3 is the simplest example - you're used to it and thus don't
wonder what's going on. Of course, you can use Perl (or any
other language) to represent numbers in a 10 based system
(and some processors even have some kind of support for it,
see for example BCD (binary coded decimals)). But you then
have to write functions for all the arithmetic operators
printing etc. etc. and the computation speed will probably
be rather abysimal.

But the fundamental problem remains. You have an infinite
number of floating point numbers that must be represented
with a limited number of bits. Thus except for an infinitely
small fraction of them you have just approximations. And
once you have only an approximation there's no going back to
the exact number you started of with.

Asking for 0.1 + 0.05 to be output after rounding as "0.2"
when the numbers are represented in binary is exactly the
same as asking for 2.0/6.0 + 1.0/6.0 to be output as "1"
after rounding when using a base 10 representation with a
limited number of digits - it simply can't be done. That
you accept the problem when using base 10 but not when
using base 2 is just a result of being used to the limi-
tations of base 10 but less to that of base 2.

Regards, Jens
 
S

sln

Hello,

I don't understand why

perl -e 'print sprintf("%.0f", 1.5). "\n"'

returns 2 as expected, but

perl -e 'print sprintf("%.1f", 0.15). "\n"'

returns 0.1, whereby I expected to get 0.2.

What am I doing wrong? What do I have to do to get 0.2?

Kind regards
Marten Lehmann

As Ben said, the 1,5 is not really 1.5 in floating point terms.

However, this below gets what you want.

If your application is accounting, and your doing discreet
calculations that in PRINT you want them to add up to a correct
"visual" total, then this is what you want.

The reassignment of course is the $newval = sprintf();
You must make sure you are only dealing with fractions of cent
and that it would not be the case of it being used itteratively
in a future calculation.

At least, I wouldn't want my bank to.

-sln
=======================

use strict;
use warnings;

my $f1 = 1.5;
my $f2 = .15;

print sprintf("%.0f", $f1). "\n";
print sprintf("%.1f", $f2). "\n";
print "\n";
print sprintf("%.0f", (($f1 * 100)+5)/100 ). "\n";
print sprintf("%.1f", (($f2 * 100)+5)/100). "\n";

__END__
Output:

2
0.1

2
0.2
 
J

Jürgen Exner

Marten Lehmann said:
although I know about the difference between an internal representation
and the number itself, I don't want to care about that the whole day.
All I'm expecting is the programming language to remember the precision
I used when a value was stored. 1.5 is not an infinite value as 1/3.

Yes, it is when stored in binary format as virtually all computers do.
And it has nothing to do with the particular programming language but
with how your typical computer works.

Please re-read your notes from "Basics of Computer Numerics"

jue
 
S

sln

=======================

use strict;
use warnings;

my $f1 = 1.5;
my $f2 = .15;

print sprintf("%.0f", $f1). "\n";
print sprintf("%.1f", $f2). "\n";
print "\n";
print sprintf("%.0f", (($f1 * 100)+5)/100 ). "\n";
print sprintf("%.1f", (($f2 * 100)+5)/100). "\n";
^^
I'm sorry these should be ' + .5'

print sprintf("%.0f", (($f1 * 100) + .5)/100 ). "\n";
print sprintf("%.1f", (($f2 * 100) + .5)/100). "\n";

Now $f1,$f2 can be used with a common format string, like "%.1f", or "%.0f"
and be consistant.

-sln
 
S

sln

^^
I'm sorry these should be ' + .5'

print sprintf("%.0f", (($f1 * 100) + .5)/100 ). "\n";
print sprintf("%.1f", (($f2 * 100) + .5)/100). "\n";

Now $f1,$f2 can be used with a common format string, like "%.1f", or "%.0f"
and be consistant.

-sln
It can be generalized.
It only works up to the width of the mantissa.
Which is what 8 digits? So don't set the width to say 40.
A width of 2 max should do fine.

-sln
===============================
use strict;
use warnings;

my $f1 = 1.5;
my $f2 = .15;

my ($n1,$n2) = (
getFmtRound($f1,40),
getFmtRound($f2,40)
);

print "$n1\n$n2\n\n";

print getFmtRound($f1,0),"\n";
print getFmtRound($f2,1),"\n";

sub getFmtRound
{
my ($val,$width) = @_;
my $fmt = "%.$width"."f";
return sprintf $fmt,(($val * 10**($width))+.5)/(10**($width));
}
__END__

1.5000000000000000000000000000000000000000
0.1499999999999999900000000000000000000000

2
0.2
 
S

sln

use strict;
use warnings;

my $f1 = 1.5;
my $f2 = .15;

my ($n1,$n2) = (
getFmtRound($f1,40),
getFmtRound($f2,40)
);

print "$n1\n$n2\n\n";

print getFmtRound($f1,0),"\n";
print getFmtRound($f2,1),"\n";

sub getFmtRound
{
my ($val,$width) = @_;
my $fmt = "%.$width"."f";
return sprintf $fmt,(($val * 10**($width))+.5)/(10**($width));
^^^ ^^
return sprintf $fmt,(($val * 10**($width+1))+.5)/(10**($width+1));

Hey sorry again, this should be $width+1. Have to affect only the last digit.
-sln
 
D

Doug Miller

Hello,


why isn't it stored as 0.150000000000000 internally?


This is explains why 0.149 doesn't round to 0.2, but it doesn't explain
why 1.5 works different than 0.15 wuth just one floating point digit
difference.

Of course it does. 1.5 has an exact binary representation, 0.15 does not.
 
M

Marten Lehmann

Hello,

maybe I should change the question to:

How can I force Perl to process certain calculations using decimals?
There are special datatypes in Python and databases. Is there any in Perl?

Ben proposed using the bignums pragma, but I'm afraid that this breaks
something else in the code.

Kind regards
Marten
 
J

Jens Thoms Toerring

Marten Lehmann said:
maybe I should change the question to:
How can I force Perl to process certain calculations using decimals?
There are special datatypes in Python and databases. Is there any in Perl?

If you use just ints you should be fine since there aren't any
rounding errors. There's no built-in BCD type or similar. But
there seems to be rather new module

http://search.cpan.org/~zefram/Math-Decimal-0.001/lib/Math/Decimal.pm

for doing decimal arithmetic. Or have a look at

http://search.cpan.org/~tels/Math-BigRat-0.22/lib/Math/BigRat.pm

if you want as much precision as the amount of memory in your
machine allows (note: I didn't use any of these modules, so
I can't tell how well they work).
Ben proposed using the bignums pragma, but I'm afraid that this breaks
something else in the code.

That you will need if the numbers you use ints and end up with
are too large to be stored in a simple int.

But what will do for you (if it's possible at all) depends on
what exactly you want to do. Until now all we have seen is a
few one-liners, what you need it for is still unclear.

Regards, Jens
 
M

Marten Lehmann

Hello,
But what will do for you (if it's possible at all) depends on
what exactly you want to do.

I want to do financial calculations (nothing special, just simple things
like adding items of an invoice, adding taxes, removing disagio from
creditcard transactions etc.) and I want to be sure of correct results.

So if a total with tax is $0.015, then I need it to be rounded to $0.02
and not $0.01. Therefor, numbers need to be represented as they are and
not the typical internal representation. I don't want to use cents as
integers. Even cents will get odd numbers after the decimal point if you
have to add e.g. 19% value added taxes. And even worser: The whole code
would get bloated and the original intention of the code would be harder
to understand if the whole source is full of "* 100" and "/ 100". In
databases it is easy to define a decimal like (10,2): 10 digits before
the point, 2 after it. It would be great to have this directly in Perl
so that sprintf and other functions can still work with such values and
variables, not only specific modules like Math::Decimal.

Regards
Marten
 
S

sln

Hello,


I want to do financial calculations (nothing special, just simple things
like adding items of an invoice, adding taxes, removing disagio from
creditcard transactions etc.) and I want to be sure of correct results.

So if a total with tax is $0.015, then I need it to be rounded to $0.02
and not $0.01. Therefor, numbers need to be represented as they are and
not the typical internal representation. I don't want to use cents as
integers. Even cents will get odd numbers after the decimal point if you
have to add e.g. 19% value added taxes. And even worser: The whole code
would get bloated and the original intention of the code would be harder
to understand if the whole source is full of "* 100" and "/ 100". In
databases it is easy to define a decimal like (10,2): 10 digits before
the point, 2 after it. It would be great to have this directly in Perl
so that sprintf and other functions can still work with such values and
variables, not only specific modules like Math::Decimal.

Regards
Marten

I don't understand your big concern. Anytime you see numbers, decimal
points, or anything printed in a view from a computer is a rounded
representation of what the internal variable contains.

Its just a view, its a one way snapshot of the contents of the variable.
That doesen't affect the internal calculations. Indeed for percentage
calculations, there is absolutely nothing wrong with floating point
at all.

When you have to display (have a view) where you think its necessary
to show a correct image of the results of percentages as fractions of
dollars (cents) then use a rounding translation that everybody and thier
uncle uses.

Why is it so hard?

sub getFmtRound
{
my ($val,$width) = @_;
my $fmt = "%.$width"."f";
return sprintf $fmt,(($val * 10**($width+1))+.5)/(10**($width+1));
}

-sln
 

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

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,009
Latest member
GidgetGamb

Latest Threads

Top