Eugene Yarmash
Eugene Yarmash

Reputation: 150109

How do I perform decimal arithmetic in Perl?

I'm working on an accounting script written in Perl and I'm wondering what's the 'proper' way to perform decimal arithmetic calculations. For instance, I want to insure that comparisons like these work correctly:

"0.1" + "0.1" + "0.1" == "0.3"
"258.9" * "2000" == "517800"
...

In Python I'd use the Decimal type for the values, but what do I do in Perl?

Upvotes: 9

Views: 2131

Answers (3)

P. Pirn
P. Pirn

Reputation: 1

Here's how you might use Math::Decimal for division:

use 5.024003; # use `say'
use Math::Decimal qw( 
                     dec_mul_pow10 
                     dec_neg 
                     dec_rndiv 
                    ); 
my $num_1 = '3'; 
my $num_2 = '7'; 
my $precision = '3'; 

# You want to get three digits after the decimal, 
# so multiply dividend by 1000, divide to get an 
# integer quotient, and then divide that quotient 
# by 1000. 
my $dividend_up_some = dec_mul_pow10( $num_1, $precision ); 
# Rounding by `NEAR_EVN' is "bankers' rounding." 
my $quotient_up_some = 
          dec_rndiv( 'NEAR_EVN', $dividend_up_some, 
                     $num_2 ); 
# Move it back down to get your desired precision after 
# the decimal. 
my $quotient = 
      dec_mul_pow10( $quotient_up_some, 
                     dec_neg( $precision ) ); 
say "$num_1 / $num_2 = $quotient";

Run this program and here is the output:

3 / 7 = 0.429

Change $precision to '10' and here is the output:

3 / 7 = 0.4285714286

Upvotes: 0

Schwern
Schwern

Reputation: 165456

(NOTE: There is Math::Currency but it is currently broken).

Use Math::BigFloat to represent numbers as arbitrary precision objects.

use Math::BigFloat;

print Math::BigFloat->new(0.1) +
      Math::BigFloat->new(0.1) +
      Math::BigFloat->new(0.1) == Math::BigFloat->new(0.3);

You can do this automatically with bignum...

use bignum;

print 0.1 + 0.1 + 0.1 == 0.3;

BUT! the magic only works on numbers. If you try to add strings together it won't work, the magic comes too late. You have to explicitly force them to be numbers. To numify a string you can add 0 to the string, like $a += 0. Or you can force an equation to be done as bignums by starting with 0 + and it will cascade down the line.

use bignum;

$a = "0.1";
$b = "0.1";
$c = "0.1";
$d = "0.3";

# False
print $a + $b + $c == $d;

# True
print 0 + $a + $b + $c == $d;

Two caveats.

First, this all comes at a heavy performance cost. Not only for doing arbitrary precision math, but for all the methods and overloading magic. Benchmark it to see if this is acceptable. Fortunately bignum only upgrades numbers in its scope, not the whole program. It's also safe to use those numbers outside of bignum's scope, any math done with them will also be upgraded.

Second, Decimal will preserve significant figures. Math::BigFloat will not.

Upvotes: 8

Missaka Wijekoon
Missaka Wijekoon

Reputation: 899

Best way I know how is to test with absolute diff being less than a tolerance. For example:

perl -e '$x = 0.1 + 0.1 + 0.1; $y = 0.3; $q = abs($x - $y) < 0.0001 ? "EQUAL" : "NOT EQUAL"; print $q . "\n";'

Upvotes: -5

Related Questions