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

Does anyone know of a module that implements fixed precision numbers in Perl? Our program does monetary calculations. Using floating point numbers to represent money is dangerous because of rounding and precision issues. Currently, our program represents monetary values as integers with a scaling factor. This means that the scaling factor shows up everywhere the program formats and rounds values. It also gets stored in the database which is really messy. I would like to use a class that encapsulates the calculation and formatting logic.

Replies are listed 'Best First'.
Re: Fixed precision numbers
by hsmyers (Canon) on Feb 25, 2003 at 19:20 UTC
    CPAN, try Math::FixedPrecision as a possible starting place otherwise, hit google find a fp package you like wrap and port it---nice piece of work for a project!

    --hsm

    "Never try to teach a pig to sing...it wastes your time and it annoys the pig."
Re: Fixed precision numbers
by tachyon (Chancellor) on Feb 25, 2003 at 19:59 UTC

    Math::FixedPrecision Math::BigInt Math::Currency and overload contains elements of what you are looking for.

    As you state there are some significant problems inherent in floating point math. The solution is to use integers (as you are) internally and then making them fixed precision numbers for output. If you want it neat make the numbers objects and then just overload + / - * to behave as you want.

    package FP; use overload '+' => \&add, '-' => \&subtract, '*' => \&multiply, '/' => \÷ my $num = new FP( "10.000" ); print $num + $num; sub new { my ( $class, $num, $precision ) = @_; if ( $num =~ m/\.(\d+)$/ ) { $precision = length $1; $num = $num * 10 ** $precision; } return bless { int => $num, exp => $precision }, $class; } sub add { my ( $obj1, $obj2 ) = @_; # just for testing lets KISS of course you need to do this right # let's assume the same exp for both objects by default return ($obj1->{int} + $obj2->{int}) / (10 ** $obj1->{exp}) . " was +processed"; } sub subtract { } sub multiply { } sub divide { }

    cheers

    tachyon

    s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print

Re: Fixed precision numbers
by derby (Abbot) on Feb 25, 2003 at 19:37 UTC
Re: Fixed precision numbers
by Juerd (Abbot) on Feb 25, 2003 at 19:17 UTC

    You can use sprintf('%.2f', $foo) to round it. There's probably a module does this automatically, but creating one isn't hard. Just tie a variable.

    { package BlahBlah; use Tie::Scalar; @ISA = 'Tie::StdScalar'; sub STORE { ${+shift} = sprintf '%0.2f', pop } } tie my $foo, 'BlahBlah'; $foo = 1; print "$foo\n"; # 1.00 $foo = 1.005; print "$foo\n"; # 1.00 $foo = 1.025; print "$foo\n"; # 1.02 $foo = 0; print "$foo\n"; # 0.00

    Juerd
    - http://juerd.nl/
    - spamcollector_perlmonks@juerd.nl (do not use).
    

      It's not that simple. sprintf rounds mathematically (rounding .5 up or down, depending on... I think whether the integer portion is odd or even).

      for (0.35,0.45){print sprintf("%.1f",$_);print "\n"} # result: 0.3 0.5

      That won't work for accounting: you always need to round up.

      Dave

        That won't work for accounting: you always need to round up.

        No, it won't work for maths, but it *will* work for accounting. I think it was merlyn who told me sprintf's rounding was called "Banker's rounding" and showed me why it was important. Google has a lot of information about this phenomenon. In short: halves are rounded towards the nearest even integer, and that solves a lot of problems.

        I don't know about international accounting differences, but I don't think biasing rounding up is a good thing to do.

        Juerd
        - http://juerd.nl/
        - spamcollector_perlmonks@juerd.nl (do not use).
        

Re: Fixed precision numbers
by BrowserUk (Patriarch) on Feb 25, 2003 at 20:37 UTC

    What are you calculating? Emelda Marcos' shoe budget:)

    With accuracy to 15-digits at least, perls floats would handle sums of cash upto around $90 trillion if you maintained the numbers as cents rather than dollars & cents and just divided by 100 for output.


    ..and remember there are a lot of things monks are supposed to be but lazy is not one of them

    Examine what is said, not who speaks.
    1) When a distinguished but elderly scientist states that something is possible, he is almost certainly right. When he states that something is impossible, he is very probably wrong.
    2) The only way of discovering the limits of the possible is to venture a little way past them into the impossible
    3) Any sufficiently advanced technology is indistinguishable from magic.
    Arthur C. Clarke.
      You're making a lot of assumptions about the posters data ... in particular that they are using US Currency. They said "monetary units". They could be dealing with a wide variety of Currencies, which could each have their own scaling factor. They also may be dealing with a billing system that requires them to handle "fractions of a cent"

        I could have said pounds & pence, or yen ??, the principle holds for most currencies I'm familiar with, in that they use 100 minor:1 major unit.

        Granted, if we start talking Yen, you start to loose accuracy in the 100th of a Yen range when totals grow larger than around $750,000,000,000 US dollars equivalent, but that is still larger than the gross national product of most, if not all, countries.

        Also, a quick (read: not thorough) investigation shows that most of the currencies that have high number exchange rates - the Yen, South Korean Won, the Italian Lire (deceased) do not have (or possibly no longer use) any minor denomination for their currancies, which makes inaccuracies 100 times less likely.

        I also didn't make any assumptions, I simply asked the question. I left it to OP to decide whether that was sufficient precision for his/her purpose. It could be that they work for a clearing bank and handle sums large enough that the cumulative totals require greater precision, although they usually use BCD field in COBOL programs for their calculations.

        I only really sought to point out that for most purposes, calculating in pence/cents/other rather than pounds&pence/dollars@cents/other&other allows perls floats to retain accuracy to numbers large enough to total the annual turnover of most companies.


        ..and remember there are a lot of things monks are supposed to be but lazy is not one of them

        Examine what is said, not who speaks.
        1) When a distinguished but elderly scientist states that something is possible, he is almost certainly right. When he states that something is impossible, he is very probably wrong.
        2) The only way of discovering the limits of the possible is to venture a little way past them into the impossible
        3) Any sufficiently advanced technology is indistinguishable from magic.
        Arthur C. Clarke.

      Brazil had really ferocious inflation a few years back, and the financial community had tremendous difficulties in dealing with currency values. Basically they ran their computer programs and had to say "ok, this value of 123000 stored here, well really, you have to add 9 zeroes behind it to get the real value". By the time they had managed to fix the systems to represent currency in a meaningful way, inflation had made the figures run away again. (I wish I could find a reference to this story, it makes for fascinating reading from a programming perspective). update i.e. because of inflation, computer programs kept running up against limits of number representations.

      A quick glance at a currency cross-rates page reveals that the current winner is the Turkish lira. You'll need about 1.6 million of them to buy a US dollar, and 1.7 million of them to buy a euro. Even a single lowly japanese yen will set you back about 13.7 thousand lira. And should you wish to acquire an ounce of platinum, better get out your wheelbarrow, because you'll have to muster up 1.1 billion lira.

      Ok, I know, this doesn't mean anything except that Turkish currency must be printed with an awful lot of zeroes, but we're talking about 6-7 orders of magnitude, which makes your capacity to represent $90 trillion melt down to a mere 9 million bucks.


      print@_{sort keys %_},$/if%_=split//,'= & *a?b:e\f/h^h!j+n,o@o;r$s-t%t#u'

        Which is why they had to withdraw the 10,000 and 5,000 Turkish lira coins from circulation. The metal was worth more than the face value, so people were exchanging larger denominations for smaller ones, melting them down and selling it for scrap value.

        I believe that is is also true that the banks refuse to handle quantites less that 10,000 at a time and simple round them out if they are present in a transaction.

        Quite why they have never simple revalued the denomination 10,000:1 I don't quite understand, maybe all the zeros make them feel rich:)

        BTW. If you've got a mere $9 billion going spare...:)


        ..and remember there are a lot of things monks are supposed to be but lazy is not one of them

        Examine what is said, not who speaks.
        1) When a distinguished but elderly scientist states that something is possible, he is almost certainly right. When he states that something is impossible, he is very probably wrong.
        2) The only way of discovering the limits of the possible is to venture a little way past them into the impossible
        3) Any sufficiently advanced technology is indistinguishable from magic.
        Arthur C. Clarke.
Re: Fixed precision numbers
by Abigail-II (Bishop) on Feb 25, 2003 at 21:51 UTC
    What's the smallest monetary unit you do calculations with? Compile yourself a 64bit Perl, and you can deal with numbers ranging from -9223372036854775807 to 9223372036854775807, without rounding errors. Even if you keep count in ten thousandth of a cent, you could track amounts to over 9 trillion this way.

    Abigail

      Ah, but even that is not sufficient to be able to record Bill Gate's desired net worth.