views:

197

answers:

3

What's the best technique for handling US Dollar calculations in Perl?

Especially: the following needs to work:

$balance = 10;
$payment = $balance / 3; # Each payment should be 3.33. How best to round amount?
$balance -= $payment * 3;
# assert: $balance == .01
+10  A: 

One common technique is to do all calculations in integer cents, then convert to dollars and cents for output. So your $10 balance would be represented by 1000 (cents), and dividing by three gives 333, or $3.33.

However, if you want to divide a $10 payment by three, you will need some way to end up with payments of $3.33, $3.33, and $3.34. This will be more up to your application logic and business rules than the arithmetic features of your language.

Greg Hewgill
One good thing would be also to hash out the details either with the lawyers (if it's one's own company) - so the proper user-facing agreements carry this in the small letters; or to ensure these details are very clearly spelled out in the product requirement documents if this is a contracting work. Making assumptions in this area is dangerous. For some strange reason people tend to be sensitive about it.
Andrew Y
+1 Yes, in the past, I've used this technique. I wanted to see if there's something better these days. Math::Currency looks good.
Larry K
+7  A: 

See Math::Currency.

Updated:

Assuming all payments adding up to the balance is desirable, I came up with the following script based on the points made by Greg Hewgill:

#!/usr/bin/perl

use strict;
use warnings;

use List::Util qw( sum );

my @balances = (10, 1, .50, 5, 7, 12, 3, 2, 8, 1012);

for my $balance (@balances) {
    my @stream = get_payment_stream($balance, 3);
    my $sum = sum @stream;
    print "$balance : @stream : $sum\n";
}

sub get_payment_stream {
    my ($balance, $installments) = @_;
    $balance *= 100;
    my $payment = int($balance / $installments);
    $installments -= 1;
    my $residual = $balance - int($payment * $installments);
    my @stream = (($payment) x $installments, $residual);
    return map { sprintf '%.2f', $_ / 100} @stream;
}

Output:

C:\Temp> p
10 : 3.33 3.33 3.34 : 10
1 : 0.33 0.33 0.34 : 1
0.5 : 0.16 0.16 0.18 : 0.5
5 : 1.66 1.66 1.68 : 5
7 : 2.33 2.33 2.34 : 7
12 : 4.00 4.00 4.00 : 12
3 : 1.00 1.00 1.00 : 3
2 : 0.66 0.66 0.68 : 2
8 : 2.66 2.66 2.68 : 8
1012 : 337.33 337.33 337.34 : 1012
Sinan Ünür
Thanks. I'll go with Math::Currency.
Larry K
A: 

use Math::Currency;

Not reinventing the wheel is a good thing :)