Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

As I've said in other posts, I'm a newbie, so please don't flame me if this is a stupid question. I have a science fair project for school that requires that I research various methods for overcoming rounding errors in large numbers. I noticed that when you are doing multiplication on numbers over about 17 characters or so (I'm told it depends on size of the floating point register on the machine.) perl rounds the number or truncates it. What are some methods of calculating numbers with a huge number of decimal places (20+)? I saw a library called Math::BigFloat that was supposed to help with this, but as a newbie, I can't seem to understand how to use it. Can anyone help me out?

Replies are listed 'Best First'.
Re: Rounding Errors
by mattr (Curate) on Jan 07, 2001 at 17:26 UTC
    Well I checked it out and ended up writing a tutorial for you..

    Here's what I did when you asked that question:

    Look up http://search.cpan.org
    Search for Math::BigFloat
    Click on "Math::BigFloat" as it appears under Perl5.005_03
    Realize it must already be installed on my system!

    So at command line I typed man "Math::BigFloat" for the manual.
    You probably got this far, but it is a bit terse
    (read "cryptic").
    If you don't have a Unix command line in front of you, check
    out the manual page which shows up at the above link:
    http://search.cpan.org/doc/GBARR/perl5.005_03/lib/Math/BigFloat.pm

    There is a little longer manual page for the version of the library
    provided with perl 5.7, which I don't have but here's the link
    (it showed up in the search results above):
    http://search.cpan.org/doc/JHI/perl-5.7.0/lib/Math/BigFloat.pm

    This library is object oriented, meaning that it lets you create
    an object which represents a long floating point number, and then
    perform operations on such objects. It also transforms ("overloads")
    common arithmetic operators so that you can have fun and appear to
    multiply and divide really huge number objects together by just typing
    a multiplication or division sign. Overloading is quick and fun but it is
    sometimes hard to debug, and may not give you access to subtle points
    in functionality of the library.

    The Synopsis section shows the "use" line you need to load the library,
    and a sample line which shows you how to create a bigfloat object.

    By picking a variable name and setting it to the output of the
    library's "new" command you can generate a new bigfloat object based on
    a long floating point number which you provide as a string, in quotes.
    You could append a number of strings together to make a longer one if you like.
    Since Perl uses the same type (scalar) variable for strings and numbers,
    you can mix them together. Either of these following two ways of stating it
    would create a bigfloat object (technically, an "instance" of the object) for you:

    $alongstring = "143208.13420789709814309781432"; # just some random keystrokes

    $hisfloat = Math::BigFloat->new($alongstring);
    $herfloat = new Math::BigFloat($alongstring);

    You can also leave out the parentheses, as the documentation does.

    The "new" method (think subroutine) is special in that you can type it in advance
    so it sounds like English. But normally an arrow is used when you want to access
    a method which belongs to a certain instance of an object. Since $hisfloat and
    $herfloat inherit the methods defined in the library, such as "fmul" and "fdiv".
    Here is the same thing first calling methods, then using the overloading provided
    by the library. I guessed that NSTR in the documentation means "any number string
    or a bigfloat object". Usually overloading just works with objects, but it is
    even looser than that in this library (which can be dangerous, see below).

    print $hisfloat->fmul($herfloat);
    print $hisfloat * $herfloat;

    Here is a test you can do on the command line (all on one line):

    perl -e 'use Math::BigFloat; $s="14372098.1320498713209847102398471"; $f = new Math::BigFloat($s); $g = Math::BigFloat->new("13409870123087.1329481723049817230498712304981"); print $f * $g * 3.14159 , "\n";'
    605472261221004971587.2079631921116943252504708191763655250636246607274730865378109

    The library seems to add engineering notation when you call by method instead of using overloaded arithmetic operators (no loss of precision):

    perl -e 'use Math::BigFloat; $s="14372098.1320498713209847102398471"; $f = new Math::BigFloat($s); $g = Math::BigFloat->new("13409870123087.1329481723049817230498712304981"); print $f->fmul($g * 3.14159) , "\n";'
    +6054722612210049715872079631921116943252504708191763655250636246607274730865378109E-61

    But truncates it to normal short floating point length, when you put a bare number outside the parentheses:

    perl -e 'use Math::BigFloat; $s="14372098.1320498713209847102398471"; $f = new Math::BigFloat($s); $g = Math::BigFloat->new("13409870123087.1329481723049817230498712304981"); print $f->fmul($g) * 3.14159 , "\n";'
    6.05472261221005e+20

    Which I fixed by keeping everything a BigFloat object:

    perl -e 'use Math::BigFloat; $s="14372098.1320498713209847102398471"; $f = new Math::BigFloat($s); $g = Math::BigFloat->new("13409870123087.1329481723049817230498712304981"); print $f->fmul($g) * (new Math::BigFloat("3.14159")) , "\n";'
    605472261221004971587.2079631921116943252504708191763655250636246607274730865378109

    I can't tell if this is flakiness or proper functioning, since the docs do say that it is a development version I am using.

    I recommend that you do everything in a program, one operation per line, and only
    explicit method calls, not operator overloading, to ensure debuggable, consistent results.

    You can also look at the module code itself for hints as to what is going on..
    try "locate BigFloat.pm" on your system. You will note that unless you explicitly
    specify scale, $div_scale is 40 in the code which means you get only 40 digits of
    accuracy in that case.

    Here is an example of the difference which global settings for your library will have. Instead of div_scale (which can be set on a per-instance basis anyway) I am looking at trunc:

    perl -e 'use Math::BigFloat; $s="14372098.1320498713209847102398471"; $f = new Math::BigFloat($s); $g = Math::BigFloat->new("13409870123087.1329481723049817230498712304981"); print $f->fdiv($g) , "\nmode: "
    +107175520718176952100999428361427933683486722E-50
    mode: even

    perl -e 'use Math::BigFloat; $Math::BigFloat::rnd_mode = "trunc"; $s="14372098.1320498713209847102398471"; $f = new Math::BigFloat($s); $g = Math::BigFloat->new("13409870123087.1329481723049817230498712304981"); print $f->fdiv($g) , "\nmode: ", $Math::BigFloat::rnd_mode, "\n";'
    +1071755207181769521009994283614279336834867219E-51
    mode: trunc



    Well that was fun, back to work for me.. Hope this helps.

    Matt
      Thanks ever so much for all your help. With the information you gave me I was able to get the science fair report done on time today and I learned a lot that I didn't know before. I appreciate each of you taking the time to explain it to me. That tutorial especially helped, as I didn't understand what many of those terms (like overloading) meant until you explained them. Thank you!
Re: Rounding Errors
by Anonymous Monk on Jan 07, 2001 at 01:26 UTC
    use Math::BigFloat; $float0 = new Math::BigFloat "1.23456789123456789123"; $float1 = new Math::BigFloat "2.123123123123123123123"; print $float0*$float1;
Re: Rounding Errors
by blueAdept (Beadle) on Jan 08, 2001 at 11:06 UTC
    The truncation problem you mention with large decimal numbers is a hardware(platform) issue. Its because of the way floating point math is done, its simulated really. The FPU is a processor/set of optimized operations that do integer math behind the scenes.

    Floating point numbers need to be represented in binary, but not just the number itself. Things like whether they're positive or negative, and their power(decimal position).

    Standardized schemes are used internally for doing this, but each time the computer encounters a floating point number it needs to digest/squish it down into the special binary format. So one floating point number(i.e. 1.234) is represented with a few binary numbers(values) - the number, its power, and sign. Its during this conversion process that things get truncated. Depending upon the bit width of the FPU(as you mentioned), the conversion process will differ in the amount of truncation that needs to be done. I believe the computer doesn't do any rounding(just truncation), but this truncation could mess up any rounding you tried to do within your code.

    To avoid the truncation you can do everything with whole numbers(integer values), its just more work. Any floating point math equation can be solved using whole numbers(multiplying everything out to get rid of the decimal).

    There is a similar problem with large integers when the values surpass what can be natively reprsented by the bit width(if its 32bit, something greater than 2^32) But thats much much less of an issue...

    DISCLAIMER: This is all simplified..I'm not into electrical engineering, not an expert in the area. Just taking a shot at answering the question :)
    hope this all helps - blueAdept