Пользователь
0,0
рейтинг
5 июня 2015 в 08:58

Разработка → Приведение типов в PHP == табурет о двух ножках?

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

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

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

Проголосовал 531 человек. Воздержалось 399 человек.

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

Николай @mnv
карма
28,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

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

  • +7
    $x = true; или что-то новое?
  • +4
    12 == "0xC"
    

    В PHP7 ликвидировано такое поведение: 3v4l.org/nEgeH, wiki.php.net/rfc/remove_hex_support_in_numeric_strings
  • +1
    «2.000000000000000123» == «2.000000000000000456»

    Я этого не понял, должны ведь сравниваться как строки?
    • 0
      Всё, что похоже на число PHP преобразует тут в число. Довольно известное правило, где-то в документации есть.
      • 0
        ну да известно всем кроме интерпретатора:
        var_dump(«2.000000000000000123» == «2.000000000000000456»); // false
        • 0
          Нулей просто мало. Вот:
           php -r 'var_dump("2.00000000000000000123" == "2.00000000000000000456");'
          bool(true)
          
          • +4
            да жесть, в принципе кроме этого всё объяснимо… но зачем так-то?!!!
            • 0
              Потому что PHP традиционно мешает строки и числа в кучу, см., например, empty(«0»). Это из-за того, что снаружи ($_GET, $_POST и так далее) вообще всё приходит как строки, а создателям языка хотелось максимально упростить жизнь веб-мастеров в этом месте.
              • 0
                Ага. Упростили. Вместо объявления типа 1 раз — приведение, то там, то сям. И ===. Ну или ребусы как в сабже, а не код. Удобно, сил нет.
            • 0
              Ну и я бы не сказал, что «кроме этого всё объяснимо» :)) я таких примеров кучу знаю, например:
              $ php -r '$a=null; var_dump(--$a); var_dump(++$a);'
              NULL
              int(1)
              

              То есть уменьшение null даёт null, увеличение — единицу.
              • 0
                ну об этом в доке написано сразу: php.net/manual/en/language.operators.increment.php это можно объяснить, даже некую логику под это подвести (но я согласен, что это не ожидаемо)
                но почему 2 строки сравниваются как числа нет
                • 0
                  Ну так и об операции сравнения написано в доке:
                  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
                    про switch это боль да, там === не напишешь
                • 0
                  На самом деле в доке есть почти всё (за очень редким исключением), но очень уж забавны некоторые вещи. Например:

                  $ php -r 'var_dump([] < new stdclass); var_dump([] > INF);'
                  bool(true) // пустой массив меньше пустого класса
                  bool(true) // пустой массив больше бесконечности
                  
                  • +1
                    А пустой класс больше бесконечности?
                    • +1
                      да.
  • +1
    "2.000000000000000123" == "2.000000000000000456"
    

    у вас ошибка, это сравнение строк, а не float
    • +3
      Это от разрядности зависит, нужно просто ноликов побольше.
      var_dump("2.00000000000000000000000000000000123" == "2.00000000000000000000000000000000456"); // bool(true)
      
      3v4l.org/pCMMn
  • +1
    "2.33a" == "2.33b" // false - сравниваем строки, все логично
    "1e2" == "100" // true - зачем он их преобразовывает в числа? Для кого тут кавычки стоят?!
    

    Хоть убейте, не понимаю, какого черты PHP при сравнении двух строк вообще пытается преобразовывать их в числа. Тип же совпадает.
    • –1
      Это у автора ошибка. PHP так не делает
      • +2
        Прошу прощения. Проверил. Действительно делает.
        Хорошо хоть рекомендуется везде использовать ===
  • –1
    Тоже два float в строках:
    «2.000000000000000123» == «2.000000000000000456»

    false, сравниваются строки, приведение не выполняется
    • +3
      Тут я просто нолик пропустил, попробуйте так
      php >  echo "2.00000000000000000123" == "2.00000000000000000456" ? 'true' : 'false';
      true
      
  • +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
    • 0
      Да, ноль пропустил
      php >  echo "2.00000000000000000123" == "2.00000000000000000456" ? 'true' : 'false';
      true
      
  • +13
    Паскаль нас учил писать =
    Си учил на писать ==
    Пхп нас учит писать ===
    • 0
      Ещё Бейсик учил…
      Причём тот, который с нумерацией строк.
      • +2
        10 GOTO 10

        там вообще таких фокусов не было. LET A$ = KEYBOARD$ и все.
    • 0
      === и в JS есть
      • +1
        … как и во многих языках с неявным приведением типов. но топик-то про пхп?
  • 0
    12 == «0xC»
    Конечно, true.
    Спасибо. Хорошая шпаргалка.
    • 0
      Но не в php7
  • +3
    Почитал, поужасался. К концу остался вопрос: а что, вот так действительно кто-то пишет?
    Я к тому, что если идёт более-менее осмысленное написание кода то вот такого понаписать сложно (чтобы (string) «false» сравнивать с (boolean) true, 12 == «0xC» и тому подобное).
    Вот честное слово, с подобными проблемами сравнения последний раз сталкивался может быть лет пять назад.
    Явное приведение типов, фильтрация входящих данных и принцип «никогда не доверяй тому, что пришло от пользователя» — избавляют от подобных проблем. Разве нет?
    • +3
      Ну вот сравнение «100» == «1e2» может в какой-то задаче попасться. Скажем, 2 пользователя с такими никами — и вроде все нормально, правильные данные от пользователя, сравниваем строки со строками… А нет, PHP за нас что-то додумывает!
    • 0
      Бывают не очевидные случаи. Приходит вам с клиента $POST, а там строки, а вы вполне можете думать, что там целые числа и сравниваете эти строки с числами через ==. Как вариант.
    • +1
      Нормальные люди конечно так никогда не пишут. Это статья больше для развлечения, чем для использования всего этого на практике. Маленькие перлы языка =)
  • +2
    Мое любимое:

    setlocale('LC_ALL', 'ru_RU.UTF-8');
    $f = 0.9;
    $s = (string)$f;
    $f2 = (float)$s;
    var_dump($f, $s, $f2);
    • +1
      double(0.9)
      string(3) «0.9»
      double(0.9)

      А что должно было быть?
      • –1
        Похоже, вы пренебрегли первой командой
        • 0
          Да нет, скопировал целиком, правда если включить 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
            Локаль «ru_RU.UTF-8», видимо, в системе отсутствует. Смысл в том, чтобы установить такую локаль, где десятичный разделитель — запятая.

            Если у вас unix-система, посмотрите список локалей с помощью команды locale -a. Если Windows — не знаю :-)
            • 0
              Ага, просто кто-то забыл проверить результат выполнения функции 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";
              }
      • 0
        > А что должно было быть?

        double(0.9)
        string(3) «0,9»
        double(0)
  • –3
    Мне вот не понятно, как после такого люди добровольно выбирают похапе в проекты?
    • +6
      Это же пограничные случаи, а не правило, при этом никто ж не запрещает использовать ===
      Думаю пасхалки есть в любом языке, просто в PHP их немного больше :)
      • +1
        s/немного/намного/

        fixed ;)
        • 0
          Соглашусь, если объясните для чего использовать ==, вместо ===?
          • –1
            Ну если вы точно уверены в типах переменных в каком то месте то думаю вполне допустимо ==
      • –3
        Ну так каждая пасхалочка у тебя отнимает (условно) час времени. Поэтому и не ясно, зачем брать инструмент с пасхалками, когда есть такой же без. А час времени лучше потратить на себя/жену/детей.
        • +3
          Скажем так, у меня опыта в пыхе не мало, но проблемы подобного плана возникали лет эдак 5-6 назад. Если ты думаешь не только о себе, а и о других сотрудниках — то подобный ф-ционал ты просто не используешь.
          В любом языке можно найти что-то такое, после чего скажешь: да зачем Вы взяли этот язык???
          • 0
            Ага, из всех зол выбираем не меньшее, а наиболее кривое. Такая то аргументация…
            • 0
              Каждый человек сам отвечает за свой выбор, поступки и действия.
    • +4
      Половина кода из статьи будет работать в JS. как? как люди выпирают писать на js?
      • +4
        Можно подумать есть выбор.
        • +5
          Выбора очень много, на каждый популярный язык по компилятору в JS.
          Вот так, например, в typescript выглядит сравнение разных типов:
        • 0
          есть === и для PHP, и для js
      • –3
        У js альтернативы как таковой нет. У похапе же навалом. Да и уровень идиотии у js не такой зашкаливающий.
        • +6
          Да и уровень идиотии у js не такой зашкаливающий.

          Если и не такой зашкаливающий, то только потому, что в PHP встроенного функционала на два порядка больше, там было место, где развернуться. В процентном соотношении они идут плечо к плечу.
        • +2
          www.wtfjs.com
        • 0
          У js альтернативы как таковой нет.

          TypeScript ;-)
          • 0
            TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.

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

    (Только это JavaScript)
    • +1
      var x * 3
      

      fix*
      var x = 3
      
  • –3
    Прочитал заголовок как «PHP — баллада о двух стульях» (ну вы поняли). Закрыл вкладку, т.к. всё уже было сказано несколько лет назад: habrahabr.ru/post/142140
    • 0
      Да, та статья шикарная
  • +5
    Сравнивать float'ы нужно всегда с учетом погрешности в любом языке, а не только в PHP.
  • +1
    Решение задачки: $x == 1 && $x == 2

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

    • +1
      Это первый же комментарий к топику.
  • 0
    ИМХО, резюме должно быть такое: юзайте ===
  • +2
    Кому нужна транзитивность в PHP, те используют === тождественное сравнение активных типов (без преобразований). Но статья классная, хороший справочник)
  • 0
    В большинстве случаев большинство, к счастью, эти примеры так и остаются просто любопытными случаями, но есть то на чем наверное рано или поздно спотыкается любой пхпшник, при том иногда довольно больно спотыкается — это empty('0')==true, возможно стоило упомянуть в статье и об этом, оно в принципе о том же.
  • 0
    На работе php-шники делают нам REST-API. Когда десериализуем Json происходит сущий ад. Числа в одном и том же запросе то в string, то во float, а если число 1, то приходит int. Писали, ругались. Проблемы они не видят, видимо слово «типы» им вообще не знакомо. Не хочу обижать всех, кто пишет на php, но сам язык предрасположен к написанию говнокода. В новых версиях намечается прогресс, хоть это радует.
    • +1
      На работе сишники присылают нам в php xml, валидацию не проходит, даже кавычки расставлены неверно. Не хочу обижать никого, но c++ предрасположен к генерации невалидного xml. Проблемы они не видят, их собственноручная библиотека проходит все тесты. Надеюсь в версии c++ 15 эту проблему решат, а пока хотим перейти на nodejs.
      • 0
        Если в c++ вы указали тип поля int, то string вы туда никак не засунете. Чего не скажешь о php. Я вам реальный случай из жизни привожу, а вы передёргиваете.
        • 0
          Вы рассказали реальный случай из жизни неквалифицированных работников (и видимо такого же отдела кадров и руководителя тех. отдела). Не понятно, причем тут PHP абсолютно, именно поэтому я привела пример с кавычками, а не типами.
          И если вы не можете выставить элементарные требования к формату, то хоть кто вам будет слать JSON, и хоть с какими типами. А если формат есть, а работа выполняется некачественно, то можно обвинять язык, ОС, железо, ну вы поняли.
          • 0
            В языке со статической типизацией даже совсем зелёный новичок не совершил бы таких ошибок.
            • 0
              Подразумевал сильную типизацию, не статическую.
            • 0
              Совершил бы другие, и? Вы обвиняете язык в его возможностях?
    • 0
      А зачем ругаться? Есть такая штука json-schema. И тут как бы одно из двух либо результат ей соответсвует и тогда никаких претензий друг к другу. Либо нет, тогда значит апи не соответствует спеке.
      • –2
        Json это лишь пример. Я говорю о том, что люди понятия не имеют, чем отличается «1», 1.0 и 1. Хотя пишут на php не первый год.
  • +1
    Всё это говорит о том, что программисту надо воздерживаться от неоднозначностей, предпочитать ===, а не ==, и вообще, писать ясный и читаемый код. При желании в любом языке можно найти разные «пасхалки», глюки и неоднозначности, — только зачем их все пихать в код? Опять же, юнит-тесты на что?

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