This isn’t botched code per se, but it’s something I’ve implemented for several projects, and then had to go back and fix later every time. I’m putting it here so others can learn from my apparent inability to.
Say you have a list of IP addresses that you need to store in a database:
$ips = array('74.28.103.255', '192.168.0.3', '15.47.199.20');
Storing each number in a separate field would be dumb. So you store them as a formatted string, and then realize that you can’t sort them the way you need to!
$sorted_ips = $sort($ips);
$sorted_ips = Array(
[0] =>; '15.47.199.20',
[1] => '192.168.0.3', <- WRONG!
[2] => '74.28.103.255'
)
That’s the correct alphabetical ordering, but you don’t want that; you want the numeric ordering. Digging through the PHP documentation, you find the ip2long() function that converts IP addresses to integers between 0 and 232-1. Perfect!
foreach ($ips as &$ip) {
$ip = ip2long($ip);
}
$sorted_ips = sort($ips);
foreach ($sorted_ips as &$sorted_ip) {
$sorted_ip = long2ip($sorted_ip);
}
$sorted_ips = Array(
[0] => '192.168.0.3', <- STILL WRONG!
[1] => '15.47.199.20',
[2] => '74.28.103.255'
)
But now your problem gets even stranger. When you try to look up entries by IP, you can’t find them even though you know they’re in the database. And even weirder, some of the numbers being stored are negative! And it’s only happening with IP addresses that start with 128 or higher! What’s going on here?
Sound familiar? Congratulations, you’re the victim of a type mismatch! See, most computers are still 32-bit, and can store any number between 0 and 232-1 in a single register. But if you might need to store a negative number, you lose a bit for the sign, and can only store between -216 and 216-1 instead. In languages where data types are enforced, these are treated as “unsigned” vs. “signed” integers.
PHP, however, treats all integers as signed, which means any integers larger than 216-1 are interpreted incorrectly. And not all MySQL data types can handle values that big either, for the exact same reason. If you’re seeing negative numbers in your database, you need to change the data type of that field to one that supports numbers up to 232-1, such as BIGINT. If you’re seeing them in your business logic, you need to use the %u option of sprintf, which casts the value as an unsigned integer.
foreach ($ips as &$ip) {
$ip = floatval(sprintf("%u",ip2long($ip)));
}
$sorted_ips = sort($ips);
foreach ($sorted_ips as &$sorted_ip) {
$sorted_ip = long2ip($sorted_ip);
}
$sorted_ips = Array(
[0] => '15.47.199.20',
[1] => '74.28.103.255'
[2] => '192.168.0.3', <- RIGHT!
)
The best part is that 64-bit computing won’t fix this; it’ll just increase the range of numbers that we can get away with using before this problem emerges.