Четвертого апреля на stackoverflow появился вопрос, касающийся работы операторов сравнения в PHP. Почти сразу же на него поступил развернутый ответ. Наверняка для многих это является интересной темой.
PHP славится своим приведением типов. Я потратил много времени в поисках основ логики сравнения в нем.
Например: если
Руководствуясь простейшей логикой я могу предположить что это выражение также верно, однако я не очень доверяю PHP в этом в вопросе. Может кто-нибудь привести мне пример, в котором данное утверждение будет ложным?
Также мне интересна работа операторов «больше» и «меньше». Изменится ли результат сравнения при переворачивании выражения:
Для большинства комбинаций типов работа операторов сравнения больше/меньше не документирована.
Оператор сравнения в PHP в некоторых моментах отличается от канонического определения:
Отношение равенства должно быть рефлексивным, симметричным и транзитивным:
Отношение
Вы можете найти несколько восхитительных графиков в статье PHP Sadness 52 — Операторы сравнения.
И в конце я хочу отметить, что два равенства в PHP гарантированы (в отличии от почти всего остального) потому что интерпретатор приводит их к одному виду:
В PHP (актуально для версии 5.5.0beta2) нет строгого сравнения >== или <== с проверкой типов, но есть несколько способов сравнить типы до сравнения больше/меньше:
Также обратите внимание на следующее:
Забавное сравнение строк:
Также посмотрите таблицу сравнения типов в PHP в которой указаны сравнения:
Код, который сгенерировал списки на Git Hub
Различия в версиях PHP: http://3v4l.org/MAfDu
Вопрос
PHP славится своим приведением типов. Я потратил много времени в поисках основ логики сравнения в нем.
Например: если
$a > $b
является истиной и $b > $c
является истиной, значит ли это, что $a > $c
также является истиной?Руководствуясь простейшей логикой я могу предположить что это выражение также верно, однако я не очень доверяю PHP в этом в вопросе. Может кто-нибудь привести мне пример, в котором данное утверждение будет ложным?
Также мне интересна работа операторов «больше» и «меньше». Изменится ли результат сравнения при переворачивании выражения:
# precondition:
if ($a === $b) {
throw new Exception(
'both are strictly equal, can not compare strictly for greater or smaller'
);
}
($a > $b) !== ($b > $a)
Для большинства комбинаций типов работа операторов сравнения больше/меньше не документирована.
Ответ
Оператор сравнения в PHP в некоторых моментах отличается от канонического определения:
Отношение равенства должно быть рефлексивным, симметричным и транзитивным:
- Оператор
==
в PHP не рефлексивен, т. е.$a == $a
не всегда является истиной:
Примечание: То, что сравнение с участием NAN всегда является ложным не является особенностью PHP. Это поведение определено в стандарте IEEE 754 формата представления чисел с плавающей точкой (пояснения на stackoverflow);var_dump(NAN == NAN); // bool(false)
- Оператор
==
симметричен, т. е.$a == $b
и$b == $a
всегда равны;
- Оператор
==
не транзитивен, т. е.$a == $b
и$b == $c
не означает, что$a == $c
:
var_dump(true == "a"); // bool(true) var_dump("a" == 0); // bool(true) var_dump(true == 0); // bool(false)
Отношение
<=
/>=
должно быть не рефлексивным, антисимметричным и транзитивным:- Оператор
<=
в PHP не рефлексивен, т. е. выражение$a <= $a
не всегда является истинным (см. пример для==
);
- Оператор
<=
не антисимметричен, т. е. истинность выражений$a <= $b
и$b <= $a
не означает, что$a == $b
:
var_dump(NAN <= "foo"); // bool(true) var_dump("foo" <= NAN); // bool(true) var_dump(NAN == "foo"); // bool(false)
- Оператор
<=
не транзитивен, т. е. истинность выражений$a <= $b
и$b <= $c
не означает, что$a <= $c
(пример такой же, как и для оператора==
).
- Оператор
<=
не является полным, т. е. и$a <= $b
, и$b <= $a
могут быть ложными:var_dump(new stdClass <= new DateTime); // bool(false) var_dump(new DateTime <= new stdClass); // bool(false)
<
/>
должно быть антирефлексивным, асимметричным и транзитивным:- Оператор
<
в PHP антирефлексивен, т. е.$a < $a
всегда является ложным. Это актуально начиная с версии PHP 5.4. В предыдущих версияхINF < INF
является истинным;
- Оператор
<
не асимметричен, т. е. истинность выражения$a < $b
не означает, что!($b < $a)
является истинным (см. пример для<=
);
- Оператор
<
не транзитивен, т. е. из истинности$a < $b
и$b < $c
не следует, что$a < $c
также является истиной:var_dump(-INF < 0); // bool(true) var_dump(0 < TRUE); // bool(true) var_dump(-INF < TRUE); // bool(false)
- Дополнительно: Оператор
<
не трихотомичен, т. е. выражения$a < $b
,$b < $a
и$a == $b
могут быть ложными (пример такой же, как и для<=
);
- Дополнительно: Оператор
<
может быть закольцованным, т. е. бывают случаи, когда$a < $b
,$b < $c
и$c < $a
являются истинными:
Примечание: Этот пример генерирует предупреждение «Object of class stdClass could not be converted to double» уровня notice.var_dump(INF < []); // bool(true) var_dump([] < new stdClass); // bool(true) var_dump(new stdClass < INF); // bool(true)
Вы можете найти несколько восхитительных графиков в статье PHP Sadness 52 — Операторы сравнения.
И в конце я хочу отметить, что два равенства в PHP гарантированы (в отличии от почти всего остального) потому что интерпретатор приводит их к одному виду:
($a > $b) == ($b < $a)
($a >= $b) == ($b <= $a)
UPD: Второй ответ
В PHP (актуально для версии 5.5.0beta2) нет строгого сравнения >== или <== с проверкой типов, но есть несколько способов сравнить типы до сравнения больше/меньше:
- Сравнить типы переменных
if ( gettype($a)===gettype($b) ) ...
- Явно привести переменные к нужному типу
if ( (string)$a===(string)$b ) ...
- Использовать манипуляцию с типами
if ( ($a.'')===($b.'') ) ...
Также обратите внимание на следующее:
- Числа с плавающей точкой имеют ограниченную точность;
- Константы NAN и INF являются типом float;
- Сравнение INF с INF является математически неверным;
- Числа в e-нотации являются типом float, даже если они небольшие;
- Целые числа превышающие PHP_INT_MAX автоматически преобразуются в числа с плавающей точкой;
- Числа с плавающей точкой выходящие за границы определенные системой содержат INF;
- Необъявленные переменные возвращают NULL;
- При присвоении целые числа начинающиеся с 0 преобразуются из восьмеричного в десятеричный вид;
- При приведении строки к целому числу оно теряет начальные нули.
Несколько специфических сравнений
Необычно
$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
Примечание переводчика: В ответе выше сказано, что любое сравнение с NAN возвращает false, так что ничего необычного здесь нет.Равно, но не идентично
$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
И больше, и меньше одновременно?
$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
Идентично
$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
Больше или меньше
$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
Примеры с $a > $b > $c в которых $a не больше $c
A<C : float(NAN) > str('a') > str('')
A<C : float(NAN) > str('a') > str('1')
A<C : float(NAN) > str('a') > str('A')
A<C : float(NAN) > str('a') > str('0')
A<C : float(NAN) > str('1') > str('')
A<C : float(NAN) > str('1') > str('0')
A<C : float(NAN) > str('A') > str('')
A<C : float(NAN) > str('A') > str('1')
A<C : float(NAN) > str('A') > str('0')
A<C : float(NAN) > str('0') > str('')
A==C : str('') > float(NAN) > NULL(NULL)
A===C : str('') > float(NAN) > str('')
A<C : str('') > float(NAN) > str('a')
A<C : str('') > float(NAN) > str('1')
A==C : str('') > float(NAN) > bool(false)
A<C : str('') > float(NAN) > str('A')
A<C : str('') > 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<C : str('a') > str('') > float(NAN)
A<C : str('a') > str('1') > float(NAN)
A==C : str('a') > str('1') > int(0)
A==C : str('a') > str('1') > float(0)
A<C : str('a') > str('A') > float(NAN)
A<C : str('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<C : str('1') > float(NAN) > str('a')
A===C : str('1') > float(NAN) > str('1')
A<C : str('1') > float(NAN) > str('A')
A<C : str('1') > str('') > float(NAN)
A<C : str('1') > str('0') > float(NAN)
A<C : str('A') > float(NAN) > str('a')
A===C : str('A') > float(NAN) > str('A')
A<C : str('A') > str('') > float(NAN)
A<C : str('A') > str('1') > float(NAN)
A==C : str('A') > str('1') > int(0)
A==C : str('A') > str('1') > float(0)
A<C : str('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<C : str('0') > float(NAN) > str('a')
A<C : str('0') > float(NAN) > str('1')
A==C : str('0') > float(NAN) > bool(false)
A<C : str('0') > float(NAN) > str('A')
A===C : str('0') > float(NAN) > str('0')
A==C : str('0') > float(-INF) > bool(false)
A<C : str('0') > 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)
Забавное сравнение строк:
'Queen' > 'King' > 'Jack' > 'Ace'
Также посмотрите таблицу сравнения типов в PHP в которой указаны сравнения:
isset()
иis_null()
- if() и empty()
- Различия в
==
и===
Код, который сгенерировал списки на Git Hub
Различия в версиях PHP: http://3v4l.org/MAfDu