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. *

Sérgio Domingues
fonte
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 ===

Nanne
fonte
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.

quiabo
fonte
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;
}
Paolo
fonte
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
Vincent Mimoun-Prat
fonte
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.

Sébastien Renauld
fonte
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

Jim Morrison
fonte
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"
Tereško
fonte
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)
Kosem
fonte
Ótimo teste, fiz o mesmo, mas fiz uma boa mesa com. veja minha resposta
IAMTHEBEST
0

Basicamente, use sempre o ===operador, para garantir a segurança de tipo.

Insira a descrição da imagem aqui

Insira a descrição da imagem aqui

EU SOU O MELHOR
fonte