Тип-жонглирование и (строгое) большее/меньшее, чем сравнение в PHP
PHP известен своим жужжанием типа. Я должен признать, что это меня озадачивает, и мне сложно найти основные логические/фундаментальные вещи в сравнении.
Например: Если $a > $b
истинно, а $b > $c
- true, значит, оно означает, что $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)
Для большинства комбинаций сравнения типов эти большие/меньшие операторы сравнения не документируются, поэтому чтение руководства в этом случае не очень полезно.
Ответы
Ответ 1
Операторы сравнения PHP отклоняются от компьютерно-научных определений несколькими способами:
Чтобы составить отношение эквивалентности ==
должно быть рефлексивным, симметричным и транзитивным:
-
Оператор PHP ==
не рефлексивный, т.е. $a == $a
не всегда верно:
var_dump(NAN == NAN); // bool(false)
Примечание. Тот факт, что любое сравнение с NAN
всегда false
не является специфичным для PHP. Это предусмотрено стандартом IEEE 754 для арифметики с плавающей точкой (подробнее).
-
Оператор PHP ==
симметричный, т.е. $a == $b
и $b == $a
всегда одни и те же.
-
Оператор PHP ==
не транзитивен, т.е. от $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
не всегда верен (пример такой же, как для ==
).
-
Оператор PHP <=
не является антисимметричным, т.е. из $a <= $b
и $b <= $a
не следует $a == $b
:
var_dump(NAN <= "foo"); // bool(true)
var_dump("foo" <= NAN); // bool(true)
var_dump(NAN == "foo"); // bool(false)
-
Оператор PHP <=
не транзитивен, т.е. из $a <= $b
и $b <= $c
не следует $a <= $c
(пример такой же, как для ==
).
-
Дополнительно: PHP <=
оператор не полный, то есть оба $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
оценивалось до true
.
-
Оператор PHP <
не асимметричен, т.е. из $a < $b
не следует !($b < $a)
(пример такой же, как для <=
не является антисимметричным).
-
Оператор PHP <
не транзитивен, т.е. из $a < $b
и $b < $c
не следует $a < $c
:
var_dump(-INF < 0); // bool(true)
var_dump(0 < TRUE); // bool(true)
var_dump(-INF < TRUE); // bool(false)
-
Дополнительно: PHP <
оператор не трихотомический, т.е. все $a < $b
, $b < $a
и $a == $b
могут быть ложными (пример такой же, как для <=
not будучи полным).
-
Дополнительно: оператор PHP <
может быть круговой, то есть возможно, что $a < $b
, $b < $c
и $c < $a
:
var_dump(INF < []); // bool(true)
var_dump([] < new stdClass); // bool(true)
var_dump(new stdClass < INF); // bool(true)
Примечание. В приведенном выше примере выдается сообщение об ошибке "Объект класса stdClass не может быть преобразован в двойное".
Вы можете найти несколько хороших графиков для операторов сравнения PHP на PHP Sadness 52 - Операторы сравнения.
Как последнее примечание, я хочу отметить, что есть два равенства, которые гарантирует PHP (в отличие от почти всего остального). Эти два всегда выполняются просто потому, что компилятор сводит один к другому:
($a > $b) == ($b < $a)
($a >= $b) == ($b <= $a)
Ответ 2
Существуют строгие идентичные операторы сравнения нет (>==
или <==
) в PHP (по крайней мере, по PHP 5.6.14), но есть несколько способов обеспечить соблюдение строгая проверка типа перед проверкой "Большой/Нижний":
- Проверьте оба типа переменных с помощью
if (gettype($a) === gettype($b))
- Принудительно введите тип, например.
if ((string)$a === (string)$b)
- Принудительный тип жонглирования, например.
if (($a . '') === ($b . ''))
Обратите внимание, что:
- Точность плавающей точки ограничена
-
INF
и NAN
имеют тип float
под ieee754
- Некоторая бесконечность равна некоторой другой бесконечности (начиная с PHP 5.4)
- Научная нотация
e
всегда имеет тип float
и никогда integer
, даже если число мало
- Целые числа, переходящие
PHP_INT_MAX
, автоматически преобразуются в float
- Поплавки по границам системы получают значение
INF
- Undefined переменные имеют тип и значение
NULL
- Целые числа, предшествующие
0
, преобразуются из восьмеричного в десятичное (по соглашению)
- Преобразование строк, содержащих целое число с ведущим
0
в integer, разделяет ведущий 0
Список некоторых экзотических сравнений:
Very strange:
$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
Равно, но не идентично:
$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) > str('a')
A<C : str('') > float(NAN) > str('1')
A<C : str('') > float(NAN) > str('A')
A<C : str('') > float(NAN) > str('0')
A<C : str('a') > str('') > float(NAN)
A<C : str('a') > str('1') > float(NAN)
A<C : str('a') > str('A') > float(NAN)
A<C : str('a') > str('0') > float(NAN)
A<C : str('0') > str('') > 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('a') > str('1') > int(0)
A==C : str('a') > str('1') > float(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('') > float(NAN) > bool(false)
A==C : str('') > float(NAN) > NULL(NULL)
A==C : str('A') > str('1') > int(0)
A==C : str('A') > str('1') > float(0)
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) > bool(false)
A==C : str('0') > float(-INF) > bool(false)
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)
A===C : str('0') > float(NAN) > str('0')
A===C : str('') > float(NAN) > str('')
A===C : str('a') > float(NAN) > str('a')
A===C : str('1') > float(NAN) > str('1')
A===C : str('A') > float(NAN) > str('A')
Смешное сравнение строк: 'Queen' >
'King' >
'Jack' >
'Ace'
Также проверьте таблицы сравнения типов PHP, охватывающие пары:
-
isset()
и is_null()
-
if()
и empty()
- boolean
==
vs. ===
Проверьте различия между версиями PHP в прямом эфире. http://3v4l.org/MAfDu.
Ответ 3
После исправления второй части вашего вопроса я оставляю ответ на эту часть другим. Я просто хочу дать самый неожиданный ответ на первую часть вашего вопроса, т.е. Является ли пример операторов <
и >
непереходными. Вот оно.
Это все true
:
"10" < "1a"
"1a" < "2"
"10" > "2"
Если <
были транзитивными ($a < $b
& и; $b < $c
→ $a < $c
), последняя строка была бы
"10" < "2"
но PHP пытается быть добрым (?!) и интерпретировать строки как числа, когда это возможно.
Оказывается, что из-за вышеперечисленной непереходности sort()
может сортировать те же элементы в другой порядок в зависимости от их порядка ввода, even когда нет двух элементов ==
(и ни один элемент не является NAN). Я указал на это в комментарии для сортировки(), суть которого заключается в следующем:
sort(array("10", "1a", "2" )) => array("10", "1a", "2" )
sort(array("10", "2", "1a")) => array("1a", "2", "10")
sort(array("1a", "10", "2" )) => array("2", "10", "1a")
sort(array("1a", "2", "10")) => array("1a", "2", "10")
sort(array("2", "10", "1a")) => array("2", "10", "1a")
sort(array("2", "1a", "10")) => array("10", "1a", "2" )