# Rounding issue

#### Do you have a question? Post it now! No Registration Necessary.  Now with pictures!

•  Subject
• Author
• Posted on

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

## Re: Rounding issue

~% perl -E'say sprintf "%.40f", 1.5'
1.5000000000000000000000000000000000000000

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

This is less than 0.15, so it rounds to 0.1.

Where is this number coming from? Why do you need it to round to 0.2?

Ben

## Re: Rounding issue

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.

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

## Re: Rounding issue

Hint: Typical computers are binary, they don't use the decimal system.

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?

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

jue

## Re: Rounding issue

Hello,

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

## Re: Rounding issue

I hope you don't work at my bank...

--
email: perl -le "print scalar reverse qq/moc.noitatibaher0cmdat/"

## Re: Rounding issue

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

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

--
Randal L. Schwartz - Stonehenge Consulting Services, Inc. - +1 503 777 0095
Smalltalk/Perl/Unix consulting, Technical writing, Comedy, etc. etc.
See http://methodsandmessages.vox.com/ for Smalltalk and Seaside discussion

## Re: Rounding issue

Tough. That's the price for using floating point. If you don't want to
pay it, don't use floating point.

That concept is called 'bignums'. Perl supports bignums through the use
of the bignum pragma. They are (substantially) slower than native
floats, and cannot easily be passed to functions like sprintf that
expect a float. Nevertheless, feel free to use them if you like.

1.5 has a finite decimal representation in both base 10 and base 2.
0.15 does not.

Ben

## Re: Rounding issue

Then you shouldn't do numeric work.

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").

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

## Re: Rounding issue

Then you better don't use floating point numbers;-)

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
--
\   Jens Thoms Toerring  ___      jt@toerring.de
\__________________________      http://toerring.de

## Re: Rounding issue

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.

jue

## Re: Rounding issue

Because it is not represented in base 10, it is represented in base 2 (binary).

Using floating point numbers.

Do not work with floating point dollars, instead work with
integer cents (then convert to dollars only at the last output stage).

--
email: perl -le "print scalar reverse qq/moc.noitatibaher0cmdat/"

## Re: Rounding issue

Because 0.15 is not representable in (finite) binary floating point.
(This is the same as asking why 1/3 is not exactly representable as a
percentage, except the numbers are different because computers use base
2 instead of base 10.)

1.5 and 0.15 do not look particularly similar in binary. 1.5 is '1.1',
0.15 is a recurring 'binimal' that starts '0.001001100110011...'.

Then don't use floating point. Store all your amounts in cents, and do
the necessary rounding yourself. I would expect that if this is a
financial application there are some very carefully-specified rounding
rules you must follow: you need to know what these are.

~% perl -E'say sprintf "%0.40f\n%0.40f\n%0.40f", 0.1, 0.05, 0.1+0.05'
0.1000000000000000055511151231257827021182
0.0500000000000000027755575615628913510591
0.1500000000000000222044604925031308084726

You could perfectly well have answered that question yourself.

Ben

## Re: Rounding issue

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.

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
--
\   Jens Thoms Toerring  ___      jt@toerring.de
\__________________________      http://toerring.de

## Re: Rounding issue

Quoth jt@toerring.de (Jens Thoms Toerring):

No, it's exactly what you (ought to) expect. Most modern computers do
round-to-even by default, for good reasons.

Ben

## Re: Rounding issue

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

## Re: Rounding issue

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).

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
--
\   Jens Thoms Toerring  ___      jt@toerring.de
\__________________________      http://toerring.de

## Re: Rounding issue

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

## Re: Rounding issue

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

## Re: Rounding issue

sln@netherlands.com wrote:
) 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.

No it isn't.  It can also be an exact representation.  For example,
if the numbers are integers or some kind of decimal format.

) 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.

Yes there is.  Percentage calculations are fixed point, and to get exact
results, you need to be doing them in an exact representation such as
integers or rationals.

) 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.

If you use floating point internally, then the display result will not
always be exact.

) Why is it so hard?

Because people think too easy of it.  Such as you.

SaSW, Willem
--
Disclaimer: I am in no way responsible for any of the statements
made in the above text. For all I know I might be
drugged or something..
No I'm not paranoid. You all think I'm paranoid, don't you !
#EOT