There is no doubt: PHP is an easy, flexible, and forgiving language. But it can also exhibit some surprising behavior. In this article I’ll present some “strange facts” and explain why PHP gives the results it does.
Floating Point Imprecision
Most of you probably know that floating-point numbers cannot really represent all real numbers. Besides, some operations between two well represented numbers can lead to surprising situations. This is because the finite precision with which computers represent numbers and so it affects not just PHP but all the programming languages. Inaccuracies in floating point problems have been given programmers headaches from the beginning of the discipline.
About this question many words have been said, but one of the most important articles written is What Every Computer Scientist Should Know About Floating-Point Arithmetic. If you’ve never had a chance to read it, I strongly suggest you do so.
Let’s look at this very small snippet:
<?php echo (int) ((0.1 + 0.7) * 10);
What do you think will be the result? If you guessed 8, you’d be wrong… I’m sorry, you lost the prize! This code will actually print 7! For those who have a Zend Certification, this example is already known. Infact, you can find it in Zend PHP 5 Certification Study Guide.
Now let’s see why this happens. The first operation executed is:
0.1 + 0.7 // result is 0.79999999999
The result of the arithmetic expression is stored internally as 0.7999999, not 0.8 due to the precision issue, and this is where the problem starts.
The second operation executed is:
0.79999999 * 10 = 7.999999999
This operation works well but preserves the error.
The third and last operation executed is:
(int) 7.9999999 // result is 7
The expression makes use of an explicit cast. When a value is convert to
int, PHP truncates the decimal portion of the number which makes the result 7.
There are two interesting things to note here:
- If you cast the number to
int, or if you don’t cast at all, you’ll get 8 as result as you expected.
- Although the CPU doesn’t know, and we actually we got this error due to its imprecisions, it’s working accordingly to the mathematics paradox that asserts 0.999… is equal to 1.
To conclude this point, I’d like to cite the Zend PHP 5 Certification Study Guide Second Edition:
Whenever the precision of your calculation is a relevant factor to the proper functioning of your application, you should consider using a (sic) the arbitrary precision functions provided by the BCMath extension (you can search for it in your copy of the PHP manual) instead of PHP’s built-in data types.
How PHP “Increments” Strings
During our everyday work we write instructions that perform incrementing/decrementing operations like these:
<?php $a = 5; $b = 6; $a++; ++$b;
Everyone easily understands what’s going on with those statements. But given the following statements, guess what will be the output:
<?php $a = 1; $b = 3; echo $a++ + $b; echo $a + ++$b; echo ++$a + $b++;
Did you write down your answers? Good. Let’s check ’em all.
4 6 7
Not too difficult, right? Now let’s raise the bar a little. Have you ever tried to increment strings? Try to guess what the output will be for the following:
<?php $a = 'fact_2'; echo ++$a; $a = '2nd_fact'; echo ++$a; $a = 'a_fact'; echo ++$a; $a = 'a_fact?'; echo ++$a;
It’s not so easy this time, is it. Let’s uncover the answers.
fact_3 2nd_facu a_facu a_fact?
Surprised? Using an increment operator on a string that ends with a number has the effect of increasing the letter (the one following it in the alphabet, e.g. t -> u). Regardless if the string starts with digits or not, the last character is changed. But there is no effect if the string ends with a non-alphanumeric character.
This is well documented by the official documentation about the incrementing/decrementing operators but most of you may not have read it because you probably thought nothing special was there. I must admit I thought the same until a short time ago.
PHP follows Perl’s convention when dealing with arithmetic operations on character variables and not C’s. For example, in PHP and Perl $a = ‘Z'; $a++; turns $a into ‘AA’, while in C a = ‘Z'; a++; turns a into ‘[‘ (ASCII value of ‘Z’ is 90, ASCII value of ‘[‘ is 91). Note that character variables can be incremented but not decremented and even so only plain ASCII characters (a-z and A-Z) are supported. Incrementing/decrementing other character variables has no effect, the original string is unchanged.
The Mystery of Value Appearance
You’re a real array master in PHP, admit it. Don’t be shy. You already know everything about how to create, manage, and delete arrays. Nonetheless, the next example might surprise you at first sight.
Often you work with arrays and need to search if a given value is in an array. PHP has a function for this,
in_array(). Let’s see it in action:
<?php $array = array( 'isReady' => false, 'isPHP' => true, 'isStrange' => true ); var_dump(in_array('sitepoint.com', $array));
What will be the output? Place your bets and let’s check it.
Isn’t this a little bit strange? We have an associative array where its values are all boolean and if we search for a string, we got
true. Has this value magically appeared? Let’s see another example.
<?php $array = array( 'count' => 1, 'references' => 0, 'ghosts' => 1 ); var_dump(in_array('aurelio', $array));
And what we get is…
Once again the
in_array() function returns true. How is this possible?
You’re experiencing one of both best and worst, loved and hated PHP features. PHP isn’t strongly typed and this is where the error starts. In fact, all the following values, if compared in PHP, are considered the same unless you use the identity operator:
By default, the
in_array() function uses the loose comparison, a non-empty (
"") and non-zero-filled (
"0") string is equal to
true and to all non-zero numbers (like
1). Hence, in the first example we got true because
'sitepoint.com' == true, while in the second we have
'aurelio' == 0.
To solve the problem you’ve to use the third and optional parameter of
in_array() that allows you to compare the values strictly. So, if we write:
<?php $array = array( 'count' => 1, 'references' => 0, 'ghosts' => 1 ); var_dump(in_array('aurelio', $array, true));
we’ll finally get
false as we expect!
Throughout this article you’ve seen some strange and unexpected behaviors you might experience while using PHP. The lessons to take away from these examples are:
- Never trust floating point numbers
- Double check what data type you’ve before using them in operations
- Be aware of the problems of loose comparisons and loose data types
If you’re an advanced user, you probably already knew them already, but a refresher is never worthless.
Image via Fotolia