4

I lose precision when doing arithmetic or trying to print (debug) numbers this big: 1234567890.123456789

I think my problems are with $d (result of arithmetic) and the formatted print of $e. How can I force long doubles? My Perl version (5.8.4 on SUN) says it's possible. sprintf has a size option for long doubles (q or L or ll), but I haven't figured out how to use it, and don't know if it would work with printf.

Edit: I added BigFloat, which works! But I'd still like to force long doubles.

Try to add 1234567890 + 0.123456789 and subtract 1234567890 - 0.123456789.

use Config;
use Math::BigFloat;
$a = 1234567890;
$b = 123456789;
$c = $b/1e9;                   # 0.123456789
$d = $a + $c;                  # not enough precision (32-bit or double?)
$e = sprintf("%d.%.9d",$a,$b); # combine as strings
$f = 1234567890.123456789;     # for reference (not enough precision)

# Use BigFloat to bypass lack of longdbl
$aBig = Math::BigFloat->new("$a");
$dSum = $aBig->fadd("$c");         # $dSum = $a + $c
$aBig = Math::BigFloat->new("$a"); # <-- Need a new one for every operation?
$dDif = $aBig->fsub(abs("$c"));    # $dDif = $a - $c

print "a $a\n";             # 1234567890  
print "c $c\n";             # 0.123456789
print "d=a+c $d\n";         # 1234567890.12346  <-- **Problem**
print "dSum=a+c $dSum\n";   # 1234567890.123456789  <-- Solution
print "dDif=a-c $dDif\n";   # 1234567890.876543211  <-- Solution
print "e $e\n";             # 1234567890.123456789
print "f $f\n";             # 1234567890.12346  <-- double, 52-bit, not longdbl? 
printf ("printf    e 20.9f %20.9f\n",$e);    # 1234567890.123456717 <-- **Problem**
printf ("printf dSum 20.9f %20.9f\n",$dSum); # 1234567890.123456717 <-- **Problem**
printf ("printf dSum 20s %20s\n",$dSum);     # 1234567890.123456789 
printf ("printf dDif 20.9f %20.9f\n",$dDif); # 1234567890.876543283 <-- **Problem**
printf ("printf dDif 20s %20s\n",$dDif);     # 1234567890.876543211 

print "uselongdouble $Config{uselongdouble}\n"; # empty. No long doubles by default
print "d_longdbl $Config{d_longdbl}\n";         # "define". Supports long doubles
print "size double longdbl $Config{doublesize} $Config{longdblsize}\n"; # Ans 8 16

I also used this code to try to understand the types, but it didn't help much. Has anyone used it to explain problems like this?

use Devel::Peek 'Dump';

Dump ($dSum); # Wow, it's complicated
Dump ($f);
dnvrdave
  • 117
  • 2
  • 7
  • 5
    uselongdouble will tell whether your perl was compiled using long doubles or not; if it wasn't, you need to recompile perl. but Math::BigFloat could be useful instead. – ysth Jan 13 '15 at 23:51
  • Related: http://stackoverflow.com/q/3722679/827263 – Keith Thompson Jan 14 '15 at 00:11
  • @ysth: By which I assume you mean `$Config{uselongdouble}` which requires `use Config` – Borodin Jan 14 '15 at 00:54
  • 5
    correct. re the Dump() output, there's only one native perl floating point type, the NV. it is normally a double, but $Config{uselongdouble} indicates perl was compiled to have it be a long double. note that even a long double will have barely enough precision to store your 1234567890.123456789, so you may need to look at Math::BigFloat anyway – ysth Jan 14 '15 at 01:05
  • I added BigFloat and it works. Thanks! I also added $f which seems to indicate that I'm only using double. How can I force long double? – dnvrdave Jan 15 '15 at 00:09
  • 1
    You probably want to update that Perl. You can get a slightly less antiquated version as an [OpenSolaris package](http://www.opencsw.org/packages/perl/), or you can compile one up easily with [perlbrew](http://perlbrew.pl/). There have been [a lot of upgrades to Math::BigFloat](https://metacpan.org/changes/distribution/Math-BigInt) since 1.44 which shipped with 5.8.4 – Schwern Jan 15 '15 at 00:27
  • This explains why printf didn't work. http://stackoverflow.com/questions/5426791/how-can-i-printf-a-perl-bignum-without-losing-precision – dnvrdave Jan 17 '15 at 00:02

2 Answers2

3

Perl has one size of float, and it's called NV. The size of an NV is decided when Perl is built.

$ perl -V:nvsize
nvsize='8';

This information is also accessible within a Perl program via the Config module.

$ perl -MConfig -E'say $Config{nvsize}'
8

The size of NV cannot be changed after Perl is built.

You can force Perl to be built to use long double floats as follows when when building Perl:

sh Configure -Duselongdouble ...
  -or-
perlbrew install -Duselongdouble ...
  -or-
perlbrew install --ld ...

If you don't want to rebuild your perl or make a new one, you will need to use a module. I recommend Math::LongDouble as it provides access to native long double floats, and it does so as transparently as possible.

Another option is to use an arbitrary-precision library such as Math::BigFloat, but that will be slower that necessary if all you need is a long double.

ikegami
  • 367,544
  • 15
  • 269
  • 518
1

bignum will overload all operators in the current scope to use arbitrary precision integers and floating point operations.

use bignum;

my $f = 123456789.123456789;
print "$f\n";         # 123456789.123456789
print $f + $f, "\n";  # 246913578.246913578

Behind the scenes, bignum turns all numeric constants into Math::BigInt and Math::BigNum objects as appropriate.

It's important to note that bignum is lexically scoped and does not effect the whole program. For example...

{
    use bignum;
    $f = 123456789.123456789;  # $f is a Math::BigNum object
}

$g = 123456789.123456789;      # $g is a regular NV

print "$f\n";     # 123456789.123456789
print "$g\n";     # 123456789.123457

# This will use Math::BigNum's addition method, but $g has already lost precision.
print $f + $g, "\n";  # 246913578.246913789

You can get a bit better performance out of this by using the Math::BigInt::GMP plugin to use the GNU Multiple Precision Arithmetic Library for some operations. You have to install that module first using the normal CPAN install process (you don't need GMP). Then tell bignum to use GMP.

use bignum lib => "GMP";
Schwern
  • 153,029
  • 25
  • 195
  • 336