Peculiar rounding behaviour

P

P

D:\>perl -e "printf '%.1f', 37.15"
37.1

D:\>perl -e "printf '%.2f', 37.115"
37.12

D:\>perl -e "printf '%.3f', 37.1115"
37.111

D:\>perl -e "printf '%.4f', 37.11115"
37.1112

D:\>perl -v

This is perl, v5.8.7 built for MSWin32-x86-multi-thread

OS: Microsoft XP SP2


Can you explain this behaviour?
 
B

Ben Bullock

P said:
D:\>perl -e "printf '%.1f', 37.15"
37.1

D:\>perl -e "printf '%.2f', 37.115"
37.12

D:\>perl -e "printf '%.3f', 37.1115"
37.111

D:\>perl -e "printf '%.4f', 37.11115"
37.1112
Can you explain this behaviour?

On page 731 of the third edition of "Programming Perl" by Wall et al it says
that "machine representations of floating point numbers sometimes give
counterintuitive results" and gives an example where int (-6.725/0.025)
gives a result of -268 rather than -269 because of the representation of
floating point numbers. I guess the above is a similar case; the numbers are
probably "just above" the 1/2 value in floating point representation. It
says to use POSIX::floor and POSIX::ceil in the above book.
 
M

Mintcake

P said:
D:\>perl -e "printf '%.1f', 37.15"
37.1

D:\>perl -e "printf '%.2f', 37.115"
37.12

D:\>perl -e "printf '%.3f', 37.1115"
37.111

D:\>perl -e "printf '%.4f', 37.11115"
37.1112

D:\>perl -v

This is perl, v5.8.7 built for MSWin32-x86-multi-thread

OS: Microsoft XP SP2


Can you explain this behaviour?

perl -e 'printf "%.16f", 37.15'

37.1499999999999990

This is nothing to do with Perl - it is because floating point
representation of most real numbers is imprecise. The nearest internal
representation of 37.15 is just a tad underweight, so normal rounding
rules dictate that to 1 dp it should be appear as 37.1
 
D

Daniel Fischer

P!
Can you explain this behaviour?

Many real numbers that can be expressed exactly in base 10 cannot be
written as an exact number in base 2. The internal representation of
floats is in base 2.

This means that when you say 37.15, perl might actually store
something more like 37.14999... and the 37.15 that you see is only rounded
up anyway. However, rounding 37.149 to one decimal digit clearly produces
a different result from rounding 37.15.


This is also why you don't use floating point arithmetics for banking
related software.


Daniel
 
P

P

Daniel said:
P!


Many real numbers that can be expressed exactly in base 10 cannot be
written as an exact number in base 2. The internal representation of
floats is in base 2.

Yes, I know all of this, but it doesn't explain why it rounds up the
odd amount
of digits after the decimal, and down when the amount is even.
 
M

Mintcake

P said:
Yes, I know all of this, but it doesn't explain why it rounds up the
odd amount
of digits after the decimal, and down when the amount is even.
There is no explanation because it is not true. If you'd carried on
with a couple more examples in the series you would have broken it...

perl -e "printf '%.5f', 37.111115"
37.11111

perl -e "printf '%.6f', 37.1111115"
37.111111
 
P

P

Mintcake wrote:

[...]
There is no explanation because it is not true. If you'd
carried on with a couple more examples in the series
you would have broken it...

perl -e "printf '%.5f', 37.111115"
37.11111

perl -e "printf '%.6f', 37.1111115"
37.111111

D:\>perl -e "printf '%.5f', 37.111115"
37.11111
D:\>perl -e "printf '%.6f', 37.1111115"
37.111112
D:\>perl -e "printf '%.7f', 37.11111115"
37.1111111
D:\>perl -e "printf '%.8f', 37.111111115"
37.11111112


Maybe you're using a different version/OS.
 
D

Dave

P said:
Mintcake wrote:

[...]
There is no explanation because it is not true. If you'd
carried on with a couple more examples in the series
you would have broken it...

perl -e "printf '%.5f', 37.111115"
37.11111

perl -e "printf '%.6f', 37.1111115"
37.111111

D:\>perl -e "printf '%.5f', 37.111115"
37.11111
D:\>perl -e "printf '%.6f', 37.1111115"
37.111112
D:\>perl -e "printf '%.7f', 37.11111115"
37.1111111
D:\>perl -e "printf '%.8f', 37.111111115"
37.11111112


Maybe you're using a different version/OS.

Note that if you add a tenth of the accuracy that you want (i.e. add 1 at
one order of magnitude less than the one you are interested in) before the
rounding you should get the results you expect.
 
P

P

Dave said:
P said:
Mintcake wrote:

[...]
Yes, I know all of this, but it doesn't explain why it
rounds up the odd amount of digits after the decimal,
and down when the amount is even.

There is no explanation because it is not true. If you'd
carried on with a couple more examples in the series
you would have broken it...

perl -e "printf '%.5f', 37.111115"
37.11111

perl -e "printf '%.6f', 37.1111115"
37.111111

D:\>perl -e "printf '%.5f', 37.111115"
37.11111
D:\>perl -e "printf '%.6f', 37.1111115"
37.111112
D:\>perl -e "printf '%.7f', 37.11111115"
37.1111111
D:\>perl -e "printf '%.8f', 37.111111115"
37.11111112


Maybe you're using a different version/OS.

Note that if you add a tenth of the accuracy that
you want (i.e. add 1 at one order of magnitude less
than the one you are interested in) before the rounding
you should get the results you expect.


I don't know at which point I gave the impression that this
was a problem I couldn't deal with or solve. What I asked
was for a possible explanation of why this behaviour occurs,
so I could decide whether this should be reported as a bug
or not.
 
I

Ilya Zakharevich

[A complimentary Cc of this posting was sent to
P
D:\>perl -e "printf '%.5f', 37.111115"
37.11111
D:\>perl -e "printf '%.6f', 37.1111115"
37.111112
D:\>perl -e "printf '%.7f', 37.11111115"
37.1111111
D:\>perl -e "printf '%.8f', 37.111111115"
37.11111112

This means that the compiler used to build Perl is extremely buggy (up
to being unusable for anything which requires correct arithmetic).
Require a refund.

The first thing to check should be

perl -e "printf '%.18f', 37.111111115"
37.111111114999999927

(after .18f different CRTL I have start to produce different results).
Note that 37.111111115 * 2**(53-4) is 20891698273602384.04108288
(exactly), and 20891698273602384./2**(53-4) is
37.111111114999999927022145129740238189697265625 (exactly). So THIS
should be the number rounded by printf() when the compiler uses 64-bit
IEEE FP numbers.

Actually, Sun's cc (Sun WorkShop 6 update 2 C 5.3 Patch 111679-09
2002/11/12) gives this result with

perl -e "printf '%.49f', 37.111111115"

this is pretty amazing; I would not expect the C standard requires so
much precision from printf(); does it? But about .18f should be
expected to provide correct results.

On two systems (with different vendors' CRTL) I get

~->perl -e 'printf qq(%.${_}f\n), q(37.) . 1 x $_ . 5 for 1..14'
37.1
37.12
37.111
37.1112
37.11111
37.111111
37.1111111
37.11111111
37.111111112
37.1111111112
37.11111111112
37.111111111111
37.1111111111111
37.11111111111111

Hope this helps,
Ilya
 
D

Dr.Ruud

P schreef:
Daniel Fischer:

Yes, I know all of this, but it doesn't explain why it rounds up the
odd amount of digits after the decimal, and down when the amount is
even.

You are creating out of thin air so you must be God.


perl, v5.8.6 built for i386-freebsd-64int:

$ perl -e 'printf qq{%.*f\n}, $_, q{37.} . q{1} x $_ . q{5} for (1..10)'
37.1
37.12
37.111
37.1112
37.11111
37.111111
37.1111111
37.11111111
37.111111112
37.1111111112


perl, v5.8.8 built for MSWin32-x86-multi-thread:

C:\>perl -e "printf qq{%.*f\n}, $_, q{37.} . q{1} x $_ . q{5} for
(1..10)"
37.1
37.12
37.111
37.1112
37.11111
37.111112
37.1111111
37.11111112
37.111111112
37.1111111112
 
I

Ilya Zakharevich

[A complimentary Cc of this posting was NOT [per weedlist] sent to
Ilya Zakharevich
The first thing to check should be

perl -e "printf '%.18f', 37.111111115"
37.111111114999999927

(after .18f different CRTL I have start to produce different results).
Note that 37.111111115 * 2**(53-4) is 20891698273602384.04108288
(exactly), and 20891698273602384./2**(53-4) is
37.111111114999999927022145129740238189697265625 (exactly). So THIS
should be the number rounded by printf() when the compiler uses 64-bit
IEEE FP numbers.

Actually, Sun's cc (Sun WorkShop 6 update 2 C 5.3 Patch 111679-09
2002/11/12) gives this result with

perl -e "printf '%.49f', 37.111111115"

this is pretty amazing; I would not expect the C standard requires so
much precision from printf(); does it? But about .18f should be
expected to provide correct results.

http://www.opengroup.org/onlinepubs/009695399/functions/printf.html

(essentially) says two things:

%.18f should produce the CORRECT result if 18 <= DECIMAL_DIG;

rounding of the last digit is implementation-defined.

I cannot connect these two requirements into a consistent picture...

Puzzled,
Ilya

P.S. On one of the systems I have DECIMAL_DIG is 21 (but the system is
using gcvt() as its `perl -V:d_Gconvert', so it is not covered by
any standard). Another system is using gconvert() (so also is
not covered by standards) - but I can't get the value of
DECIMAL_DIG... Running cc -E on this does not expand the macro:

#include <math.h>
#include <stdio.h>

print(DECIMAL_DIG);
 
P

P

Dr.Ruud said:
P schreef:

You are creating out of thin air so you must be God.
perl, v5.8.8 built for MSWin32-x86-multi-thread:

You are using a different version, so you must be talking
out of your ass.


This is perl, v5.8.7 built for MSWin32-x86-multi-thread:

D:\>perl -e "printf qq{%.*f\n}, $_, q{37.} . q{1} x $_ . q{5} for
(1..10)"
37.1
37.12
37.111
37.1112
37.11111
37.111112
37.1111111
37.11111112
37.111111111
37.1111111112


Thank you, Ilya, for shedding some light on this.
 
D

Dr.Ruud

Ilya Zakharevich schreef:

I> perl -e 'printf qq(%.${_}f\n), q(37.) . 1 x $_ . 5 for 1..14'


R> perl -e 'printf qq{%.*f\n}, $_, q{37.} . q{1} x $_ . q{5} for
(1..10)'

Heheh, they are a lot alike, though yours looks cleaner.

Is there a specific reason why you used ${_} and not '*' inside the
format string?
 
I

Ilya Zakharevich

[A complimentary Cc of this posting was NOT [per weedlist] sent to
Dr.Ruud
I> perl -e 'printf qq(%.${_}f\n), q(37.) . 1 x $_ . 5 for 1..14'
R> perl -e 'printf qq{%.*f\n}, $_, q{37.} . q{1} x $_ . q{5} for (1..10)'
Is there a specific reason why you used ${_} and not '*' inside the
format string?

* should be quickier (no interpolation needed); however, IMO, the only
"actual" reason for existence of '*' is that interpolation is not
available in C.

Yours,
Ilya
 
I

Ilya Zakharevich

[A complimentary Cc of this posting was sent to
P
D:\>perl -e "printf qq{%.*f\n}, $_, q{37.} . q{1} x $_ . q{5} for
(1..10)"
37.1
37.12
37.111
37.1112
37.11111
37.111112
37.1111111
37.11111112
37.111111111
37.1111111112
Thank you, Ilya, for shedding some light on this.

The key question is the value of `perl -V:d_Gconvert'. If it uses
sprintf(), then the standard says that DECIMAL_DIG should be at least
10, so sprintf("%.10g") and less should give exact result (if one
ignores the contradicting part about "implementation-specific"
rounding).

If it uses some other function, then it is a problem (bug? maybe
not...) in Perl build process that "this other function which is
buggy" is chosen.

Hope this helps,
Ilya
 
D

Dr.Ruud

Ilya Zakharevich schreef:
Dr.Ruud:

* should be quickier (no interpolation needed); however, IMO, the only
"actual" reason for existence of '*' is that interpolation is not
available in C.

Yes, it's an old habit showing through. But we also built format strings
(with sprintf) in those days.
 

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
474,186
Messages
2,570,998
Members
47,587
Latest member
JohnetteTa

Latest Threads

Top