The below code mimics actual production code. double quotes are used as the actual data comes from an XML file, parsed using XML:Twig
:
#!/usr/bin/perl
use strict;
use warnings;
use diagnostics;
use Devel::Peek;
my $linetotalinclusive = "8458.80" * 1_000_000;
$linetotalinclusive = $linetotalinclusive;
my $c = "7980.00" * 1_000_000;
my $data = $linetotalinclusive - $c;
print Dump $c;
print Dump $linetotalinclusive;
print "$linetotalinclusive - $c = $data \n";
Gives the following result:
SV = PVNV(0x22885f0) at 0x21984f8
REFCNT = 1
FLAGS = (PADMY,IOK,NOK,pIOK,pNOK)
IV = 7980000000
NV = 7980000000
PV = 0
SV = PVNV(0x2288650) at 0x21984c8
REFCNT = 1
FLAGS = (PADMY,NOK,pIOK,pNOK)
IV = 8458799999
NV = 8458800000
PV = 0
8458800000 - 7980000000 = 478799999.999999
When run on my laptop and on our production server. (the above is from my laptop)
However, when I run it on another production machine, it works fine.
use integer;
on the above code makes it work. But I am unable to do that to production code (easily). So, I was would like to know...
Further info: This is from the broken machine:
This is perl 5, version 18, subversion 1 (v5.18.1) built for x86_64-linux-thread-multi
perl -MPOSIX -le 'print LONG_MAX'
9223372036854775807
perl -V:[in]vsize
ivsize='8';
nvsize='8';
This is from the machine that works:
This is perl, v5.8.9 built for x86_64-linux-ld
perl -MPOSIX -le 'print LONG_MAX'
9223372036854775807
perl -V:[in]vsize
ivsize='8';
nvsize='16';
This gives the expected answer:
Summary of my perl5 (revision 5 version 8 subversion 9) configuration:
Platform:
osname=linux, osvers=2.6.32-431.3.1.el6.x86_64, archname=x86_64-linux-ld
uname='linux 553291-amon-sul2.firstb2b.net 2.6.32-431.3.1.el6.x86_64 #1 smp sat jan 4 02:04:49 est 2014 x86_64 x86_64 x86_64 gnulinux '
config_args=''
hint=recommended, useposix=true, d_sigaction=define
usethreads=undef use5005threads=undef useithreads=undef usemultiplicity=undef
useperlio=define d_sfio=undef uselargefiles=define usesocks=undef
use64bitint=define use64bitall=define uselongdouble=define
usemymalloc=n, bincompat5005=undef
Compiler:
cc='cc', ccflags ='-fno-strict-aliasing -pipe -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -I/usr/include/gdbm',
optimize='-O2',
cppflags='-fno-strict-aliasing -pipe -I/usr/local/include -I/usr/include/gdbm'
ccversion='', gccversion='4.4.7 20120313 (Red Hat 4.4.7-4)', gccosandvers=''
intsize=4, longsize=8, ptrsize=8, doublesize=8, byteorder=12345678
d_longlong=define, longlongsize=8, d_longdbl=define, longdblsize=16
ivtype='long', ivsize=8, nvtype='long double', nvsize=16, Off_t='off_t', lseeksize=8
alignbytes=16, prototype=define
Linker and Libraries:
ld='cc', ldflags =' -L/usr/local/lib'
libpth=/usr/local/lib /lib /usr/lib /lib64 /usr/lib64 /usr/local/lib64
libs=-lnsl -lgdbm -ldb -ldl -lm -lcrypt -lutil -lc
perllibs=-lnsl -ldl -lm -lcrypt -lutil -lc
libc=, so=so, useshrplib=false, libperl=libperl.a
gnulibc_version='2.12'
Dynamic Linking:
dlsrc=dl_dlopen.xs, dlext=so, d_dlsymun=undef, ccdlflags='-Wl,-E'
cccdlflags='-fPIC', lddlflags='-shared -O2 -L/usr/local/lib'
Characteristics of this binary (from libperl):
Compile-time options: PERL_MALLOC_WRAP USE_64_BIT_ALL USE_64_BIT_INT
USE_FAST_STDIO USE_LARGE_FILES USE_LONG_DOUBLE
USE_PERLIO
This one doesn't:
Summary of my perl5 (revision 5 version 8 subversion 8) configuration:
Platform:
osname=linux, osvers=2.6.18-194.26.1.el5, archname=x86_64-linux-thread-multi
uname='linux x86-002.build.bos.redhat.com 2.6.18-194.26.1.el5 #1 smp fri oct 29 14:21:16 edt 2010 x86_64 x86_64 x86_64 gnulinux '
config_args='-des -Doptimize=-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -Dversion=5.8.8 -Dmyhostname=localhost -Dperladmin=root@localhost -Dcc=gcc -Dcf_by=Red Hat, Inc. -Dinstallprefix=/usr -Dprefix=/usr -Dlibpth=/usr/local/lib64 /lib64 /usr/lib64 -Dprivlib=/usr/lib/perl5/5.8.8 -Dsitelib=/usr/lib/perl5/site_perl/5.8.8 -Dvendorlib=/usr/lib/perl5/vendor_perl/5.8.8 -Darchlib=/usr/lib64/perl5/5.8.8/x86_64-linux-thread-multi -Dsitearch=/usr/lib64/perl5/site_perl/5.8.8/x86_64-linux-thread-multi -Dvendorarch=/usr/lib64/perl5/vendor_perl/5.8.8/x86_64-linux-thread-multi -Darchname=x86_64-linux-thread-multi -Dvendorprefix=/usr -Dsiteprefix=/usr -Duseshrplib -Dusethreads -Duseithreads -Duselargefiles -Dd_dosuid -Dd_semctl_semun -Di_db -Ui_ndbm -Di_gdbm -Di_shadow -Di_syslog -Dman3ext=3pm -Duseperlio -Dinstallusrbinperl=n -Ubincompat5005 -Uversiononly -Dpager=/usr/bin/less -isr -Dd_gethostent_r_proto -Ud_endhostent_r_proto -Ud_sethostent_r_proto -Ud_endprotoent_r_proto -Ud_setprotoent_r_proto -Ud_endservent_r_proto -Ud_setservent_r_proto -Dinc_version_list=5.8.7 5.8.6 5.8.5 -Dscriptdir=/usr/bin'
hint=recommended, useposix=true, d_sigaction=define
usethreads=define use5005threads=undef useithreads=define usemultiplicity=define
useperlio=define d_sfio=undef uselargefiles=define usesocks=undef
use64bitint=define use64bitall=define uselongdouble=undef
usemymalloc=n, bincompat5005=undef
Compiler:
cc='gcc', ccflags ='-D_REENTRANT -D_GNU_SOURCE -fno-strict-aliasing -pipe -Wdeclaration-after-statement -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -I/usr/include/gdbm',
optimize='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic',
cppflags='-D_REENTRANT -D_GNU_SOURCE -fno-strict-aliasing -pipe -Wdeclaration-after-statement -I/usr/local/include -I/usr/include/gdbm'
ccversion='', gccversion='4.1.2 20080704 (Red Hat 4.1.2-50)', gccosandvers=''
intsize=4, longsize=8, ptrsize=8, doublesize=8, byteorder=12345678
d_longlong=define, longlongsize=8, d_longdbl=define, longdblsize=16
ivtype='long', ivsize=8, nvtype='double', nvsize=8, Off_t='off_t', lseeksize=8
alignbytes=8, prototype=define
Linker and Libraries:
ld='gcc', ldflags =''
libpth=/usr/local/lib64 /lib64 /usr/lib64
libs=-lresolv -lnsl -lgdbm -ldb -ldl -lm -lcrypt -lutil -lpthread -lc
perllibs=-lresolv -lnsl -ldl -lm -lcrypt -lutil -lpthread -lc
libc=, so=so, useshrplib=true, libperl=libperl.so
gnulibc_version='2.5'
Dynamic Linking:
dlsrc=dl_dlopen.xs, dlext=so, d_dlsymun=undef, ccdlflags='-Wl,-E -Wl,-rpath,/usr/lib64/perl5/5.8.8/x86_64-linux-thread-multi/CORE'
cccdlflags='-fPIC', lddlflags='-shared -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic'
Characteristics of this binary (from libperl):
Compile-time options: MULTIPLICITY PERL_IMPLICIT_CONTEXT
PERL_MALLOC_WRAP USE_64_BIT_ALL USE_64_BIT_INT
USE_ITHREADS USE_LARGE_FILES USE_PERLIO
USE_REENTRANT_API
Simplifying a little, floating point numbers are stored in binary; that is, each number internally is stored as a certain number of bits of mantissa (significant digits) (commonly 53) and an exponent that shows what power of 2 to multiply that mantissa by.
Most decimal numbers are not exactly representable in this format. For instance, 8458.8 might be represented as 0b10000100001010110011001100110011001100110011001100110 * 2**-39. This is a number that is slightly smaller than 8458.8, but is the closest possible representable number. Because it is smaller, if you multiply it by 100 and then int it, you will get 845879, not 845880.
You multiplying your input by a large power of ten, which, if numbers were exactly stored, would give an integer. Because you know it should be an integer, you should round it at that point; then numbers represented as either slightly smaller or slightly larger than the exact number will come out correct:
use strict;
use warnings;
my $linetotalinclusive = int( "8458.80" * 1_000_000 + .5 );
$linetotalinclusive = $linetotalinclusive;
my $c = int( "7980.00" * 1_000_000 + .5 );
my $data = $linetotalinclusive - $c;
print "$linetotalinclusive - $c = $data \n";
Or alternatively, round after your computations.
The suggestion to compile perl with uselongdouble will (if your machine supports it) use 64 bits of precision instead of 53. This will affect whether particular numbers are represented as larger or smaller than the exact value, but there will still be some numbers that go each way.