Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid
 
PerlMonks  

Proper monetary rounding

by blogical (Pilgrim)
on Mar 04, 2007 at 22:22 UTC ( [id://603141]=CUFP: print w/replies, xml ) Need Help??

When converting a monetary amount that includes a fractional amount of the lowest possible denomination into the closest real amount, use the "round to even" method of rounding to avoid trending upward. See Rounding#Round-to-even_method for specifics on this method of rounding, implemented in Math::BigFloat->ffround. Examples:
1.5000 -> $1.50 1.5050 -> $1.50 1.5051 -> $1.51
and
1.5100 -> $1.51 1.5150 -> $1.52

Edit: Adding a list of relevant links that may be of interest.

sub monetize { require Math::BigFloat; #Defaults to a real $USD amount, rounding to nearest penny Math::BigFloat->new($_[0])->ffround( $_[1] || -2 ); }

Replies are listed 'Best First'.
Re: Proper monetary rounding
by izut (Chaplain) on Mar 04, 2007 at 22:35 UTC

    You should use require instead of use if you want lazy importing. Check perldoc -f use and perldoc -f require.

    Igor 'izut' Sutton
    your code, your rules.

      Changed.
Re: Proper monetary rounding
by merlyn (Sage) on Mar 05, 2007 at 00:46 UTC
    The built-in sprintf implements round-to-even already:
    for (qw(1.0049 1.0050 1.0051 1.0149 1.0150 1.0151 1.0249 1.0250 1.0251 +)) { printf "%s => %0.2f\n", $_, $_; } ... 1.0049 => 1.00 1.0050 => 1.00 1.0051 => 1.01 1.0149 => 1.01 1.0150 => 1.01 1.0151 => 1.02 1.0249 => 1.02 1.0250 => 1.02 1.0251 => 1.03
    Hmm. Why didn't that work? {grin}

      That's what I had been doing, until I heard that accountants are actually picky about this sort of thing.

      "Round to even" considers the case where the number just past the least significant is a 5, followed only by zeroes. It then rounds odd LSDs up to the nearest even, rounding off even LSDs.

        I think he's saying that sprintf actually does that, in just that way:

        perl -e 'my $x = 1.5050; my $y = 1.5150; my $s = sprintf "%0.2f\n%0.2f +\n", $x, $y; print $s;' 1.50 1.52

        "Normal" (gradeschool) rounding would produce 1.51 and 1.52 there, but sprintf says 1.50 and 1.52, which seems an aweful lot like round-to-even to me. How does it differ from what you wanted?

        As far as accountants being picky about this... have you ever seen the movie Superman 3?

        -- 
        We're working on a six-year set of freely redistributable Vacation Bible School materials.
Re: Proper monetary rounding
by blogical (Pilgrim) on Mar 05, 2007 at 15:59 UTC
    Here's a little toy to examine just what's going on here. Try it out and play with the ranges.
    #!/usr/bin/perl -w use strict; require Math::BigFloat; my $sig = $ARGV[0] || 2; my $ini = $ARGV[1] || 0; my $end = $ARGV[2] || 1; my $inc = $ARGV[3] || 0.0001; test_methods($sig, $ini, $end, $inc); sub test_methods { my ($sig, $ini, $end, $inc) = @_; my $count = 0; my %result = (); print "For $ini to $end, incrementing by $inc, with $sig significa +nt digit". ($sig==1 ? '' : 's') ."\n"; for (my $num = $ini;$num<$end;$num += $inc) { $count++; $result{'real'} += $num; $result{'ffround'} += Math::BigFloat->new($num)->ffround( -$si +g ); $result{'sprintf'} += sprintf "%0.${sig}f", $num; printf "%-20s || %-10s || %-20s\r", ($result{'real'}/$count), +($result{'ffround'}/$count), ($result{'sprintf'}/$count); } print ' 'x58 . "\rOver $count iterations:\n"; print " Method | Sum | Average\n"; for (qw[real ffround sprintf]) { printf "%9s | %-20s| %-20s\n", $_, $result{$_}, ($result{$_}/$ +count); } return \%result; }

    "One is enough. If you are acquainted with the principle, what do you care for the myriad instances and applications?"
    - Henry David Thoreau, Walden

Re: Proper monetary rounding
by ysth (Canon) on Mar 07, 2007 at 12:40 UTC
    Round to even doesn't work so well on floating point, unless you are rounding to an integer. This is because it is supposed to do round to closest unless the amount rounded off is 1/2th, 1/20th, 1/200th, 1/2000th, etc. (depending on what decimal place you are rounding to), and of those, only 1/2 is representable in floating point. None of the others will necessarily exactly occur, so the special round-to-even rule is not always actually invoked.

    What you really ought to do is pick an epsilon that represents how much off your floating point number may be from what the actual infinitely precise value would have been - taking into account both the inaccuracy of floating point representation and any round-off errors involved in calculations that led to the number in question. Given the latter factor, there is no one epsilon that will suit any problem. Then you apply the round-to-even rule if your number is within epsilon of 1/20th, 1/200th, etc.

    I have the suspicion that the ffround is working because at some point the number is converted to string form, and then to a bigfloat internal form, and the conversion to string does a slight amount of rounding for you. If so, it won't necessarily work 100% of the time.

Re: Proper monetary rounding
by Moron (Curate) on Mar 07, 2007 at 14:07 UTC
    The problem is that if there is any validity in the argument about trending upward, all this does it make it trend downward, which is not so clever if you are invoicing rather than paying out :).

    In fact either kind of trending is a bug that comes from rounding when you shouldn't - instead, maintain maximum possible precision throughout the lifetime of your data and only round for the purposes of display and reporting alone - then either type of rounding won't be cumulative (i.e. "trend-forming").

    -M

    Free your mind

      Unless it's a lattitudinal trend across a large number of figures being calculated at the same time, all being derived from the same initial figure using the same formula, the sum of which consistantly exceeds the original figure due to a bias in the method of calculation.

      The point of the round to even method, if properly implemented, is that it recognizes the situations where error is likely to occur and neutralizes the tendancy to err in either direction.

      What you said about avoiding rounding- ie transformation of the source figure- is true. But it isn't necessarily a longitudinal problem.

        Bias has a continuous possibly increasing relationship with the input which has definite mean effect and is apt to have a Poisson distribution whereas rounding is discrete (only renders an effect at regular intervals) and does not render a mean overall effect. The probability of it being feasible to control one with the other is therefore zero. A better and tried and tested idea would be either feed back or feed forward a predictive or measured negation of the bias.

        -M

        Free your mind

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: CUFP [id://603141]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others romping around the Monastery: (1)
As of 2024-04-19 18:30 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found