php comparison explained

php is an awful language and we all know it. It has a very silly comparison algorithm for loose comparison (==) which I am going to try to explain.

If we compare variables of different types (bool, string, int, float, array, object) with each other, php tries to cast them into a common type. But some types have no “common ground” and can never under any value assignment, be considered equal. This Venn-Diagram shows you, which stuff can be compared with each other to, in any way, get variables that are considered equal.

So first, let’s look at the “impossible” comparisons:

  • object and int/float: Will return false and a notice that the object cannot be converted in the specific number type
  • array and anything but bool: will silently fail and always return false

Comparison of object:

  • object and boolean: an object will always be true, if it actually is an object. If you get a false comparison, you don’t actually have an object. Probably it is the null-type.
  • object and string: Will return false and a notice if the object has no __toString()-method and true, if the object has a __toString()-Method, the comparison will return true if both strings are considered equal.
  • object and object: Two objects are considered equal if all their properties are considered equal (loose comparison according to all other rules).
    Note: strict comparison will only return true if it is the same actual object (instead of strict-comparing all properties).

Comparison of array:

  • array and boolean: Will always return true of the array is non-empty, regardless of the values of the content.
  • array and array: Will return true if all key-value pairs are considered equal by loose comparison for values and strict comparison for keys. array(false) and array(array()) would be considered equal since false and the empty array are considered equal.
    Remember that array keys are either int or string. String-keys that look exactly like their int-representation, and only their int-representation, (ex. “8″, not “010″ or “0×8″) it will be silently cast to int. So [1 => true] and ["1" => true] are considered equal, [1 => true] and ["1 "=>true] are not. Float keys will be converted to int on array creation/Insertion, as will “exotic” int notations. Therefore [10e2 => false] and ["1000" => false] are considered equal and [1.1 => false, 1.2 => true] only holds one value (the latter, false). For that reason, even [1.0 => false] and ["1.0" => false] are not considered equal, whereas [1.3 => false] and ["1" => false] are considered equal.
    Strict comparison of arrays also checks type and order of keys, as well as the type of the values.

comparison of string:

  • string and object: see above (object)
  • string and bool: A non-empty string is always considered true, regardless of content.
    Exception: The string “0″ is considered false. “0.0″, “00″, ” 0″ are still considered true, as is “0foo”.
  • string and string: Two strings are considered equal if they stand up to a case-sensitive string-comparison. Therefore, “a” and “A” are considered unequal.
    Exception: If both strings look exactly like numbers, they are compared as numbers. Therefore all these are considered equal: “200″, “2e2″, “200.0″, “0xC8″, “0xc8″.
    Exception:This is not true for the octal representation (“0310″) or anything that is not exactly a number (“200 “). The octal representation is always ignored in this context, since “0200″ is considered equal to “200″ (but 200 is not equal to 0200).
  • string and int/float: The string is, come what may, converted to a number. The string gets trimmed (spaces, newlines, tabs removed) and if it starts with a number, it is converted that way. Here are some examples “8bottles” -> 8, “8.0bottles” -> 8.0, “8e2bottles” -> 800.0. Then, usual comparison rules for int/floats applies.
    Exception: This doesn’t work for hexadecimal (“0x12foo” -> 0) or octal (“010foo”->10)

comparison of float/int:

  • float/int and string: see above
  • float/int and bool: Every number that is not 0 or 0.0 is considered true.
  • float/int and float/int: if both are the same type, they are checked against each other. If one of both types is float and the other is int, the int-type will be converted to float. Floating point precision loss may apply, depending on your architecture. (0.3 – 0.2)*10 is not considered equal to 1 (or 1.0 for that matter), as (0.3 – 0.2) is not considered equal to 0.1

So, let us recap the insanity. Strings are compared as strings, unless they both look like numbers, in which case they will both be converted from any number representation, except octal, which is considered decimal. This “look like a number” is a different “looks like a number” than when string-array-indices are being cast, where only true decimal representations get converted. When you try to compare a string to an integer, the string will be correctly cast to an integer, except when it is the octal representation, which gets cast as a decimal. But not if the number in the string is followed by any other character, then hexadecimal conversion will not work.

It seems like a mess.

If you want to check what you have learned, here is a fun quiz to check your knowledge.

2 Gedanken zu “php comparison explained

  1. php erlaubt mir doch, Äpfel und Birnen zu vergleichen. Das sollte dann wenigstens konsistent sein.

    Nicht getypt zu sein, ist ein Feature von php. Aber dann sollte das implizite typecasten funktionieren und offensichtlich sein. Ich meine, die object/int-comparison bekommt es ja auch hin, mit Fehler zu beenden.

    Die Regeln in der Strings woanders hin gecastet werden, sind besonders schwachsinnig. Besonders der Unterschied zwischen Scientific, Hexedezimal und Oktaler repräsentation.

    Dass jeder nicht-leere String true ist, ist ok. Und dann ist “0″ die Ausnahme. Aber “00″ ist trotzdem true. Bei toInt(“010foo”) wird die 0 dann aber beim casten ignoriert (womit die Oktale repräsentation kaputt geht). Warum?

    “010″ und “8″ (als strings) sind bei loose comparison gleich. Aber als array-indizes wird das eine zu int runtergecastet und das andere nicht. Warum?


Kommentar verfassen