image

— В PHP приведение типов работает нормально, надо только включать здравый смысл.
— А чего там, просто сравниваешь и все…
— Ого, глюк какой-то в PHP, смотри, сравниваю два флоата, они должны быть одинаковые, а он мне говорит, что они не равны.
— А, ну когда число со строкой сравниваешь, перебирать надо, что сработает.

Слышали что-то подобное от коллег или может быть у самих возникали подобные ситуации? Тогда вот вам пятничный пост с примерами про приведение типов, как это работает с оператором ==.

Буду краток, дальше будут только примерчики. А к тебе, дорогой читатель, просьба. Некоторые ответы спрятаны под спойлер. Перед тем, как заглянуть в ответ под спойлером, попробуй ответить сам. За каждый правильный ответ заслуженно прибавляй себе по баллу. В конце поста в опросе не забудь проставить свой результат. Договорились?
Тогда поехали.
Начнем с чисел

Что может быть проще сравнения чисел, числа мы умеем сравнивать с детского сада.

Для порядка проверяем очевидное:

11 == 11 // true, воистину
12 == 11 // false, само собой
12 == 0xC // true, сравниваем с 16-ричным

Как насчет такого сравнения?

12 == "0xC"

Попробуйте ответить
true. Да, тут значение в строке преобразовалось в целое с учетом системы счисления, здорово, правда?

А если так попробуем?

12 == 014

Чур не подглядываем, помните?
true. А как иначе? 014 — это же 12 в 8-ричной системе счисления.

Теперь так:

12 == "014"

Результат...
false. Те, кто хотел и тут true, умерьте свои требования, «0xC» преобразовалось в 12 и хватит.

И еще так:

14 == "014"

Равно...
true, вполне ожидаемо с учетом предыдущего примера.

Тут вроде все наглядно:

014 == "014"

Правильный ответ...
false, хотя чисто визуально это трудно заметить сходу, особенно, если читаешь чужой код.

Внимание:

0 == "0,9"

Ответ...
true, чего? А, ну да, так false: 0 == «0.9». Запятая посчиталась строковым символом и она и все после нее отбросилось.

Сравним такие значения:

"1e2" == "100"

Получим...
true, так как 1 умножить на 10 в квадрате равно 100.

0b11 == 3 // true, 0b - это относится к двоичной системе

А если 0b11 в строке?

"0b11" == 3

Сравниваем...
false, для 16-ричной системы это работает, а для двоичной — извините.

Теперь попробуем так:

2.333 == "2.333petrovich" // true, тут Петрович в конце строки и поэтому исключается из сравнения.
2.333 == "ivanovich2.333" // false, а тут Иванович вначале, поэтому сравнение идет со строкой.
0 == "ivanovich2.333" // true, строка равна нулю.


А тут попробуйте сами:
"2.33a" == "2.33b"

И получаем...
false, тут никакого преобразования нет, сравниваются строки.

233 == "233w1" // true, по аналогии с Петровичем

Почти тоже самое:

233 == "233e1"

Ответ...
false, да, кто заметил, тут 233 * 10.

"233" == "233w1" // false
233 == "233*1" // true

А если так скалькулировать?

233 == "233*2"

Вот...
Правильно, снова true, по аналогии с Петровичем.

0 == "" // true

Не подглядывайте, сперва сами:

"0" == ""

Пытаемся включить здравый смысл...
false, смиритесь.

"1.000000000000000123" == "1.000000000000000456" // false, логично, разные ж числа.

Тоже два float в строках:

"2.000000000000000123" == "2.000000000000000456"

Догадались?
true, не удивляйтесь, с виду числа хоть и разные, но 2 == 2, тут все до 2 округлилось.

Булевы сравнения

Раз с числами все проще простого и все ответили правильно, то вот еще простые примерчики.

Сперва такой каламбур… вспомнился анекдот про Поручика, но пожалуй воздержусь.

"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, а тут уже к 1 прибавление.

true == "0x" + 0 // false

Пробел пред нулем:

true == " 0"

Сверяемся...
true, хотя кто-то мог ожидать, что пробел отбросится и будет сравнение с нулем.

true == 0x0 // false
true == "0x0" // true, так-то, тут уже не 0, а строка
true == "0.0" // true, и тут строка


true == " " // true
true == [] // false, пустой массив - это false
true == [0] // а массив с нулем - true

А если так?

true == [[]]

Проверяем...
true, тут массив что-то себе содержит, пустой массив.

NULL

Позвольте еще несколько сравнений, теперь с null.

null == false // true,
null == 0 // true,
false == "0" // true,

Попробуйте догадаться по аналогии:

null == "0"

Получаем...
false, видимо, надо просто запомнить.

null == [] // true, так как массив пустой
null == [0] // false, не пустой массив все-таки


Массивы

Ну и для особо любознательных — сравнения массивов.

Тут надеюсь, сами догадаетесь, только не подглядывайте:

[] == 0

Сверяем...
Точно, false, хотя null == 0 и [] == null.

[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, если может, то когда, если нет, то почему?

А как-же резюме?

А какое тут резюме, табурет с двумя ножками вполне может быть использован по назначению. Более того, он имеет свои положительные стороны, например, помогает держать в тонусе вестибулярный аппарат и ягодицы. Так что читаем доки, набиваем шишки и все будет хорошо.
Сколько баллов получилось?

Проголосовало 494 человека. Воздержался 371 человек.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Комментарии (81)


  1. sferrka
    05.06.2015 09:16
    +7

    $x = true; или что-то новое?


  1. sectus
    05.06.2015 09:27
    +4

    12 == "0xC"
    

    В PHP7 ликвидировано такое поведение: 3v4l.org/nEgeH, wiki.php.net/rfc/remove_hex_support_in_numeric_strings


  1. Mercury13
    05.06.2015 09:45
    +1

    «2.000000000000000123» == «2.000000000000000456»

    Я этого не понял, должны ведь сравниваться как строки?


    1. bolk
      05.06.2015 10:28

      Всё, что похоже на число PHP преобразует тут в число. Довольно известное правило, где-то в документации есть.


      1. neolink
        05.06.2015 11:18

        ну да известно всем кроме интерпретатора:
        var_dump(«2.000000000000000123» == «2.000000000000000456»); // false


        1. bolk
          05.06.2015 11:31

          Нулей просто мало. Вот:

           php -r 'var_dump("2.00000000000000000123" == "2.00000000000000000456");'
          bool(true)
          


          1. neolink
            05.06.2015 12:36
            +4

            да жесть, в принципе кроме этого всё объяснимо… но зачем так-то?!!!


            1. bolk
              05.06.2015 12:40

              Потому что PHP традиционно мешает строки и числа в кучу, см., например, empty(«0»). Это из-за того, что снаружи ($_GET, $_POST и так далее) вообще всё приходит как строки, а создателям языка хотелось максимально упростить жизнь веб-мастеров в этом месте.


              1. akhmelev
                10.06.2015 08:10

                Ага. Упростили. Вместо объявления типа 1 раз — приведение, то там, то сям. И ===. Ну или ребусы как в сабже, а не код. Удобно, сил нет.


            1. bolk
              05.06.2015 12:45

              Ну и я бы не сказал, что «кроме этого всё объяснимо» :)) я таких примеров кучу знаю, например:

              $ php -r '$a=null; var_dump(--$a); var_dump(++$a);'
              NULL
              int(1)
              

              То есть уменьшение null даёт null, увеличение — единицу.


              1. neolink
                05.06.2015 12:53

                ну об этом в доке написано сразу: php.net/manual/en/language.operators.increment.php это можно объяснить, даже некую логику под это подвести (но я согласен, что это не ожидаемо)
                но почему 2 строки сравниваются как числа нет


                1. 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!"


                  1. neolink
                    05.06.2015 13:14
                    +1

                    про switch это боль да, там === не напишешь


                1. bolk
                  05.06.2015 13:14

                  На самом деле в доке есть почти всё (за очень редким исключением), но очень уж забавны некоторые вещи. Например:

                  $ php -r 'var_dump([] < new stdclass); var_dump([] > INF);'
                  bool(true) // пустой массив меньше пустого класса
                  bool(true) // пустой массив больше бесконечности
                  


                  1. vlivyur
                    05.06.2015 13:31
                    +1

                    А пустой класс больше бесконечности?


                    1. bolk
                      05.06.2015 13:44
                      +1

                      да.


  1. felicast
    05.06.2015 09:45
    +1

    "2.000000000000000123" == "2.000000000000000456"
    

    у вас ошибка, это сравнение строк, а не float


    1. sectus
      05.06.2015 09:58
      +3

      Это от разрядности зависит, нужно просто ноликов побольше.

      var_dump("2.00000000000000000000000000000000123" == "2.00000000000000000000000000000000456"); // bool(true)
      
      3v4l.org/pCMMn


  1. JeStoneDev
    05.06.2015 09:45
    +1

    "2.33a" == "2.33b" // false - сравниваем строки, все логично
    "1e2" == "100" // true - зачем он их преобразовывает в числа? Для кого тут кавычки стоят?!
    

    Хоть убейте, не понимаю, какого черты PHP при сравнении двух строк вообще пытается преобразовывать их в числа. Тип же совпадает.


    1. felicast
      05.06.2015 09:53
      -1

      Это у автора ошибка. PHP так не делает


      1. felicast
        05.06.2015 09:55
        +2

        Прошу прощения. Проверил. Действительно делает.
        Хорошо хоть рекомендуется везде использовать ===


  1. tzlom
    05.06.2015 09:49
    -1

    Тоже два float в строках:
    «2.000000000000000123» == «2.000000000000000456»

    false, сравниваются строки, приведение не выполняется


    1. mnv Автор
      05.06.2015 10:43
      +3

      Тут я просто нолик пропустил, попробуйте так

      php >  echo "2.00000000000000000123" == "2.00000000000000000456" ? 'true' : 'false';
      true
      


  1. 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, OpenServer


    1. mnv Автор
      05.06.2015 10:47

      Да, ноль пропустил

      php >  echo "2.00000000000000000123" == "2.00000000000000000456" ? 'true' : 'false';
      true
      


  1. Lure_of_Chaos
    05.06.2015 10:41
    +13

    Паскаль нас учил писать =
    Си учил на писать ==
    Пхп нас учит писать ===


    1. Mercury13
      05.06.2015 11:01

      Ещё Бейсик учил…
      Причём тот, который с нумерацией строк.


      1. Lure_of_Chaos
        05.06.2015 11:08
        +2

        10 GOTO 10

        там вообще таких фокусов не было. LET A$ = KEYBOARD$ и все.


    1. bolk
      05.06.2015 14:00

      === и в JS есть


      1. Lure_of_Chaos
        05.06.2015 14:03
        +1

        … как и во многих языках с неявным приведением типов. но топик-то про пхп?


  1. dcc0
    05.06.2015 10:49

    12 == «0xC»
    Конечно, true.
    Спасибо. Хорошая шпаргалка.


    1. kykint
      05.06.2015 15:10

      Но не в php7


  1. janson
    05.06.2015 11:39
    +3

    Почитал, поужасался. К концу остался вопрос: а что, вот так действительно кто-то пишет?
    Я к тому, что если идёт более-менее осмысленное написание кода то вот такого понаписать сложно (чтобы (string) «false» сравнивать с (boolean) true, 12 == «0xC» и тому подобное).
    Вот честное слово, с подобными проблемами сравнения последний раз сталкивался может быть лет пять назад.
    Явное приведение типов, фильтрация входящих данных и принцип «никогда не доверяй тому, что пришло от пользователя» — избавляют от подобных проблем. Разве нет?


    1. Ogra
      05.06.2015 11:50
      +3

      Ну вот сравнение «100» == «1e2» может в какой-то задаче попасться. Скажем, 2 пользователя с такими никами — и вроде все нормально, правильные данные от пользователя, сравниваем строки со строками… А нет, PHP за нас что-то додумывает!


    1. mnv Автор
      05.06.2015 12:38

      Бывают не очевидные случаи. Приходит вам с клиента $POST, а там строки, а вы вполне можете думать, что там целые числа и сравниваете эти строки с числами через ==. Как вариант.


    1. Fedot
      05.06.2015 15:32
      +1

      Нормальные люди конечно так никогда не пишут. Это статья больше для развлечения, чем для использования всего этого на практике. Маленькие перлы языка =)


  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);


    1. DarkByte
      05.06.2015 21:28
      +1

      double(0.9)
      string(3) «0.9»
      double(0.9)

      А что должно было быть?


      1. mnv Автор
        05.06.2015 23:17
        -1

        Похоже, вы пренебрегли первой командой


        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, если это важно.


          1. symbix
            06.06.2015 09:05
            +1

            Локаль «ru_RU.UTF-8», видимо, в системе отсутствует. Смысл в том, чтобы установить такую локаль, где десятичный разделитель — запятая.

            Если у вас unix-система, посмотрите список локалей с помощью команды locale -a. Если Windows — не знаю :-)


            1. 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";
              }


      1. cybd
        09.06.2015 10:36

        > А что должно было быть?

        double(0.9)
        string(3) «0,9»
        double(0)


  1. tgz
    05.06.2015 11:53
    -3

    Мне вот не понятно, как после такого люди добровольно выбирают похапе в проекты?


    1. zyaleniyeg
      05.06.2015 12:02
      +6

      Это же пограничные случаи, а не правило, при этом никто ж не запрещает использовать ===
      Думаю пасхалки есть в любом языке, просто в PHP их немного больше :)


      1. dezconnect
        05.06.2015 12:14
        +1

        s/немного/намного/

        fixed ;)


        1. sferrka
          05.06.2015 12:27

          Соглашусь, если объясните для чего использовать ==, вместо ===?


          1. dezconnect
            05.06.2015 13:08
            -1

            Ну если вы точно уверены в типах переменных в каком то месте то думаю вполне допустимо ==


      1. tgz
        05.06.2015 13:25
        -3

        Ну так каждая пасхалочка у тебя отнимает (условно) час времени. Поэтому и не ясно, зачем брать инструмент с пасхалками, когда есть такой же без. А час времени лучше потратить на себя/жену/детей.


        1. Rathil
          06.06.2015 01:44
          +3

          Скажем так, у меня опыта в пыхе не мало, но проблемы подобного плана возникали лет эдак 5-6 назад. Если ты думаешь не только о себе, а и о других сотрудниках — то подобный ф-ционал ты просто не используешь.
          В любом языке можно найти что-то такое, после чего скажешь: да зачем Вы взяли этот язык???


          1. tgz
            06.06.2015 13:45

            Ага, из всех зол выбираем не меньшее, а наиболее кривое. Такая то аргументация…


            1. Rathil
              08.06.2015 10:09

              Каждый человек сам отвечает за свой выбор, поступки и действия.


    1. neolink
      05.06.2015 12:59
      +4

      Половина кода из статьи будет работать в JS. как? как люди выпирают писать на js?


      1. kivsiak
        05.06.2015 13:28
        +4

        Можно подумать есть выбор.


        1. sferrka
          05.06.2015 15:12
          +5

          Выбора очень много, на каждый популярный язык по компилятору в JS.
          Вот так, например, в typescript выглядит сравнение разных типов:


        1. OlegTar
          05.06.2015 19:03

          есть === и для PHP, и для js


      1. tgz
        05.06.2015 15:00
        -3

        У js альтернативы как таковой нет. У похапе же навалом. Да и уровень идиотии у js не такой зашкаливающий.


        1. impwx
          05.06.2015 16:48
          +6

          Да и уровень идиотии у js не такой зашкаливающий.

          Если и не такой зашкаливающий, то только потому, что в PHP встроенного функционала на два порядка больше, там было место, где развернуться. В процентном соотношении они идут плечо к плечу.


        1. matiouchkine
          05.06.2015 18:52
          +2

          www.wtfjs.com


        1. OlegTar
          08.06.2015 10:51

          У js альтернативы как таковой нет.

          TypeScript ;-)


          1. tgz
            08.06.2015 10:57

            TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.

            Так то я лучше PureScript возьму. :)
            А еще лучше не привязываться к какому-то языку в браузере, но на сегодняшний день это фантастика.


  1. Keyten
    05.06.2015 13:04
    +9

    Напомнило :)

    (Только это JavaScript)


    1. JeStoneDev
      05.06.2015 15:29
      +1

      var x * 3
      

      fix*
      var x = 3
      


  1. dunmaksim
    05.06.2015 13:22
    -3

    Прочитал заголовок как «PHP — баллада о двух стульях» (ну вы поняли). Закрыл вкладку, т.к. всё уже было сказано несколько лет назад: habrahabr.ru/post/142140


    1. mnv Автор
      05.06.2015 14:15

      Да, та статья шикарная


  1. dim_s
    05.06.2015 13:28
    +5

    Сравнивать float'ы нужно всегда с учетом погрешности в любом языке, а не только в PHP.


  1. yegreS
    05.06.2015 14:33
    +1

    Решение задачки: $x == 1 && $x == 2

    x=...
    при $x = true;


    1. gaelpa
      05.06.2015 19:45
      +1

      Это первый же комментарий к топику.


  1. OlegTar
    05.06.2015 16:57

    ИМХО, резюме должно быть такое: юзайте ===


  1. dnabyte
    06.06.2015 00:54
    +2

    Кому нужна транзитивность в PHP, те используют === тождественное сравнение активных типов (без преобразований). Но статья классная, хороший справочник)


  1. annenkov
    06.06.2015 18:51

    В большинстве случаев большинство, к счастью, эти примеры так и остаются просто любопытными случаями, но есть то на чем наверное рано или поздно спотыкается любой пхпшник, при том иногда довольно больно спотыкается — это empty('0')==true, возможно стоило упомянуть в статье и об этом, оно в принципе о том же.


  1. fshp
    08.06.2015 15:58

    На работе php-шники делают нам REST-API. Когда десериализуем Json происходит сущий ад. Числа в одном и том же запросе то в string, то во float, а если число 1, то приходит int. Писали, ругались. Проблемы они не видят, видимо слово «типы» им вообще не знакомо. Не хочу обижать всех, кто пишет на php, но сам язык предрасположен к написанию говнокода. В новых версиях намечается прогресс, хоть это радует.


    1. sferrka
      08.06.2015 18:56
      +1

      На работе сишники присылают нам в php xml, валидацию не проходит, даже кавычки расставлены неверно. Не хочу обижать никого, но c++ предрасположен к генерации невалидного xml. Проблемы они не видят, их собственноручная библиотека проходит все тесты. Надеюсь в версии c++ 15 эту проблему решат, а пока хотим перейти на nodejs.


      1. fshp
        08.06.2015 19:14

        Если в c++ вы указали тип поля int, то string вы туда никак не засунете. Чего не скажешь о php. Я вам реальный случай из жизни привожу, а вы передёргиваете.


        1. sferrka
          08.06.2015 19:46

          Вы рассказали реальный случай из жизни неквалифицированных работников (и видимо такого же отдела кадров и руководителя тех. отдела). Не понятно, причем тут PHP абсолютно, именно поэтому я привела пример с кавычками, а не типами.
          И если вы не можете выставить элементарные требования к формату, то хоть кто вам будет слать JSON, и хоть с какими типами. А если формат есть, а работа выполняется некачественно, то можно обвинять язык, ОС, железо, ну вы поняли.


          1. fshp
            08.06.2015 19:57

            В языке со статической типизацией даже совсем зелёный новичок не совершил бы таких ошибок.


            1. fshp
              08.06.2015 20:02

              Подразумевал сильную типизацию, не статическую.


            1. sferrka
              08.06.2015 20:03

              Совершил бы другие, и? Вы обвиняете язык в его возможностях?


    1. kivsiak
      08.06.2015 19:19

      А зачем ругаться? Есть такая штука json-schema. И тут как бы одно из двух либо результат ей соответсвует и тогда никаких претензий друг к другу. Либо нет, тогда значит апи не соответствует спеке.


      1. fshp
        08.06.2015 19:21
        -2

        Json это лишь пример. Я говорю о том, что люди понятия не имеют, чем отличается «1», 1.0 и 1. Хотя пишут на php не первый год.


  1. juryev
    09.06.2015 12:07
    +1

    Всё это говорит о том, что программисту надо воздерживаться от неоднозначностей, предпочитать ===, а не ==, и вообще, писать ясный и читаемый код. При желании в любом языке можно найти разные «пасхалки», глюки и неоднозначности, — только зачем их все пихать в код? Опять же, юнит-тесты на что?