I’m Really Starting to Hate Computers
Freedom is the right to say that two plus two makes four. If that is granted, all else follows.
—George Orwell, 1984
Yesterday I spend the latter part of the day in the studio finishing up a pen and ink drawing. Working in pen and ink makes sense to me. There’s a lot I don’t understand, but every thing I learn makes me better at what I do. The world makes sense when I’m putting ink on paper. If I want to better understand the shape of a shoulder, I can look at people’s shoulders and read anatomy books and practice drawing shoulders. Whether my marks on paper resemble a shoulder depends on a consistent set of rules. Yes, different people perceive things differently, but even so what I do at the drawing table develops a consistent visual language. Some things I do better than others, but I can keep working on my shortcomings and improving my ability to convey the sense of objects and/or ideas I wish others to see when they look at the paper.
Today I’m in the office earning money by working on some Perl code.
Perhaps this is just proof that I am incapable of understanding the complex languages and logic of machines, but after the better part of my life providing programmatic instructions to computers and more than a dozen years specifically working with Perl, sometimes I feel like the computer and I aren’t speaking the same language.
Sometimes I bang my head against a silly mistake I’ve overlooked, and after a few minutes or hours or days finally see what I’ve missed and get a good laugh at myself. Other times, like tonight, I simplify my problem down to the most basic parts, confirm that I’m doing what I think I’m doing, and stare dumbfounded at the machine that tells me that two plus two is not a number. And I just don’t get it.
Forgive the following code. It’s part of a test I wrote to see why a method I wrote wasn’t returning the values I thought it should. Don’t worry, I’ll explain.
my $retail = 0;
foreach my $rateid ( @{$self->{RATES}} ) {
my $quant = $self->{TICKETQUANTITIES}->{$rateid};
my $retail_rate = $self->{'TOUR'}->{'RATE'}{$rateid}{'retail_rate'};
my $coupon = $self->{COUPONAMOUNT};
$test .= "\n<!-- Retail: $retail_rate Quantity: $quant Discount: $coupon -->\n";
# now perform maths to prove these things are numbers
$retail_rate += 1.03;
$coupon += 1;
$quant +=1;
# are they really numbers?
$test .= "\n<!-- Retail: $retail_rate Quantity: $quant Discount: $coupon -->\n";
# Okay, now add them up!
my $discounted_rate = ($retail_rate - $coupon);
$retail += ($quant * ($retail_rate - $coupon));
$test .= "\n<!-- Discounted rate: $discounted_rate Retail total: $retail -->\n";
}
Okay, it’s simplified for testing but here are the basics. In this code, anything that starts with $self-> is getting some kind of information out of the database. In particular, I’m bringing up prices and checking for discounts and quantities for a particular order. So this part:
my $retail = 0;
foreach my $rateid ( @{$self->{RATES}} ) {
establishes $retail
as a place to store the total price for the entire order, then creates a loop where we look at all of the rates for this particular order, one at a time, and we identify the rate that we’re looking at with $rateid
. With me so far?
The first thing I do for each rate is get the particular information about the rate.
my $quant = $self->{TICKETQUANTITIES}->{$rateid};
my $retail_rate = $self->{'TOUR'}->{'RATE'}{$rateid}{'retail_rate'};
my $coupon = $self->{COUPONAMOUNT};
It’s sort of inscrutable, but trust me. It pulls numbers out of the database. They could even be the wrong numbers; it doesn’t matter. The very next thing I do is check to see what the numbers are. Since what I’m testing is part of a web application, I hide my tests in HTML comments that I can see when I look at the source of the web page. This is done by dumping the comments into a variable that I echo to the page later on. Just understand that whatever I add to the variable $test I can see after I run the code.
$test .= "\n<!-- Retail: $retail_rate Quantity: $quant Discount: $coupon -->\n";
Now, I want to make sure that these values really are numbers. So I perform some math on them, by adding numbers to them and sending the results to the $test variable where I can look at them after the addition.
# now perform maths to prove these things are numbers
$retail_rate += 1.03;
$coupon += 1;
$quant += 1;
# are they really numbers?
$test .= "\n<!-- Retail: $retail_rate Quantity: $quant Discount: $coupon -->\n";
Now is the frustrating part. I want to get everything added up so that I know how much the customer is going to pay. I’m sure that everyone whose credit card will be charged would like to pay the correct amount.
# Okay, now add them up!
my $discounted_rate = ($retail_rate - $coupon);
$retail += ($quant * ($retail_rate - $coupon) );
And then we show the values of $discounted_rate
(a new variable created just so that we can look at the adjusted rate) and $retail
$test .= "\n<!-- Discounted rate: $discounted_rate Retail total: $retail -->\n";
}
That last bracket closes the loop, saying that that’s the end of the code used to look at the rates.
If this has made any sense so far, you might guess like I did, that what I’ll see from this code will look something like this:
<!-- Retail: 109.98 Quantity: 2 Discount: 0 -->
<!-- Retail: 111.01 Quantity: 3 Discount: 1 -->
<!-- Discounted rate: 110.01 Retail total: 330.03 -->
<!-- Retail: 5.00 Quantity: 0 Discount: 0 -->
<!-- Retail: 6.03 Quantity: 1 Discount: 1 -->
<!-- Discounted rate: 5.03 Retail total: 5.03 -->
Instead, what I see is the more perplexing:
<!-- Retail: 109.98 Quantity: 2 Discount: 0 -->
<!-- Retail: 111.01 Quantity: 3 Discount: 1 -->
<!-- Discounted rate: NaN Retail total: NaN -->
<!-- Retail: 5.00 Quantity: 0 Discount: 0 -->
<!-- Retail: 6.03 Quantity: 1 Discount: 1 -->
<!-- Discounted rate: NaN Retail total: NaN -->
NaN
stands for «Not a Number». It’s something the computer tells me when I try to do some sort of impossible math, like division by zero or taking the square root of a negative number. But all I think I’ve done is a little addition, subtraction, and multiplication. If any of the things I tried to add or multiply were not numbers, I’d get this result. «“Wisconsin” + 3» for example, might equal NaN
. But everything I’ve been adding and subtracting and multiplying are numbers. I made sure that they were numbers by adding to them and checking that the values were correct.
So is there any wonder why I’d rather spend time in the studio than in the office? It’s simple: art makes logical sense. Computers don’t.
I look forward to being totally humiliated by the obvious answer that I’m missing. When I find it, I will post it here just to embarrass myself. Anyone reading this may feel free to point out what I’ve missed. Until then, I’m currently in my seventh hour of trying to determine why the computer is telling me that two plus two is not a number.
A few more things that don’t make sense
$discounted_rate ++; # this is a number
$discounted_rate += 1; # this is NOT a number (should be exactly the same as the previous line)
$discounted_rate = $discounted_rate + 1; # this is also not a number and should be the same as the above
Now it makes sense
OK, here’s what happened:
Elsewhere in this module I needed to throw some big numbers around, so I used a package called Math::BigInt to get access to numbers larger than those that Perl normally lets me work with. Because I’m lazy, I told Math::BigInt to take care of all my integers in this particular module.
Math::BigInt is pretty magical. It waits for any old integer to be used in the module, then steals it away and creates a blessed BigInt object in its place. In most cases it appears identical, but it’s not. All my numbers were no longer scalar values but objects that returned scalar values. This led to some subtle differences in the way my numbers were handled.
More on this in the PerlMonks post.
I’m still not sure exactly what’s going on inside Math::BigInt, but I think what’s happening is that it is intercepting a non-integer number when it isn’t supposed to, and, seeing that it is not an integer, reports it as not a number. The following code reproduces the problem:
It prints «NaN» unless the second line is commented out. I’m inclined to think this is a bug in Math::BigInt, but the behavior may be intentional. In any case, I moved the use statement to a place where it only affects a small chunk of code where it is needed and my problems have gone away.