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

I'm working on an accounting application written in Perl. I want to be sure that I'm using the proper decimal arithmetic library. If I'm reading things correctly, the Math::BigFloat perl module can be used for accurate decimal arithmetic calculations. Am I correct? Will this module give me accuracy good enough for financial calculations?

Replies are listed 'Best First'.
Re: Decimal Arithmetic
by philcrow (Priest) on Feb 05, 2007 at 22:06 UTC
    When working with people's money it is usually wise to use pennies (or their local equivalent). This leaves you with integers and avoids (or at least delays) those nasty rounding problems. Only round to the penny at the end of a calculation and store only pennies.

    If the numbers are large, use Math::BigInt.

    If you have things like fractional stock shares, consider using the penny concept where a penny becomes the smallest unit you will display to the customer. Store those as ints.

    Phil

Re: Decimal Arithmetic
by liverpole (Monsignor) on Feb 05, 2007 at 22:09 UTC
    Hi gcoates,

    Depending on what you're trying to do, the built-in math functions in Perl should in most cases be fine for financial calculations, I would think.

    And no library is going to give you exact calculations, so if you're searching for something that's 100% accurate, you won't ever find it; there's always an example where normal rounding will cause errors to creep in.

    Can you provide an example of what kind of thing you're looking for?


    s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/

      And no library is going to give you exact calculations

      Why not? If you're just going to use the basic operators (+-/*%), then you can store any number precisely as a fraction of arbitrarily large integers.

            you can store any number precisely as a fraction of arbitrarily large integers

        Okay, how would you store pi precisely as a fraction of arbitrarily large integers?  How about the square root of 2?  Or e?

        My point is, unless you're using unlimited storage, there's always a potential for round-off to occur.  It may be very, very miniscule round-off, and it may not affect monetary calculations, but you've always got the potential for introducing such errors, depending on what operations you're performing.


        s''(q.S:$/9=(T1';s;(..)(..);$..=substr+crypt($1,$2),2,3;eg;print$..$/
      The place I'm having problems is with invoices in accounts payable. Each invoice I'm recording can have an arbitrary number of items that are purchased. Each invoice can also have a tax amount and a shipping/handling amount. The shipping and tax amounts need to be distributed to the accounts in which the invoice items are recorded. When I try to split these amounts, I'm getting rounding errors. (I'm using the Math::BigFloat library with an accuracy of 17 digits and a precision of 5 decimal places for my calculations.) Does this seem reasonable?
        When I try to split these amounts, I'm getting rounding errors.

        This sounds more like a business logic problem than a decimal precision problem.

        For example, I have $1.00 that I need to allocate out to 3 parties. If I just divide by 3 and round to the nearest penny, then everyone pays $0.33 and I'm short a penny.

        -xdg

        Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

        Splitting an invoice's lot-shipping charge so that it may be charged to accounts associated with specific line items is a task usually left to human judgement. There is no reason to imagine that
        • items belong to the same freight-rate class,
        • items are shipped from the same location,
        • an item's shipping weight is consistent
        Aside from the above, freight may be put into the cost of the line items or to a general ledger account, like Inbound Freight; how formally this policy is determined can vary.

        All of which is just to say: Ouch!

        Be well,
        rir

        5 decimal places precision

        It certainly seems reasonable to me, if you are calcuating to pennies. To be on the safe side, you could always round up/down (to the nearest penny) in favor of the customer. You could absorb the penny/per cost and raise your rate by a penny :-). If you need more accuracy, look at xs outputs faster than perl can keep up and use MPFR to as many decimal places of precision you want.


        I'm not really a human, but I play one on earth. Cogito ergo sum a bum
        This is purely a design problem. Why do you have a price for each item, but only a total tax for the invoice? Why not have a price for each item, a tax for each item, and a S&H for each item? Then, you aggregate those as the total tax and total S&H for the invoice.

        If, as I suspect, the S&H won't be amenable to that kind of breakout, then that's where your business problem arises.


        My criteria for good software:
        1. Does it work?
        2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: Decimal Arithmetic
by swampyankee (Parson) on Feb 06, 2007 at 02:19 UTC

    You could investigate to see if there is a binary-coded decimal module for Perl (although during a rather cursory search on CPAN, I did not find one). HIstorically, financial programming in COBOL was done with BCD (also known as packed decimal) as it maps better to decimal currency than the computers' normal binary arithmetic.

    emc

    Insisting on perfect safety is for people who don't have the balls to live in the real world.

    —Mary Shafer, NASA Dryden Flight Research Center
      See also the FAQ at http://www2.hursley.ibm.com/decimal/ for why it's a good idea to avoid binary floating-point. There's also a link there to the Perl BigNum library: http://dev.perl.org/perl6/pdd/pdd14_bignum.html which follows the General Arithmetic Specification. mfc