Features of logical comparison in PHP
On April 4th, a question arose on stackoverflow regarding the operation of comparison operators in PHP. Almost immediately, he received a detailed answer. Surely for many this is an interesting topic.
PHP is famous for its type casting. I spent a lot of time looking for the basics of comparison logic in it.
For example: if it
Guided by the simplest logic, I can assume that this expression is also true, however, I do not really trust PHP in this matter. Can someone give me an example in which this statement would be false?
I am also interested in the work of the "more" and "less" operators. Will the result of the comparison change when the expression is turned over:
For most type combinations, the operation of the comparison operators more / less is not documented.
The comparison operator in PHP at some points differs from the canonical definition:
The relation of equality should be reflective, symmetric and transitive:
The relation
You can find some amazing graphs in the article PHP Sadness 52 - Comparison Operators .
And in the end, I want to note that two equalities in PHP are guaranteed (unlike almost everything else) because the interpreter brings them to the same form:
In PHP (relevant for version 5.5.0beta2) there is no strict comparison> == or <== with type checking, but there are several ways to compare types before comparing more / less:
Also note the following:
Fun string comparison:
Also look at the type comparison table in PHP for comparisons:
Code that generated lists on the Git Hub
PHP version differences: http://3v4l.org/MAfDu
Question
PHP is famous for its type casting. I spent a lot of time looking for the basics of comparison logic in it.
For example: if it
$a > $b
is truth and $b > $c
is truth, does this mean that it is $a > $c
also truth? Guided by the simplest logic, I can assume that this expression is also true, however, I do not really trust PHP in this matter. Can someone give me an example in which this statement would be false?
I am also interested in the work of the "more" and "less" operators. Will the result of the comparison change when the expression is turned over:
# precondition:
if ($a === $b) {
throw new Exception(
'both are strictly equal, can not compare strictly for greater or smaller'
);
}
($a > $b) !== ($b > $a)
For most type combinations, the operation of the comparison operators more / less is not documented.
Answer
The comparison operator in PHP at some points differs from the canonical definition:
The relation of equality should be reflective, symmetric and transitive:
- The operator
==
in PHP is not reflective , that is, it is$a == $a
not always true:
Note: The fact that NAN comparisons are always false is not a feature of PHP. This behavior is defined in the IEEE 754 standard for the format for the representation of floating-point numbers ( explanation on stackoverflow );var_dump(NAN == NAN); // bool(false)
- The operator is
==
symmetric , that is,$a == $b
and$b == $a
always equal; - The operator is
==
not transitive , i.e.,$a == $b
it$b == $c
does not mean that$a == $c
:var_dump(true == "a"); // bool(true) var_dump("a" == 0); // bool(true) var_dump(true == 0); // bool(false)
The relation
<=
/ >=
must be non-reflective, antisymmetric, and transitive:- The operator
<=
in PHP is not reflective , that is, the expression is$a <= $a
not always true (see the example for==
); - The operator
<=
is not antisymmetric , that is a true expression..$a <= $b
And$b <= $a
not to say that$a == $b
:var_dump(NAN <= "foo"); // bool(true) var_dump("foo" <= NAN); // bool(true) var_dump(NAN == "foo"); // bool(false)
- The operator
<=
is not transitive , t. E. True expression$a <= $b
and$b <= $c
does not mean that$a <= $c
(for example the same as that for the operator==
). - The operator is
<=
not complete , i.e., and$a <= $b
, and$b <= $a
may be false:var_dump(new stdClass <= new DateTime); // bool(false) var_dump(new DateTime <= new stdClass); // bool(false)
<
/ >
must be antireflexive, asymmetric and transitive:- The operator
<
in PHP is anti-reflexive , that is,$a < $a
it is always false. This is relevant since PHP 5.4. In previous versionsINF < INF
is true; - The operator is
<
not asymmetric , that is, the truth of the expression$a < $b
does not mean that it!($b < $a)
is true (see the example for<=
); - The operator
<
is not transitive .., Ie, from the truth$a < $b
and$b < $c
does not follow that$a < $c
also is true:var_dump(-INF < 0); // bool(true) var_dump(0 < TRUE); // bool(true) var_dump(-INF < TRUE); // bool(false)
- Additionally: The operator is
<
not trichotomous , that is, expressions$a < $b
,$b < $a
and$a == $b
can be false (the example is the same as for<=
); - Additionally: The operator
<
can be looped , that is, there are cases when$a < $b
,$b < $c
and$c < $a
are true:
Note: This example generates a warning “Object of class stdClass could not be converted to double” of the notice level.var_dump(INF < []); // bool(true) var_dump([] < new stdClass); // bool(true) var_dump(new stdClass < INF); // bool(true)
You can find some amazing graphs in the article PHP Sadness 52 - Comparison Operators .
And in the end, I want to note that two equalities in PHP are guaranteed (unlike almost everything else) because the interpreter brings them to the same form:
($a > $b) == ($b < $a)
($a >= $b) == ($b <= $a)
UPD: Second answer
In PHP (relevant for version 5.5.0beta2) there is no strict comparison> == or <== with type checking, but there are several ways to compare types before comparing more / less:
- Compare Variable Types
if ( gettype($a)===gettype($b) ) ...
- Explicitly cast variables to the desired type
if ( (string)$a===(string)$b ) ...
- Use type manipulation
if ( ($a.'')===($b.'') ) ...
Also note the following:
- Floating-point numbers have limited precision;
- The constants NAN and INF are of type float;
- Comparing INF to INF is mathematically incorrect;
- Numbers in e-notation are a type of float, even if they are small;
- Integers greater than PHP_INT_MAX are automatically converted to floating point numbers;
- Floating-point numbers outside the bounds defined by the system contain INF;
- Undeclared variables return NULL;
- When assigned, integers starting with 0 are converted from octal to decimal;
- When a string is cast to an integer, it loses its leading zeros.
A few specific comparisons
Unusual
$a VS. $b $a>$b $a<$b $a<=$b $a>=$b $a==$b $a===$b
float(NAN) float(-INF) false false false false false false
float(NAN) float(0) false false false false false false
float(NAN) float(1) false false false false false false
float(NAN) float(INF) false false false false false false
float(NAN) float(NAN) false false false false false false
float(NAN) int(-1) false false false false false false
float(NAN) int(0) false false false false false false
float(NAN) int(1) false false false false false false
Translator's note: The answer above says that any comparison with NAN returns false, so there is nothing unusual here.Equal but not identical
$a VS. $b $a>$b $a<$b $a<=$b $a>=$b $a==$b $a===$b
NULL(NULL) array() false false true true true false
NULL(NULL) bool(false) false false true true true false
NULL(NULL) float(0) false false true true true false
NULL(NULL) int(0) false false true true true false
NULL(NULL) str('') false false true true true false
array() bool(false) false false true true true false
bool(false) float(0) false false true true true false
bool(false) int(0) false false true true true false
str('') bool(false) false false true true true false
bool(false) str('0') false false true true true false
float(-INF) bool(true) false false true true true false
bool(true) float(1) false false true true true false
float(INF) bool(true) false false true true true false
float(NAN) bool(true) false false true true true false
bool(true) int(-1) false false true true true false
bool(true) int(1) false false true true true false
bool(true) str("\0") false false true true true false
bool(true) str('+') false false true true true false
bool(true) str('-') false false true true true false
bool(true) str('01') false false true true true false
bool(true) str('1') false false true true true false
bool(true) str('false') false false true true true false
str('text') bool(true) false false true true true false
str('true') bool(true) false false true true true false
int(0) float(0) false false true true true false
str("\0") float(0) false false true true true false
str('') float(0) false false true true true false
str('+') float(0) false false true true true false
str('-') float(0) false false true true true false
str('0') float(0) false false true true true false
str('false') float(0) false false true true true false
str('text') float(0) false false true true true false
str('true') float(0) false false true true true false
int(1) float(1) false false true true true false
float(1) str('01') false false true true true false
float(1) str('1') false false true true true false
str("\0") int(0) false false true true true false
str('') int(0) false false true true true false
str('+') int(0) false false true true true false
str('-') int(0) false false true true true false
int(0) str('0') false false true true true false
str('false') int(0) false false true true true false
str('text') int(0) false false true true true false
str('true') int(0) false false true true true false
int(1) str('01') false false true true true false
int(1) str('1') false false true true true false
str('1') str('01') false false true true true false
And more and less at the same time?
$a VS. $b $a>$b $a<$b $a<=$b $a>=$b $a==$b $a===$b
float(NAN) str("\0") true true true true false false
float(NAN) str('') true true true true false false
float(NAN) str('+') true true true true false false
float(NAN) str('-') true true true true false false
float(NAN) str('0') true true true true false false
float(NAN) str('01') true true true true false false
float(NAN) str('1') true true true true false false
float(NAN) str('false') true true true true false false
float(NAN) str('text') true true true true false false
float(NAN) str('true') true true true true false false
Identically
$a VS. $b $a>$b $a<$b $a<=$b $a>=$b $a==$b $a===$b
NULL(NULL) NULL(NULL) false false true true true true
float(-INF) float(-INF) false false true true true true
float(INF) float(INF) false false true true true true
More or less
$a VS. $b $a>$b $a<$b $a<=$b $a>=$b $a==$b $a===$b
NULL(NULL) bool(true) false true true false false false
float(-INF) NULL(NULL) true false false true false false
NULL(NULL) float(1) false true true false false false
float(INF) NULL(NULL) true false false true false false
float(NAN) NULL(NULL) true false false true false false
NULL(NULL) int(-1) false true true false false false
NULL(NULL) int(1) false true true false false false
NULL(NULL) str("\0") false true true false false false
NULL(NULL) str('+') false true true false false false
NULL(NULL) str('-') false true true false false false
NULL(NULL) str('0') false true true false false false
NULL(NULL) str('01') false true true false false false
NULL(NULL) str('1') false true true false false false
NULL(NULL) str('false') false true true false false false
NULL(NULL) str('text') false true true false false false
NULL(NULL) str('true') false true true false false false
array() bool(true) false true true false false false
float(-INF) array() false true true false false false
array() float(0) true false false true false false
array() float(1) true false false true false false
float(INF) array() false true true false false false
float(NAN) array() false true true false false false
array() int(-1) true false false true false false
array() int(0) true false false true false false
array() int(1) true false false true false false
array() str("\0") true false false true false false
str('') array() false true true false false false
array() str('+') true false false true false false
array() str('-') true false false true false false
array() str('0') true false false true false false
array() str('01') true false false true false false
array() str('1') true false false true false false
array() str('false') true false false true false false
array() str('text') true false false true false false
array() str('true') true false false true false false
bool(true) bool(false) true false false true false false
float(-INF) bool(false) true false false true false false
float(1) bool(false) true false false true false false
float(INF) bool(false) true false false true false false
float(NAN) bool(false) true false false true false false
bool(false) int(-1) false true true false false false
int(1) bool(false) true false false true false false
bool(false) str("\0") false true true false false false
bool(false) str('+') false true true false false false
bool(false) str('-') false true true false false false
bool(false) str('01') false true true false false false
str('1') bool(false) true false false true false false
bool(false) str('false') false true true false false false
str('text') bool(false) true false false true false false
str('true') bool(false) true false false true false false
bool(true) float(0) true false false true false false
bool(true) int(0) true false false true false false
str('') bool(true) false true true false false false
bool(true) str('0') true false false true false false
float(-INF) float(0) false true true false false false
float(-INF) float(1) false true true false false false
float(INF) float(-INF) true false false true false false
float(-INF) int(-1) false true true false false false
float(-INF) int(0) false true true false false false
float(-INF) int(1) false true true false false false
float(-INF) str("\0") false true true false false false
float(-INF) str('') false true true false false false
float(-INF) str('+') false true true false false false
float(-INF) str('-') false true true false false false
float(-INF) str('0') false true true false false false
float(-INF) str('01') false true true false false false
float(-INF) str('1') false true true false false false
float(-INF) str('false') false true true false false false
float(-INF) str('text') false true true false false false
float(-INF) str('true') false true true false false false
float(1) float(0) true false false true false false
float(INF) float(0) true false false true false false
float(0) int(-1) true false false true false false
int(1) float(0) true false false true false false
float(0) str('01') false true true false false false
str('1') float(0) true false false true false false
float(INF) float(1) true false false true false false
float(1) int(-1) true false false true false false
float(1) int(0) true false false true false false
float(1) str("\0") true false false true false false
str('') float(1) false true true false false false
float(1) str('+') true false false true false false
float(1) str('-') true false false true false false
float(1) str('0') true false false true false false
float(1) str('false') true false false true false false
str('text') float(1) false true true false false false
str('true') float(1) false true true false false false
float(INF) int(-1) true false false true false false
float(INF) int(0) true false false true false false
float(INF) int(1) true false false true false false
float(INF) str("\0") true false false true false false
float(INF) str('') true false false true false false
float(INF) str('+') true false false true false false
float(INF) str('-') true false false true false false
float(INF) str('0') true false false true false false
float(INF) str('01') true false false true false false
float(INF) str('1') true false false true false false
float(INF) str('false') true false false true false false
float(INF) str('text') true false false true false false
float(INF) str('true') true false false true false false
int(0) int(-1) true false false true false false
int(1) int(-1) true false false true false false
str("\0") int(-1) true false false true false false
str('') int(-1) true false false true false false
str('+') int(-1) true false false true false false
str('-') int(-1) true false false true false false
str('0') int(-1) true false false true false false
int(-1) str('01') false true true false false false
str('1') int(-1) true false false true false false
str('false') int(-1) true false false true false false
str('text') int(-1) true false false true false false
str('true') int(-1) true false false true false false
int(1) int(0) true false false true false false
int(0) str('01') false true true false false false
str('1') int(0) true false false true false false
int(1) str("\0") true false false true false false
str('') int(1) false true true false false false
int(1) str('+') true false false true false false
int(1) str('-') true false false true false false
int(1) str('0') true false false true false false
int(1) str('false') true false false true false false
str('text') int(1) false true true false false false
str('true') int(1) false true true false false false
str('') str("\0") false true true false false false
str('+') str("\0") true false false true false false
str('-') str("\0") true false false true false false
str("\0") str('0') false true true false false false
str("\0") str('01') false true true false false false
str('1') str("\0") true false false true false false
str('false') str("\0") true false false true false false
str('text') str("\0") true false false true false false
str('true') str("\0") true false false true false false
str('') str('+') false true true false false false
str('') str('-') false true true false false false
str('') str('0') false true true false false false
str('') str('01') false true true false false false
str('') str('1') false true true false false false
str('') str('false') false true true false false false
str('') str('text') false true true false false false
str('') str('true') false true true false false false
str('-') str('+') true false false true false false
str('+') str('0') false true true false false false
str('+') str('01') false true true false false false
str('1') str('+') true false false true false false
str('false') str('+') true false false true false false
str('text') str('+') true false false true false false
str('true') str('+') true false false true false false
str('-') str('0') false true true false false false
str('-') str('01') false true true false false false
str('1') str('-') true false false true false false
str('false') str('-') true false false true false false
str('text') str('-') true false false true false false
str('true') str('-') true false false true false false
str('0') str('01') false true true false false false
str('1') str('0') true false false true false false
str('false') str('0') true false false true false false
str('text') str('0') true false false true false false
str('true') str('0') true false false true false false
str('false') str('01') true false false true false false
str('text') str('01') true false false true false false
str('true') str('01') true false false true false false
str('1') str('false') false true true false false false
str('text') str('1') true false false true false false
str('true') str('1') true false false true false false
str('text') str('false') true false false true false false
str('true') str('false') true false false true false false
str('true') str('text') true false false true false false
Examples with $ a> $ b> $ c in which $ a is at most $ c
A str('a') > str('')
A str('a') > str('1')
A str('a') > str('A')
A str('a') > str('0')
A str('1') > str('')
A str('1') > str('0')
A str('A') > str('')
A str('A') > str('1')
A str('A') > str('0')
A str('0') > str('')
A==C : str('') > float(NAN) > NULL(NULL)
A===C : str('') > float(NAN) > str('')
A float(NAN) > str('a')
A float(NAN) > str('1')
A==C : str('') > float(NAN) > bool(false)
A float(NAN) > str('A')
A float(NAN) > str('0')
A==C : str('') > float(-INF) > NULL(NULL)
A==C : str('') > float(-INF) > bool(false)
A==C : str('') > int(-1) > NULL(NULL)
A==C : str('') > int(-1) > bool(false)
A==C : str('') > float(-1) > NULL(NULL)
A==C : str('') > float(-1) > bool(false)
A==C : array() > float(NAN) > NULL(NULL)
A==C : array() > float(NAN) > bool(false)
A==C : array() > float(INF) > NULL(NULL)
A==C : array() > float(INF) > bool(false)
A==C : array() > float(-INF) > NULL(NULL)
A==C : array() > float(-INF) > bool(false)
A==C : array() > str('a') > NULL(NULL)
A==C : array() > str('a') > bool(false)
A==C : array() > int(1) > NULL(NULL)
A==C : array() > int(1) > bool(false)
A==C : array() > float(1) > NULL(NULL)
A==C : array() > float(1) > bool(false)
A==C : array() > str('1') > NULL(NULL)
A==C : array() > str('1') > bool(false)
A==C : array() > str('A') > NULL(NULL)
A==C : array() > str('A') > bool(false)
A==C : array() > str('0') > NULL(NULL)
A==C : array() > int(-1) > NULL(NULL)
A==C : array() > int(-1) > bool(false)
A==C : array() > float(-1) > NULL(NULL)
A==C : array() > float(-1) > bool(false)
A===C : str('a') > float(NAN) > str('a')
A str('') > float(NAN)
A str('1') > float(NAN)
A==C : str('a') > str('1') > int(0)
A==C : str('a') > str('1') > float(0)
A str('A') > float(NAN)
A str('0') > float(NAN)
A==C : bool(true) > str('') > float(NAN)
A==C : bool(true) > str('') > float(-INF)
A==C : bool(true) > str('') > int(-1)
A==C : bool(true) > str('') > float(-1)
A==C : bool(true) > array() > float(NAN)
A==C : bool(true) > array() > float(INF)
A==C : bool(true) > array() > float(-INF)
A==C : bool(true) > array() > str('a')
A==C : bool(true) > array() > int(1)
A==C : bool(true) > array() > float(1)
A==C : bool(true) > array() > str('1')
A==C : bool(true) > array() > str('A')
A==C : bool(true) > array() > int(-1)
A==C : bool(true) > array() > float(-1)
A==C : bool(true) > int(0) > float(-INF)
A==C : bool(true) > int(0) > int(-1)
A==C : bool(true) > int(0) > float(-1)
A==C : bool(true) > str('0') > float(NAN)
A==C : bool(true) > str('0') > float(-INF)
A==C : bool(true) > str('0') > int(-1)
A==C : bool(true) > str('0') > float(-1)
A==C : bool(true) > float(0) > float(-INF)
A==C : bool(true) > float(0) > int(-1)
A==C : bool(true) > float(0) > float(-1)
A==C : int(1) > str('a') > str('1')
A==C : int(1) > str('A') > str('1')
A==C : float(1) > str('a') > str('1')
A==C : float(1) > str('A') > str('1')
A float(NAN) > str('a')
A===C : str('1') > float(NAN) > str('1')
A float(NAN) > str('A')
A str('') > float(NAN)
A str('0') > float(NAN)
A float(NAN) > str('a')
A===C : str('A') > float(NAN) > str('A')
A str('') > float(NAN)
A str('1') > float(NAN)
A==C : str('A') > str('1') > int(0)
A==C : str('A') > str('1') > float(0)
A str('0') > float(NAN)
A==C : int(0) > float(-INF) > NULL(NULL)
A==C : int(0) > float(-INF) > bool(false)
A==C : int(0) > int(-1) > NULL(NULL)
A==C : int(0) > int(-1) > bool(false)
A==C : int(0) > float(-1) > NULL(NULL)
A==C : int(0) > float(-1) > bool(false)
A float(NAN) > str('a')
A float(NAN) > str('1')
A==C : str('0') > float(NAN) > bool(false)
A float(NAN) > str('A')
A===C : str('0') > float(NAN) > str('0')
A==C : str('0') > float(-INF) > bool(false)
A str('') > float(NAN)
A==C : str('0') > int(-1) > bool(false)
A==C : str('0') > float(-1) > bool(false)
A==C : float(0) > float(-INF) > NULL(NULL)
A==C : float(0) > float(-INF) > bool(false)
A==C : float(0) > int(-1) > NULL(NULL)
A==C : float(0) > int(-1) > bool(false)
A==C : float(0) > float(-1) > NULL(NULL)
A==C : float(0) > float(-1) > bool(false)
Fun string comparison:
'Queen' > 'King' > 'Jack' > 'Ace'
Also look at the type comparison table in PHP for comparisons:
isset()
andis_null()
- if () and empty ()
- Differences in
==
and===
Code that generated lists on the Git Hub
PHP version differences: http://3v4l.org/MAfDu