Search code examples
perloperators

Perl numbers don't equal each other but appear the same when printed to STDOUT


I am reading in numbers from two different data files, and doing some basic math on them (adding them together to get a total for one file, and taking the absolute value for the other file).

When I try to compare them with the == operator, they don't appear equal! But I don't get any error messages about how the variables aren't numbers, or anything like that. I think I'm missing something easy, but I've been banging my head against a wall.

my $vendor_total = 0;
            
print "hash total: ", $our_payments{$id}{'total'}, "\n\n";

my $our_total = abs($our_payments{$id}{'total'});
print "our total: $our_total\n";
            
foreach my $payer ( @{ $our_payments{$id}{'payers'} } ) {
    print "Adding ", $vendor_payments{$last_name}{$payer}{'total'},
        " to vendor total $vendor_total.\n";

    $vendor_total += $vendor_payments{$last_name}{$payer}{'total'};
}
        
print "\nVendor total: $vendor_total\n";
print "Our total: $our_total\n";
            
if ($our_total == $vendor_total) {
    print "Yay! They match!\n";
}
else {
    print "Boo! They don't match!\n";
}

And here's the relevant output:

hash total: -25856.73

our total: 25856.73
Adding 25856.73 to vendor total 0.

Vendor total: 25856.73
Our total: 25856.73
Boo! They don't match!

The only thing I can think of is that their might be some weird encoding problem with the files (there's accent symbols on names in one of them) but I don't see how that would affect the strings being converted to numbers.

I thought that by outputting the data to screen, I'd be able to see what's going on, but the numbers look exactly the same to me, and I'm running into this issue with multiple "matching" numbers, so I know there's something programmatically wrong.


Solution

  • What Every Programmer Should Know About Floating-Point Arithmetic

    #!/usr/bin/perl
    use warnings;
    use strict;
    
    my %our_payments = (
        one => {total  => '12.1',
                payers => [qw[ x y ]]}
    );
    my %vendor_payments = (
        last => {x => {total => 9.9 },
                 y => {total => 2.2 }}
    );
    my $last_name = 'last';
    my $id = 'one';
    
    my $vendor_total = 0;
    
    print "hash total: ", $our_payments{$id}{'total'}, "\n\n";
    
    my $our_total = abs($our_payments{$id}{'total'});
    print "our total: $our_total\n";
    
    foreach my $payer ( @{ $our_payments{$id}{'payers'} } ) {
        print "Adding ", $vendor_payments{$last_name}{$payer}{'total'},
            " to vendor total $vendor_total.\n";
    
        $vendor_total += $vendor_payments{$last_name}{$payer}{'total'};
    }
    
    printf "\nVendor total: %.20f\n", $vendor_total;
    printf "Our total: %.20f\n", $our_total;
    
    if ($our_total == $vendor_total) {
        print "Yay! They match!\n";
    }
    else {
        print "Boo! They don't match!\n";
    }
    

    Output:

    hash total: 12.1
    
    our total: 12.1
    Adding 9.9 to vendor total 0.
    Adding 2.2 to vendor total 9.9.
    
    Vendor total: 12.10000000000000142109
    Our total: 12.09999999999999964473
    Boo! They don't match!
    

    To fix the problem, don't use ==, but rather something like

    if (abs($our_total - $vendor_total) < 1E-10) {
    

    or

    if (sprintf('%.10f', $our_total) eq sprintf('%.10f', $vendor_total)) {