PHP Array Keys and Floats

Wow! I found another obscure “feature” of PHP! Array keys were silently round off into integers when I tried to index by floating point numbers!

We’ve all heard PHP is “smart.” It converts stuff for you. If you try to add “4” and ” 2 ” together, it will make 6, even though the two is really a part of a word that happens to contain the number two in it. You see, PHP is very forgiving about mixing variable types. It just converts integers into floats, floats into strings, and strings into doubles without hesitation.

So imagine my surprise when I was creating an array using floating point numbers as keys. Why floating points? Coincidence.

A little “trick” I use is to create an array using some unique value as the key so that I can ensure there are no duplicates. For example, when I am working with result sets for a user, I might use the user’s user ID as the key so that if the result set contained that user again, I can ensure they show up only once in my final array. Other times, I might use the username, since PHP accepts strings as keys. And here was my problem.

It just so happened that the “ID” in this particular case was a float, which is very unusual, but hey, it happened. You see, sometimes my application grouped stuff by strings. Other times numbers. It just so happened this time was a float because that’s just what I was given. Here’s some demo code.

$totals = array();
// grouping results by subtotal
foreach($result as $record) {
    $unique = $record[‘subtotal’];
    $totals[$unique] = $record;
}

Examine that code. Now some people might say, “but why not just format your SQL so that the results are already grouped!” Well, that’s beyond the scope of this discussion, but I’ll entertain the notion. For me, changing a 2 page long SQL query that examined, on average, 2 – 4 million rows was not an option. I’m sure you can sympathize. That’s not the point anyway.

The point is that when you run the above code and “subtotal” happens to be a floating point number, weird shit happens. The unique values looked something like:

0, 0.1, 1, 1.95, 2, 5, 9.95, 35.50, 35.90, 35.99, etc.

The grouped results looked like this…

0, 1, 2, 5, 10, 36, etc

So it was very surprising when the actual result had about half as many uniques as it should have. What happened? Things were getting rounded off! So since most of the values had decimals in them, they were getting lumped together with their nearby counterparts, silently screwing up my results!

The lesson here is that array keys *can not* be floating numbers. And if you assign floating numbers (numbers with decimals), you must convert (cast) them into strings. So to fix the original code snippet…

$totals = array();
foreach($result as $record) {
    $unique = $record[‘subtotal’];
    $totals[(string) $unique] = $record;
}

That makes the number 0.1 into the string “0.1”. Thus, PHP is able to tell “1.1” apart from “1”. Remember, this only applies to array keys since PHP will normally distinguish the two numbers correctly.

And this is one of the many reasons how PHP trains programmers (including me) to be sloppy about their data types. Oh well.