— В PHP приведение типов работает нормально, надо только включать здравый смысл.
— А чего там, просто сравниваешь и все…
— Ого, глюк какой-то в PHP, смотри, сравниваю два флоата, они должны быть одинаковые, а он мне говорит, что они не равны.
— А, ну когда число со строкой сравниваешь, перебирать надо, что сработает.
Слышали что-то подобное от коллег или может быть у самих возникали подобные ситуации? Тогда вот вам пятничный пост с примерами про приведение типов, как это работает с оператором ==.
Буду краток, дальше будут только примерчики. А к тебе, дорогой читатель, просьба. Некоторые ответы спрятаны под спойлер. Перед тем, как заглянуть в ответ под спойлером, попробуй ответить сам. За каждый правильный ответ заслуженно прибавляй себе по баллу. В конце поста в опросе не забудь проставить свой результат. Договорились?
Тогда поехали.
Начнем с чисел
Что может быть проще сравнения чисел, числа мы умеем сравнивать с детского сада.
Для порядка проверяем очевидное:
11 == 11 // true, воистину
12 == 11 // false, само собой
12 == 0xC // true, сравниваем с 16-ричным
Как насчет такого сравнения?
12 == "0xC"
А если так попробуем?
12 == 014
Теперь так:
12 == "014"
И еще так:
14 == "014"
Тут вроде все наглядно:
014 == "014"
Внимание:
0 == "0,9"
Сравним такие значения:
"1e2" == "100"
0b11 == 3 // true, 0b - это относится к двоичной системе
А если 0b11 в строке?
"0b11" == 3
Теперь попробуем так:
2.333 == "2.333petrovich" // true, тут Петрович в конце строки и поэтому исключается из сравнения.
2.333 == "ivanovich2.333" // false, а тут Иванович вначале, поэтому сравнение идет со строкой.
0 == "ivanovich2.333" // true, строка равна нулю.
А тут попробуйте сами:
"2.33a" == "2.33b"
233 == "233w1" // true, по аналогии с Петровичем
Почти тоже самое:
233 == "233e1"
"233" == "233w1" // false
233 == "233*1" // true
А если так скалькулировать?
233 == "233*2"
0 == "" // true
Не подглядывайте, сперва сами:
"0" == ""
"1.000000000000000123" == "1.000000000000000456" // false, логично, разные ж числа.
Тоже два float в строках:
"2.000000000000000123" == "2.000000000000000456"
Булевы сравнения
Раз с числами все проще простого и все ответили правильно, то вот еще простые примерчики.
Сперва такой каламбур… вспомнился анекдот про Поручика, но пожалуй воздержусь.
"true" == true // true
"false" == true // true, строка равна true
"false" == false // false, строка равна true
"true" == false // false, строка равна true
Тут все просто и понятно.
true == "0" // false, тут уже идет сравнение не со строкой, а с нулем
true == "00" // true, а тут со строкой...
true == 00 // false
true == "00" + 0 // false, тут к строке с нулем 0 прибавили
А если так прибавить:
true == "01" + 0
true == "0x" + 0 // false
Пробел пред нулем:
true == " 0"
true == 0x0 // false
true == "0x0" // true, так-то, тут уже не 0, а строка
true == "0.0" // true, и тут строка
true == " " // true
true == [] // false, пустой массив - это false
true == [0] // а массив с нулем - true
А если так?
true == [[]]
NULL
Позвольте еще несколько сравнений, теперь с null.
null == false // true,
null == 0 // true,
false == "0" // true,
Попробуйте догадаться по аналогии:
null == "0"
null == [] // true, так как массив пустой
null == [0] // false, не пустой массив все-таки
Массивы
Ну и для особо любознательных — сравнения массивов.
Тут надеюсь, сами догадаетесь, только не подглядывайте:
[] == 0
[0] == 0 // false,
В документации сказано, что "$a == $b — TRUE в случае, если $a и $b содержат одни и те же пары ключ/значение".
Проверим, как именно работает это утверждение. тем более, что в доке ничего ни сказано про то, как сравниваются ключи.
[1 => 1] == [1 => 1] // true
[1 => 1] == ["1" => 1] // true, при этом:
[1 => 1] == ["0x1" => 1] // false, при том, что если отдельно сравнить ключи таким образом:
array_keys([1 => 1]) == array_keys(["0x1" => 1]) // true
Зато:
[1 => 1] == [1 => "0x1"] // true
Загадочка
И на десерт загадочка (загадка не от меня, коллега однажды ее мне дал).
Может ли когда-либо выполниться условие $x == 1 && $x == 2, если может, то когда, если нет, то почему?
А как-же резюме?
А какое тут резюме, табурет с двумя ножками вполне может быть использован по назначению. Более того, он имеет свои положительные стороны, например, помогает держать в тонусе вестибулярный аппарат и ягодицы. Так что читаем доки, набиваем шишки и все будет хорошо.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Комментарии (81)
sectus
05.06.2015 09:27+412 == "0xC"
В PHP7 ликвидировано такое поведение: 3v4l.org/nEgeH, wiki.php.net/rfc/remove_hex_support_in_numeric_strings
Mercury13
05.06.2015 09:45+1«2.000000000000000123» == «2.000000000000000456»
Я этого не понял, должны ведь сравниваться как строки?bolk
05.06.2015 10:28Всё, что похоже на число PHP преобразует тут в число. Довольно известное правило, где-то в документации есть.
neolink
05.06.2015 11:18ну да известно всем кроме интерпретатора:
var_dump(«2.000000000000000123» == «2.000000000000000456»); // falsebolk
05.06.2015 11:31Нулей просто мало. Вот:
php -r 'var_dump("2.00000000000000000123" == "2.00000000000000000456");' bool(true)
neolink
05.06.2015 12:36+4да жесть, в принципе кроме этого всё объяснимо… но зачем так-то?!!!
bolk
05.06.2015 12:40Потому что PHP традиционно мешает строки и числа в кучу, см., например, empty(«0»). Это из-за того, что снаружи ($_GET, $_POST и так далее) вообще всё приходит как строки, а создателям языка хотелось максимально упростить жизнь веб-мастеров в этом месте.
akhmelev
10.06.2015 08:10Ага. Упростили. Вместо объявления типа 1 раз — приведение, то там, то сям. И ===. Ну или ребусы как в сабже, а не код. Удобно, сил нет.
bolk
05.06.2015 12:45Ну и я бы не сказал, что «кроме этого всё объяснимо» :)) я таких примеров кучу знаю, например:
$ php -r '$a=null; var_dump(--$a); var_dump(++$a);' NULL int(1)
То есть уменьшение null даёт null, увеличение — единицу.neolink
05.06.2015 12:53ну об этом в доке написано сразу: php.net/manual/en/language.operators.increment.php это можно объяснить, даже некую логику под это подвести (но я согласен, что это не ожидаемо)
но почему 2 строки сравниваются как числа нетbolk
05.06.2015 13:09Ну так и об операции сравнения написано в доке:
If you compare a number with a string or the comparison involves numerical strings, then each string is converted to a number and the comparison performed numerically.
Более того!These rules also apply to the switch statement.
То есть:
$ php -r 'switch("0x000000") { case "0e0": var_dump("Duh!"); }' string(4) "Duh!"
bolk
05.06.2015 13:14На самом деле в доке есть почти всё (за очень редким исключением), но очень уж забавны некоторые вещи. Например:
$ php -r 'var_dump([] < new stdclass); var_dump([] > INF);' bool(true) // пустой массив меньше пустого класса bool(true) // пустой массив больше бесконечности
felicast
05.06.2015 09:45+1"2.000000000000000123" == "2.000000000000000456"
у вас ошибка, это сравнение строк, а не floatsectus
05.06.2015 09:58+3Это от разрядности зависит, нужно просто ноликов побольше.
3v4l.org/pCMMnvar_dump("2.00000000000000000000000000000000123" == "2.00000000000000000000000000000000456"); // bool(true)
JeStoneDev
05.06.2015 09:45+1"2.33a" == "2.33b" // false - сравниваем строки, все логично "1e2" == "100" // true - зачем он их преобразовывает в числа? Для кого тут кавычки стоят?!
Хоть убейте, не понимаю, какого черты PHP при сравнении двух строк вообще пытается преобразовывать их в числа. Тип же совпадает.
tzlom
05.06.2015 09:49-1Тоже два float в строках:
«2.000000000000000123» == «2.000000000000000456»
false, сравниваются строки, приведение не выполняетсяmnv Автор
05.06.2015 10:43+3Тут я просто нолик пропустил, попробуйте так
php > echo "2.00000000000000000123" == "2.00000000000000000456" ? 'true' : 'false'; true
VokaMut
05.06.2015 10:15+3Не поверил, решил проверить:
<?php $a1 = "1.000000000000000123"; $b1 = "1.000000000000000456"; $a2 = "2.000000000000000123"; $b2 = "2.000000000000000456"; if($a1 == $b1) echo $a1.' == '.$b1; else echo $a1.' != '.$b1; echo '<br>'; if($a2 == $b2) echo $a2.' == '.$b2; else echo $a2.' != '.$b2;
В итоге:
1.000000000000000123 != 1.000000000000000456
2.000000000000000123 != 2.000000000000000456
PHP 5.4.35, Win 7x86, OpenServermnv Автор
05.06.2015 10:47Да, ноль пропустил
php > echo "2.00000000000000000123" == "2.00000000000000000456" ? 'true' : 'false'; true
Lure_of_Chaos
05.06.2015 10:41+13Паскаль нас учил писать =
Си учил на писать ==
Пхп нас учит писать ===Mercury13
05.06.2015 11:01Ещё Бейсик учил…
Причём тот, который с нумерацией строк.Lure_of_Chaos
05.06.2015 11:08+210 GOTO 10
там вообще таких фокусов не было. LET A$ = KEYBOARD$ и все.
bolk
05.06.2015 14:00=== и в JS есть
Lure_of_Chaos
05.06.2015 14:03+1… как и во многих языках с неявным приведением типов. но топик-то про пхп?
janson
05.06.2015 11:39+3Почитал, поужасался. К концу остался вопрос: а что, вот так действительно кто-то пишет?
Я к тому, что если идёт более-менее осмысленное написание кода то вот такого понаписать сложно (чтобы (string) «false» сравнивать с (boolean) true, 12 == «0xC» и тому подобное).
Вот честное слово, с подобными проблемами сравнения последний раз сталкивался может быть лет пять назад.
Явное приведение типов, фильтрация входящих данных и принцип «никогда не доверяй тому, что пришло от пользователя» — избавляют от подобных проблем. Разве нет?Ogra
05.06.2015 11:50+3Ну вот сравнение «100» == «1e2» может в какой-то задаче попасться. Скажем, 2 пользователя с такими никами — и вроде все нормально, правильные данные от пользователя, сравниваем строки со строками… А нет, PHP за нас что-то додумывает!
mnv Автор
05.06.2015 12:38Бывают не очевидные случаи. Приходит вам с клиента $POST, а там строки, а вы вполне можете думать, что там целые числа и сравниваете эти строки с числами через ==. Как вариант.
Fedot
05.06.2015 15:32+1Нормальные люди конечно так никогда не пишут. Это статья больше для развлечения, чем для использования всего этого на практике. Маленькие перлы языка =)
symbix
05.06.2015 11:39+2Мое любимое:
setlocale('LC_ALL', 'ru_RU.UTF-8');
$f = 0.9;
$s = (string)$f;
$f2 = (float)$s;
var_dump($f, $s, $f2);DarkByte
05.06.2015 21:28+1double(0.9)
string(3) «0.9»
double(0.9)
А что должно было быть?mnv Автор
05.06.2015 23:17-1Похоже, вы пренебрегли первой командой
DarkByte
06.06.2015 08:22Да нет, скопировал целиком, правда если включить e_depricated, то можно увидеть:
Deprecated: setlocale(): Passing locale category name as string is deprecated. Use the LC_* -constants instead in test.php on line 2
Замена 'LC_ALL' на LC_ALL убирает предупреждение, вывод не меняется. php 5.4.12, если это важно.symbix
06.06.2015 09:05+1Локаль «ru_RU.UTF-8», видимо, в системе отсутствует. Смысл в том, чтобы установить такую локаль, где десятичный разделитель — запятая.
Если у вас unix-система, посмотрите список локалей с помощью команды locale -a. Если Windows — не знаю :-)DarkByte
09.06.2015 11:06Ага, просто кто-то забыл проверить результат выполнения функции setlocale
if(setlocale(LC_ALL, 'ru_RU.UTF-8') !== false){ echo "Мое любимое:\n"; $f = 0.9; $s = (string)$f; $f2 = (float)$s; var_dump($f, $s, $f2); } else { echo "В системе отсуствует локаль 'ru_RU.UTF-8'\n"; }
tgz
05.06.2015 11:53-3Мне вот не понятно, как после такого люди добровольно выбирают похапе в проекты?
zyaleniyeg
05.06.2015 12:02+6Это же пограничные случаи, а не правило, при этом никто ж не запрещает использовать ===
Думаю пасхалки есть в любом языке, просто в PHP их немного больше :)dezconnect
05.06.2015 12:14+1s/немного/намного/
fixed ;)sferrka
05.06.2015 12:27Соглашусь, если объясните для чего использовать ==, вместо ===?
dezconnect
05.06.2015 13:08-1Ну если вы точно уверены в типах переменных в каком то месте то думаю вполне допустимо ==
tgz
05.06.2015 13:25-3Ну так каждая пасхалочка у тебя отнимает (условно) час времени. Поэтому и не ясно, зачем брать инструмент с пасхалками, когда есть такой же без. А час времени лучше потратить на себя/жену/детей.
Rathil
06.06.2015 01:44+3Скажем так, у меня опыта в пыхе не мало, но проблемы подобного плана возникали лет эдак 5-6 назад. Если ты думаешь не только о себе, а и о других сотрудниках — то подобный ф-ционал ты просто не используешь.
В любом языке можно найти что-то такое, после чего скажешь: да зачем Вы взяли этот язык???
neolink
05.06.2015 12:59+4Половина кода из статьи будет работать в JS. как? как люди выпирают писать на js?
tgz
05.06.2015 15:00-3У js альтернативы как таковой нет. У похапе же навалом. Да и уровень идиотии у js не такой зашкаливающий.
impwx
05.06.2015 16:48+6Да и уровень идиотии у js не такой зашкаливающий.
Если и не такой зашкаливающий, то только потому, что в PHP встроенного функционала на два порядка больше, там было место, где развернуться. В процентном соотношении они идут плечо к плечу.
OlegTar
08.06.2015 10:51У js альтернативы как таковой нет.
TypeScript ;-)tgz
08.06.2015 10:57TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.
Так то я лучше PureScript возьму. :)
А еще лучше не привязываться к какому-то языку в браузере, но на сегодняшний день это фантастика.
dunmaksim
05.06.2015 13:22-3Прочитал заголовок как «PHP — баллада о двух стульях» (ну вы поняли). Закрыл вкладку, т.к. всё уже было сказано несколько лет назад: habrahabr.ru/post/142140
dim_s
05.06.2015 13:28+5Сравнивать float'ы нужно всегда с учетом погрешности в любом языке, а не только в PHP.
yegreS
05.06.2015 14:33+1Решение задачки: $x == 1 && $x == 2
dnabyte
06.06.2015 00:54+2Кому нужна транзитивность в PHP, те используют === тождественное сравнение активных типов (без преобразований). Но статья классная, хороший справочник)
annenkov
06.06.2015 18:51В большинстве случаев большинство, к счастью, эти примеры так и остаются просто любопытными случаями, но есть то на чем наверное рано или поздно спотыкается любой пхпшник, при том иногда довольно больно спотыкается — это empty('0')==true, возможно стоило упомянуть в статье и об этом, оно в принципе о том же.
fshp
08.06.2015 15:58На работе php-шники делают нам REST-API. Когда десериализуем Json происходит сущий ад. Числа в одном и том же запросе то в string, то во float, а если число 1, то приходит int. Писали, ругались. Проблемы они не видят, видимо слово «типы» им вообще не знакомо. Не хочу обижать всех, кто пишет на php, но сам язык предрасположен к написанию говнокода. В новых версиях намечается прогресс, хоть это радует.
sferrka
08.06.2015 18:56+1На работе сишники присылают нам в php xml, валидацию не проходит, даже кавычки расставлены неверно. Не хочу обижать никого, но c++ предрасположен к генерации невалидного xml. Проблемы они не видят, их собственноручная библиотека проходит все тесты. Надеюсь в версии c++ 15 эту проблему решат, а пока хотим перейти на nodejs.
fshp
08.06.2015 19:14Если в c++ вы указали тип поля int, то string вы туда никак не засунете. Чего не скажешь о php. Я вам реальный случай из жизни привожу, а вы передёргиваете.
sferrka
08.06.2015 19:46Вы рассказали реальный случай из жизни неквалифицированных работников (и видимо такого же отдела кадров и руководителя тех. отдела). Не понятно, причем тут PHP абсолютно, именно поэтому я привела пример с кавычками, а не типами.
И если вы не можете выставить элементарные требования к формату, то хоть кто вам будет слать JSON, и хоть с какими типами. А если формат есть, а работа выполняется некачественно, то можно обвинять язык, ОС, железо, ну вы поняли.
kivsiak
08.06.2015 19:19А зачем ругаться? Есть такая штука json-schema. И тут как бы одно из двух либо результат ей соответсвует и тогда никаких претензий друг к другу. Либо нет, тогда значит апи не соответствует спеке.
fshp
08.06.2015 19:21-2Json это лишь пример. Я говорю о том, что люди понятия не имеют, чем отличается «1», 1.0 и 1. Хотя пишут на php не первый год.
juryev
09.06.2015 12:07+1Всё это говорит о том, что программисту надо воздерживаться от неоднозначностей, предпочитать ===, а не ==, и вообще, писать ясный и читаемый код. При желании в любом языке можно найти разные «пасхалки», глюки и неоднозначности, — только зачем их все пихать в код? Опять же, юнит-тесты на что?
sferrka
$x = true; или что-то новое?