Por que o PHP considera 0 igual a uma string?


111

Eu tenho o seguinte código:

$item['price'] = 0;
/* Code to get item information goes in here */
if($item['price'] == 'e') {
    $item['price'] = -1;
}

A intenção é inicializar o preço do item para 0 e, em seguida, obter informações sobre ele. Se o preço for informado como 'e', ​​significa uma troca ao invés de uma venda, que é armazenada em um banco de dados como um número negativo.

Existe também a possibilidade de deixar o preço 0, seja porque o artigo é um bónus ou porque o preço será definido posteriormente.

Porém, sempre que o preço não for definido, o que o deixa com o valor inicial 0, o ifloop indicado acima é avaliado como verdadeiro e o preço é definido como -1. Ou seja, considera 0 igual a 'e'.

Como isso pode ser explicado?

Quando o preço é fornecido como 0 (após a inicialização), o comportamento é errático: às vezes if é avaliado como verdadeiro, às vezes é avaliado como falso. *


1
Descobri que usar triplo === em vez de duplo == dá o comportamento esperado. Mas ainda é estranho.
— Sérgio Domingues

2
(referência) suficientemente explicado no Manual do PHP no capítulo Malabarismo de Tipos e ilustrado na Tabela de Comparação de Tipos
— Gordon

1
Se o único tipo de string possível for 'e', ​​você não pode simplesmente ir para uma verificação is_string ($ item ["preço"])? Isso seria um pouco mais eficiente do que ===. [carece de fontes?]
— Jimmie Lin

em comparações fracas entre string e inteiro, a string é convertida em inteiro (em vez do inteiro sendo "promovido" para string). if((string)$item['price'] == 'e')corrige o comportamento estranho. Consulte stackoverflow.com/a/48912540/1579327 para mais detalhes
— Paolo

Observe outro caso nos comentários abaixo de @Paolo onde 0 (inteiro) é igual a qualquer outra string ao usar o operador double equals.
— Haitham Sweilem

Respostas:


113

Você está fazendo o ==que separa os tipos para você.

0é um int, portanto, neste caso, ele será convertido 'e'em um int. Que não é analisável como um e se tornará 0. Uma string '0e'se tornaria 0e combinaria!

Usar ===


14
Outra desvantagem da comparação vaga.
— MC Emperor

5
Complicado. Acabei de esbarrar neste e fiquei surpreso por que string == 0. Tenho que me lembrar disso.
— Grzegorz

2
Também estava coçando a cabeça quando estava fazendo um loop em chaves de string, mas o array tinha um item de índice inicial 'zero' que continuou resultando em true na primeira comparação de chave de string. Eu era como o quê? Como no ... então, com certeza, essa resposta esclareceu tudo! Estou surpreso que toda esta questão não tenha UMA resposta aceita. Só serve para mostrar que alguns questionadores são idiotas.
— IncredibleHat

48

Isso se deve à forma como o PHP faz a operação de comparação que o ==operador de comparação denota:

Se você comparar um número com uma string ou se a comparação envolver strings numéricas, cada string será convertida em um número e a comparação será realizada numericamente. [...] A conversão de tipo não ocorre quando a comparação é ===ou !==porque envolve comparar o tipo e também o valor.

Como o primeiro operando é um número ( 0) e o segundo é uma string ( 'e'), a string também é convertida em um número (consulte também a tabela Comparação com vários tipos ). A página manual no tipo de dados de string definiu como a conversão de string em número é feita:

Quando uma string é avaliada em um contexto numérico, o valor e o tipo resultantes são determinados da seguinte maneira.

Se a string não contiver nenhum dos caracteres ' .', ' e' ou ' E' e o valor numérico se ajustar aos limites do tipo inteiro (conforme definido por PHP_INT_MAX), a string será avaliada como um inteiro. Em todos os outros casos, será avaliado como um flutuador.

Neste caso, a string é 'e'e, portanto, será avaliada como um flutuante:

O valor é fornecido pela parte inicial da string. Se a string começar com dados numéricos válidos, este será o valor usado. Caso contrário, o valor será 0(zero). Os dados numéricos válidos são um sinal opcional, seguido por um ou mais dígitos (contendo opcionalmente uma vírgula decimal), seguido por um expoente opcional. O expoente é um ' e' ou ' E' seguido por um ou mais dígitos.

Como 'e'não começa com dados numéricos válidos, ele é avaliado como flutuante 0.


3
php projeta quase tudo para ser facilmente comparável e, em seguida, lança alguns truques apenas para arruinar nosso dia. Isso não se encaixa com o resto da filosofia de design do PHP. A menos que haja decepção, existe filosofia ???
— user3338098

1
especialmente porque "e" converte para verdadeiro e ""
— converte

20
"ABC" == 0

avalia trueporque primeiro "ABC" é convertido em inteiro e se torna, em 0 seguida , é comparado com 0.

Este é um comportamento estranho da linguagem PHP: normalmente, espera- 0se ser promovido a string "0"e, em seguida, comparado "ABC"com um resultado false. Talvez seja o que acontece em outras linguagens como JavaScript, onde a comparação fraca "ABC" == 0avalia false.

Fazer uma comparação estrita resolve o problema:

"ABC" === 0

avalia false.

Mas e se eu precisar comparar números como strings com números?

"123" === 123

avalia falseporque os termos esquerdo e direito são de tipos diferentes.

O que é realmente necessário é uma comparação fraca sem as armadilhas do malabarismo do tipo PHP.

A solução é promover explicitamente os termos para string e então fazer uma comparação (estrito ou fraco não importa mais).

(string)"123" === (string)123

é

true

enquanto

(string)"123" === (string)0

é

false


Aplicado ao código original:

$item['price'] = 0;
/*code to get item information goes in here*/
if((string)$item['price'] == 'e') {
    $item['price'] = -1;
}

9

O operador == tentará combinar os valores mesmo se forem de tipos diferentes. Por exemplo:

'0' == 0 will be true

Se você também precisar de comparação de tipo, use o operador ===:

'0' === 0 will be false

9

Seu problema é o operador de igualdade dupla, que fará o typecast do membro direito pelo tipo do membro esquerdo. Use estrito se preferir.

if($item['price'] == 'e') {
    $item['price'] = -1;
}

Vamos voltar ao seu código (copiado acima). Nesse caso, na maioria dos casos, $ item ['preço'] é um inteiro (exceto quando for igual a e, obviamente). Como tal, pelas leis do PHP, o PHP será typecast "e"para inteiro, que produz int(0). (Não acredita em mim? <?php $i="e"; echo (int)$i; ?>).

Para escapar facilmente disso, use o operador de triplo igual (comparação exata), que verificará o tipo e não implicará no typecast.

PS: um fato divertido sobre PHP: a == bnão significa isso b == a. Pegue seu exemplo e inverta-o: if ("e" == $item['price'])nunca será realmente cumprido desde que $ item ['preço'] seja sempre um número inteiro.


7

Existe um método bastante útil em PHP para validar uma combinação de "0", "false", "off" como == false e "1", "on", "true" como == true que é freqüentemente esquecido. É particularmente útil para analisar argumentos GET / POST:

filter_var( $item['price'], FILTER_VALIDATE_BOOLEAN );

Não é totalmente relevante para este caso de uso, mas dada a semelhança e o fato de que este é o resultado que a pesquisa tende a encontrar ao fazer a pergunta de validar (string) "0" como falso, pensei que ajudaria outros.

http://www.php.net/manual/en/filter.filters.validate.php


6

Você deve usar em ===vez de ==, porque o operador comum não compara os tipos. Em vez disso, ele tentará moldar os itens.

Enquanto isso, ===leva em consideração o tipo de itens.

  • === significa "igual",
  • == significa "eeeeh .. meio que parece"

1
Entendo. Funciona agora (com um typecast):if((string)$item['price']=='e'){ $item['price'] = -1; }
— Sérgio Domingues

mas você não deve fazer isso. basta usar o ===operador
— tereško

3

Eu acho que é melhor mostrar por exemplos que eu fiz, enquanto encontrava o mesmo comportamento estranho. Veja meu caso de teste e espero que ele ajude você a entender melhor o comportamento:

// Normal comparison using the == Operator
echo (0 == "0"); // true
echo (0 == "a"); // true
echo (0 == "safta!"); // true
echo (1000 == "bla"); // false. It appears that PHP has a weird behavior only with the number / string 0 / "0" according to the past 3 examples.
echo (23 == "23"); // true. So as we said, PHP has a problem (not a problem but weird behavior) only when the number / string 0 (or "0") is present
echo (23 == "24"); // false. values aren't equal (unlike last example). The type is less relevant with the == operator as we can see.

// Now using the === and !== Operators
echo ("0" === 0); // false, since === requires both value and type to be the same. Here, type is different (int vs string)
echo ("0" !== 0); // true because they aren't the same in terms of === comparison (type is different and that's why it's true)
echo ("bla" === "blaa"); // false because the values are not the same. The type is the same, but === checks for both equal type and equal value.

//Now using casting and === Operator:
echo ((string)123 === "123"); // true. The casting of the int 123 to string changed it to "123" and now both variables have same value and are of same type
echo ((int)"123" === 123); // true. The casting of the string 123 to int, changed it to int, and now both variables are of same value and type (which is exactly what the === operator is looking for)

// Now using casting and == Operator. Basically, as we've seen above, the == care less for the
// type of var, but more to the value. So the casting is less relevant here, because even
// without casting, like we saw earlier, we can still compare string to int with the == operator
// and if their value is same, we'll get true. Either way, we will show that:
echo ((string)123 == "123"); // true. The casting of the int 123 to string changed it to "123" and now both vars have same value and are of same type
echo ((int)"123" == 123); // true. The casting of the string 123 to int, changed it to int, and now both vars are of same value and type (which is exactly what the === operator is looking for)

Ótimo teste, fiz o mesmo, mas fiz uma boa mesa com. veja minha resposta
— IAMTHEBEST

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.