decimal round off issue

S

Shiping Zhang

Is this a bug in perl?

$v = sprintf "%.1f", 2.25;
print $v, "\n";

The above code produced 2.2

$v = sprintf "%.1f", 2.35;
print $v, "\n";

The above code produced 2.4

I tested several versions, they all behaved the same.
 
J

Jürgen Exner

Shiping Zhang said:
Is this a bug in perl?

$v = sprintf "%.1f", 2.25;
print $v, "\n";

The above code produced 2.2

I cannot reproduce this observation. For me it prints 2.3.
( v5.10.0 built for MSWin32-x86-multi-thread)
$v = sprintf "%.1f", 2.35;
print $v, "\n";

The above code produced 2.4

I tested several versions, they all behaved the same.

However more than likely you don't even need the sprintf() assignment
with separate print(), a simple
printf "%.1f", 2.35;
will most likely behave exactly the same.

If so then try
printf "%.40f", 2.25;
and
printf "%.40f", 2.35;
on your Perl installation. This will most likely demonstrate the reason
for your observation. For an explanation please
- see "perldoc -q 999"
- re-read your notes from "Introduction into Computer Numerics"
- check the NG archives for dozens and dozens of similar posts and the
replies to them

jue
 
S

Shiping Zhang

Is this a bug in perl?

$v = sprintf "%.1f", 2.25;
print $v, "\n";

The above code produced 2.2

$v = sprintf "%.1f", 2.35;
print $v, "\n";

The above code produced 2.4

I tested several versions, they all behaved the same.

Okay, it's not a bug. This is due to limitation of machine (binary)
representation of floating point numbers. A test C program behaved
the same.
 
S

sln

Okay, it's not a bug. This is due to limitation of machine (binary)
representation of floating point numbers. A test C program behaved
the same.

Right, a binary thing! But more than that, different results across
different floating point hardware, firmware, libs.

You would think all things being equal, the same error pattern should
be had on all machines.

The good news is that internal fp operations wash out those discrepenccies
well past the number of digits of which the real universe can actually
be measured by physical means. Ie, its not right/wrong unless you can
prove it is/isn't. There are probably wider (bit-wise) floating point
registers available for calculations on scientific principles that can't
be proven.

c:\>perl -e "for(2.15,2.25,2.35,2.45,2.55,2.65) {$v = sprintf \"%.1f\", $_;
print \"$v\n\"}"
2.1
2.3
2.4
2.5
2.5
2.6

-sln
 
S

sln

I don't think I missed class that day, but what binary representation of
floating point CANNOT represent the base ten number "2.25"? That's 10.01
base two.
Thats very true. And its non-repeating. Therefore, it should not be a
problem going from (1.001 x 2**1)base 2, to 2.25 base 10 and having it display
correctly when it comes to rounding and formatting string representation.
I'm not sure that rounding happens the same way on all systems when there is
a repeting binary fraction (like .55 base 10).

From wikipedia: http://en.wikipedia.org/wiki/IEEE_754-1985

I wonder if you do a bitwise examination of a float variable if it
will show the sign, exp, fraction bits like in the standard?
Or on the language level, are the bitwise operators restricted to
operations on integers?

Where are the calculators that do binary <-> decimal fractions?
Don't want to do it by hand all the time.

-sln
 
J

Josef Moellers

Jürgen Exner said:
I cannot reproduce this observation. For me it prints 2.3.
( v5.10.0 built for MSWin32-x86-multi-thread)

v5.8,8 running on Kubuntu 8.04LTS has the same peculiarity:

$v = sprintf "%.1f", 2.25;
print $v, "\n";
$v = sprintf "%.1f", 2.35;
print $v, "\n";

2.2
2.4
 
P

Peter J. Holzer

I cannot reproduce this observation. For me it prints 2.3.
( v5.10.0 built for MSWin32-x86-multi-thread)

Strange. It should produce 2.2 (2.25 is exactly halfway between 2.2 and
2.3, so the "round to even" rule is used). However, 2.2 and 2.3 aren't
exactly representable, so maybe that's where the rounding error is.

On Debian Linux (perl, v5.10.0 built for i486-linux-gnu-thread-multi),
this:

perl -e 'for(2.15,2.25,2.35,2.45,2.55,2.65, 2.75, 2.85, 2.95) {printf "%.60f %.1f\n", $_, $_ }'

prints:

2.149999999999999911182158029987476766109466552734375000000000 2.1
2.250000000000000000000000000000000000000000000000000000000000 2.2
2.350000000000000088817841970012523233890533447265625000000000 2.4
2.450000000000000177635683940025046467781066894531250000000000 2.5
2.549999999999999822364316059974953532218933105468750000000000 2.5
2.649999999999999911182158029987476766109466552734375000000000 2.6
2.750000000000000000000000000000000000000000000000000000000000 2.8
2.850000000000000088817841970012523233890533447265625000000000 2.9
2.950000000000000177635683940025046467781066894531250000000000 3.0

which is exactly correct: The values which are slightly above or below
0.x5 are rounded up or down as appropriate, and 2.25 and 2.75 are
rounded to even.

hp
 
S

sln

Strange. It should produce 2.2 (2.25 is exactly halfway between 2.2 and
2.3, so the "round to even" rule is used). However, 2.2 and 2.3 aren't
exactly representable, so maybe that's where the rounding error is.

On Debian Linux (perl, v5.10.0 built for i486-linux-gnu-thread-multi),
this:

perl -e 'for(2.15,2.25,2.35,2.45,2.55,2.65, 2.75, 2.85, 2.95) {printf "%.60f %.1f\n", $_, $_ }'

prints:

2.149999999999999911182158029987476766109466552734375000000000 2.1
2.250000000000000000000000000000000000000000000000000000000000 2.2
2.350000000000000088817841970012523233890533447265625000000000 2.4
2.450000000000000177635683940025046467781066894531250000000000 2.5
2.549999999999999822364316059974953532218933105468750000000000 2.5
2.649999999999999911182158029987476766109466552734375000000000 2.6
2.750000000000000000000000000000000000000000000000000000000000 2.8
2.850000000000000088817841970012523233890533447265625000000000 2.9
2.950000000000000177635683940025046467781066894531250000000000 3.0

which is exactly correct: The values which are slightly above or below
0.x5 are rounded up or down as appropriate, and 2.25 and 2.75 are
rounded to even.

hp

I never heard of that "round to even" rule. On my build it rounds "up"
all the time. Maybe I don't have the latest build.

perl -e "for(2.15,2.25,2.35,2.45,2.55,2.65, 2.75, 2.85, 2.95) {printf \"%.60f %.1f\n\", $_, $_ }"

2.149999999999999900000000000000000000000000000000000000000000 2.1
2.250000000000000000000000000000000000000000000000000000000000 2.3
2.350000000000000100000000000000000000000000000000000000000000 2.4
2.450000000000000200000000000000000000000000000000000000000000 2.5
2.549999999999999800000000000000000000000000000000000000000000 2.5
2.649999999999999900000000000000000000000000000000000000000000 2.6
2.750000000000000000000000000000000000000000000000000000000000 2.8
2.850000000000000100000000000000000000000000000000000000000000 2.9
2.950000000000000200000000000000000000000000000000000000000000 3.0

I'm sure this is a result of a define flag passed via compiler option.
I' got the active state 5.10 built using gcc. I looked over the build
parameters using perl -V. I've only used MS compilers, so I don't know
some of the options listed. I looked for optimizations that I recognized,
only saw O2, compile for speed. MS has some /FP: optimizations like
precise/strict,etc.., that gcc doesen't.
Gcc looks like it has nvtype as double, its size 64 bit.
----------------
v5.10.0 built for MSWin32-x86-multi-thread
(with 5 registered patches)

Binary build 1004 [287188] provided by ActiveState http://www.ActiveState.com
Built Sep 3 2008 13:16:37
 
P

Peter J. Holzer

I never heard of that "round to even" rule.

It is mentioned in this group just about every time that rounding is
discussed, which is probably at least once or twice per year.

The rule was used at least since the early 20th century (Wikipedia cites
a book from 1906). In IEEE-754 arithmetic (which is used in all current
FP units) it is the default rounding mode.
On my build it rounds "up"
all the time. Maybe I don't have the latest build.

perl -e "for(2.15,2.25,2.35,2.45,2.55,2.65, 2.75, 2.85, 2.95) {printf \"%.60f %.1f\n\", $_, $_ }"

2.149999999999999900000000000000000000000000000000000000000000 2.1
2.250000000000000000000000000000000000000000000000000000000000 2.3
2.350000000000000100000000000000000000000000000000000000000000 2.4
2.450000000000000200000000000000000000000000000000000000000000 2.5
2.549999999999999800000000000000000000000000000000000000000000 2.5
2.649999999999999900000000000000000000000000000000000000000000 2.6
2.750000000000000000000000000000000000000000000000000000000000 2.8
2.850000000000000100000000000000000000000000000000000000000000 2.9
2.950000000000000200000000000000000000000000000000000000000000 3.0

I'm sure this is a result of a define flag passed via compiler option.

More likely it's caused by the implementation of sprintf in the C
library.
I' got the active state 5.10 built using gcc. I looked over the build
parameters using perl -V.

Is there a "standard" C library on Windows which gcc has to use or does
it use the glibc? I suspect it's the former (I've seen similar results
with Microsofts C compiler).

hp
 
I

Ilya Zakharevich

Huh? glibc is Linux- (well, and Hurd-) only.

Last time I compiled glibc was several years ago. But it was nowhere
close to Linux and/or Hurd. It is just a C library on top of POSIX -
at lest at was at the time...
gcc on Win32 uses MSVCRT.DLL,

AFAIK, there are many gcc's on Win32, all (?) using different CRTL...

Yours,
Ilya
 
S

sln

It is mentioned in this group just about every time that rounding is
discussed, which is probably at least once or twice per year.

The rule was used at least since the early 20th century (Wikipedia cites
a book from 1906). In IEEE-754 arithmetic (which is used in all current
FP units) it is the default rounding mode.
Yeah I just read that on wikipedia. I imagine that a fpu has
at least 2, 80 bit registers to do comarisons, +-/* operations,
and result (don't know about this at all).
When the fraction is to be chopped to 64 bit variable, the lsb
is set to 0, if the lsb-1 bit in the register is set, not sure.
It says 'midway'.
I don't thinks this is a fp rounding issue, since by the standard,
this would be consistent across fp hardware.
More likely it's caused by the implementation of sprintf in the C
library.
I think you are right on it being a lib implementation issue.
Is there a "standard" C library on Windows which gcc has to use or does
it use the glibc? I suspect it's the former (I've seen similar results
with Microsofts C compiler).

hp

There is a standard set of dll's that comes with Windows os',
2004 seems to be the last year that CRT was modified (added to).
Some older programs used to just include some runtime libs (dll's) in thier
exe path.

Microsoft defines 'core' dll's (crt, win32 interface, etc..) in thier os,
that get put in the system32 dir, part of the system path.

Most programs that link to them use import .lib 's.
That was seen all over gcc. Most can just get the platform sdk and
grab the lib's. Header files are a whole different nightmare.

So, yes. Gcc links with msvcrt.lib (import) which loads the system
dll when it runs. Single threaded static libc is no longer available
since VC2002/3/5/...

But gcc uses /MD multithread-dynamic CRT library option. So thats
where it is.

In all of MS compilers, there is the option to compile as either
C or C++. It looks like Gcc compiles it as C.
I have VC-2005.

Still, you have to wonder why MS, who supposedly is ANSI CRT
would differ from other compilers in its sprintf results.
I thought it could be that gcc doesn't use perhaps an optimization
that MS uses in its comiler that may pertain to floating point.

On the other hand, there is alot of defines being passed to the
Perl source. Are you sure that sprintf/printf from the CRT is
is not being bypassed via custom Perl implementation?

Either way, it might be nice to compile it under vc-2005,
using the float option /FP:precision

-sln

---<cut>-------
c:\temp>perl -V

Summary of my perl5 (revision 5 version 10 subversion 0) configuration:
Platform:
osname=MSWin32, osvers=5.00, archname=MSWin32-x86-multi-thread
uname=''
config_args='undef'
hint=recommended, useposix=true, d_sigaction=undef
useithreads=define, usemultiplicity=define
useperlio=define, d_sfio=undef, uselargefiles=define, usesocks=undef
use64bitint=undef, use64bitall=undef, uselongdouble=undef
usemymalloc=n, bincompat5005=undef
Compiler:
cc='cl', ccflags ='-nologo -GF -W3 -MD -Zi -DNDEBUG -O1 -DWIN32 -D_CONSOLE -
DNO_STRICT -DHAVE_DES_FCRYPT -DUSE_SITECUSTOMIZE -DPRIVLIB_LAST_IN_INC -DPERL_IM
PLICIT_CONTEXT -DPERL_IMPLICIT_SYS -DUSE_PERLIO -DPERL_MSVCRT_READFIX',
optimize='-MD -Zi -DNDEBUG -O1',
cppflags='-DWIN32'
ccversion='12.0.8804', gccversion='', gccosandvers=''
intsize=4, longsize=4, ptrsize=4, doublesize=8, byteorder=1234
d_longlong=undef, longlongsize=8, d_longdbl=define, longdblsize=10
ivtype='long', ivsize=4, nvtype='double', nvsize=8, Off_t='__int64', lseeksi
ze=8
alignbytes=8, prototype=define
Linker and Libraries:
ld='link', ldflags ='-nologo -nodefaultlib -debug -opt:ref,icf -libpath:"C:
\Perl\lib\CORE" -machine:x86'
libpth=\lib
libs= oldnames.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32
..lib advapi32.lib shell32.lib ole32.lib oleaut32.lib netapi32.lib uuid.lib ws2_
32.lib mpr.lib winmm.lib version.lib odbc32.lib odbccp32.lib msvcrt.lib
perllibs= oldnames.lib kernel32.lib user32.lib gdi32.lib winspool.lib comd
lg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib netapi32.lib uuid.lib
ws2_32.lib mpr.lib winmm.lib version.lib odbc32.lib odbccp32.lib msvcrt.lib
libc=msvcrt.lib, so=dll, useshrplib=true, libperl=perl510.lib
gnulibc_version=''
Dynamic Linking:
dlsrc=dl_win32.xs, dlext=dll, d_dlsymun=undef, ccdlflags=' '
cccdlflags=' ', lddlflags='-dll -nologo -nodefaultlib -debug -opt:ref,icf -
libpath:"C:\Perl\lib\CORE" -machine:x86'


Characteristics of this binary (from libperl):
Compile-time options: MULTIPLICITY PERL_DONT_CREATE_GVSV
PERL_IMPLICIT_CONTEXT PERL_IMPLICIT_SYS
PERL_MALLOC_WRAP PL_OP_SLAB_ALLOC USE_ITHREADS
USE_LARGE_FILES USE_PERLIO USE_SITECUSTOMIZE
Locally applied patches:
ActivePerl Build 1004 [287188]
33741 avoids segfaults invoking S_raise_signal() (on Linux)
33763 Win32 process ids can have more than 16 bits
32809 Load 'loadable object' with non-default file extension
32728 64-bit fix for Time::Local
Built under MSWin32
Compiled at Sep 3 2008 13:16:37
@INC:
C:/Perl/site/lib
C:/Perl/lib
 
S

sln

---<cut>-------

c:\temp>perl -v

This is perl, v5.10.0 built for MSWin32-x86-multi-thread
(with 5 registered patches, see perl -V for more detail)

Copyright 1987-2007, Larry Wall

Binary build 1004 [287188] provided by ActiveState http://www.ActiveState.com
Built Sep 3 2008 13:16:37

Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.

Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl". If you have access to the
Internet, point your browser at http://www.perl.org/, the Perl Home Page.


c:\temp>
c:\temp>perl -V

Summary of my perl5 (revision 5 version 10 subversion 0) configuration:
Platform:
osname=MSWin32, osvers=5.00, archname=MSWin32-x86-multi-thread
uname=''
config_args='undef'
hint=recommended, useposix=true, d_sigaction=undef
useithreads=define, usemultiplicity=define
useperlio=define, d_sfio=undef, uselargefiles=define, usesocks=undef
use64bitint=undef, use64bitall=undef, uselongdouble=undef
usemymalloc=n, bincompat5005=undef
Compiler:
cc='cl', ccflags ='-nologo -GF -W3 -MD -Zi -DNDEBUG -O1 -DWIN32 -D_CONSOLE -
DNO_STRICT -DHAVE_DES_FCRYPT -DUSE_SITECUSTOMIZE -DPRIVLIB_LAST_IN_INC -DPERL_IM
PLICIT_CONTEXT -DPERL_IMPLICIT_SYS -DUSE_PERLIO -DPERL_MSVCRT_READFIX',
optimize='-MD -Zi -DNDEBUG -O1',
cppflags='-DWIN32'
ccversion='12.0.8804', gccversion='', gccosandvers=''
intsize=4, longsize=4, ptrsize=4, doublesize=8, byteorder=1234
d_longlong=undef, longlongsize=8, d_longdbl=define, longdblsize=10
ivtype='long', ivsize=4, nvtype='double', nvsize=8, Off_t='__int64', lseeksi
ze=8
alignbytes=8, prototype=define
Linker and Libraries:
ld='link', ldflags ='-nologo -nodefaultlib -debug -opt:ref,icf -libpath:"C:
\Perl\lib\CORE" -machine:x86'
libpth=\lib
libs= oldnames.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32
.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib netapi32.lib uuid.lib ws2_
32.lib mpr.lib winmm.lib version.lib odbc32.lib odbccp32.lib msvcrt.lib
perllibs= oldnames.lib kernel32.lib user32.lib gdi32.lib winspool.lib comd
lg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib netapi32.lib uuid.lib
ws2_32.lib mpr.lib winmm.lib version.lib odbc32.lib odbccp32.lib msvcrt.lib
libc=msvcrt.lib, so=dll, useshrplib=true, libperl=perl510.lib
gnulibc_version=''
Dynamic Linking:
dlsrc=dl_win32.xs, dlext=dll, d_dlsymun=undef, ccdlflags=' '
cccdlflags=' ', lddlflags='-dll -nologo -nodefaultlib -debug -opt:ref,icf -
libpath:"C:\Perl\lib\CORE" -machine:x86'


Characteristics of this binary (from libperl):
Compile-time options: MULTIPLICITY PERL_DONT_CREATE_GVSV
PERL_IMPLICIT_CONTEXT PERL_IMPLICIT_SYS
PERL_MALLOC_WRAP PL_OP_SLAB_ALLOC USE_ITHREADS
USE_LARGE_FILES USE_PERLIO USE_SITECUSTOMIZE
Locally applied patches:
ActivePerl Build 1004 [287188]
33741 avoids segfaults invoking S_raise_signal() (on Linux)
33763 Win32 process ids can have more than 16 bits
32809 Load 'loadable object' with non-default file extension
32728 64-bit fix for Time::Local
Built under MSWin32
Compiled at Sep 3 2008 13:16:37
@INC:
C:/Perl/site/lib
C:/Perl/lib
.

c:\temp>
 
P

Peter J. Holzer

Huh? glibc is Linux- (well, and Hurd-) only.

Ports to other systems existed. I wouldn't be terribly surprised if a
(partial) port to Windows exists and is bundled with one of the ports of
gcc for Windows.

gcc on Win32 uses MSVCRT.DLL,

That's what I thought (I wrote "I suspect it's the former"), but I
didn't know it. The last time I wrote C code for a MS platform was in
the MS-DOS days ...

hp
 
P

Peter J. Holzer

[ unexpected results from perl printf on Windows ]
Still, you have to wonder why MS, who supposedly is ANSI CRT
would differ from other compilers in its sprintf results.

The C standard is deliberately vague on many aspects of floating point
arithmetic, because in the late 1980s there were still a lot of very
different implementations of fp arithmetic (both in software and
hardware). Mandating IEEE-754 compliant arithmetic would have been as
sure way to prevent the standard from being adopted by major vendors.
Even the C99 standard only contains IEEE-754 arithmetic as an option.

So the MS printf implementation is almost certainly standard-conforming,
it just isn't as good as it could be. (I haven't checked, but I think
the error is below one ulp, so it is even correct)

I thought it could be that gcc doesn't use perhaps an optimization
that MS uses in its comiler that may pertain to floating point.

It is unlikely that this has anything to do with compiler optimizations.
Printf is just implemented differently (note that the MS implementation
apparently has a fixed number of decimal digits and prints only zeros
after that).

On the other hand, there is alot of defines being passed to the
Perl source. Are you sure that sprintf/printf from the CRT is
is not being bypassed via custom Perl implementation?

No. But if Perl had its own implementation I would expect the output on
Linux and Windows to be the same. Since the output is different it is
very likely that Perl just uses the facilities of the local C library.

(The default perl FP->string conversion is a custom implementation
(since the C library doesn't offer the functionality) and it is buggy -
I've ranted about that before, but that isn't an issue here)

hp
 
I

Ilya Zakharevich

Really? The only port I've ever seen is the MinGW port, which uses
MSVCRT as it's libc. (I don't count Cygwin/Interix/whatever gccs as
running on Win32, and neither does perl.)

I saw mentions of djgcc port. At some time OS/2 EMX port was working
on Win32 with an appropriate syscalls library (RSX-NT, if I remember
correct) - but later people could not reproduce it; I did not collect
enough incentive to debug.

I know that klibc sources have __WIN32__ defines and subdirectories
scattered about... Do not know whether klibc actually compiles under
Win32. I know that Perl compiles - and at least in some repects works
- with klibc.

Yours,
Ilya
 
I

Ilya Zakharevich

(The default perl FP->string conversion is a custom implementation
(since the C library doesn't offer the functionality)

C library definitely offers the functionality. And it was used for
decades without much problem....
and it is buggy -

Agreed.

Ilya
 
P

Peter J. Holzer

C library definitely offers the functionality.

Really? Which standard C function gives you the shortest decimal string
representation of a floating point number which can be be converted back
to an fp number with the same value?

The closest I see is gcvt, which isn't a standard C function (but
probably portable enough for the purposes of perl) and doesn't quite cut
it either: It prints
1.0000000000000000818...e-05 as 1.0000000000000001e-05 instead of
0.00001 or 1e-05 (tested with glibc 2.7).

hp
 
I

Ilya Zakharevich

Really? Which standard C function gives you the shortest decimal string
representation of a floating point number which can be be converted back
to an fp number with the same value?

Really? From when "The default perl FP->string conversion" follows
this requirement? See $# (sp?). [*]

[*] Of course, it is the ONLY sane semantic for Perl (see perldoc
perlnumber). However, AFAIK, it is not implemented.

Here is the history as I know it. About '96 somebody made a patch
which implemented *this* semantic. With a very noticable slowdown as
a side effect.

At the moment I had no idea how numbers are handled in Perl. When (in
2 or 3 years) I discovered what a mess it is (I wrote an automated
testing system, and about 60% of sanity tests were failing), I started
to fix it. Now: a part of the fix was a consistent caching of the
results of number-->string conversion [**].

[**] As a resent message about print() shows, it is not THAT
consistent now...

With *this* fix in place, switching to slower number-->string
conversion MIGHT have been cured of significant slowdowns. However,
myself, I never found tuits to check this.

Yours,
Ilya
 

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,755
Messages
2,569,536
Members
45,014
Latest member
BiancaFix3

Latest Threads

Top