Dlaczego PHP traktuje 0 jako ciąg znaków?

111

Mam następujący fragment kodu:

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

Ma na celu zainicjowanie ceny przedmiotu na 0, a następnie uzyskanie informacji o niej. Jeżeli cena jest oznaczona jako „e”, oznacza to wymianę zamiast sprzedaży, która jest przechowywana w bazie danych jako liczba ujemna.

Istnieje również możliwość pozostawienia ceny jako 0, ponieważ przedmiot jest bonusem lub cena zostanie ustalona w późniejszym momencie.

Ale za każdym razem, gdy cena nie jest ustalona, ​​co pozostawia wartość początkową 0, ifpętla wskazana powyżej jest oceniana jako prawda, a cena jest ustawiana na -1. Oznacza to, że uważa 0 za równe „e”.

Jak można to wyjaśnić?

Gdy cena jest podana jako 0 (po inicjalizacji), zachowanie jest błędne: czasami wartość if jest oceniana jako prawda, czasami jest oceniana jako fałszywa. *

Sérgio Domingues
źródło
1
Zauważyłem, że użycie triple === zamiast double == daje oczekiwane zachowanie. Ale to wciąż jest dziwne.
Sérgio Domingues
2
(odniesienie) wystarczająco wyjaśnione w Podręczniku PHP w rozdziale Żonglowanie typami i zilustrowane w Tabeli porównawczej typów
Gordon
1
Jeśli jedynym możliwym typem ciągu jest „e”, czy nie możesz po prostu przejść do czeku is_string ($ item ["price"])? Byłoby to trochę bardziej wydajne niż ===. [potrzebne źródło]
Jimmie Lin,
w słabych porównaniach między łańcuchem a liczbą całkowitą ciąg jest konwertowany na liczbę całkowitą (zamiast „promowania” liczby całkowitej do łańcucha). if((string)$item['price'] == 'e')naprawia dziwne zachowanie. Zobacz stackoverflow.com/a/48912540/1579327 o więcej szczegółów
Paolo
Zwróć uwagę na inny przypadek w komentarzach poniżej autorstwa @Paolo, gdzie 0 (liczba całkowita) równa się dowolnemu innemu ciągowi, gdy używany jest operator double równa się.
Haitham Sweilem

Odpowiedzi:

113

Robisz, ==co sortuje dla ciebie typy.

0jest int, więc w tym przypadku będzie rzutować 'e'na int. Który nie jest parsowalny jako jeden i stanie się 0. Ciąg '0e'stałby się 0i pasowałby!

Posługiwać się ===

Nanne
źródło
14
Kolejna wada luźnego porównania.
MC Emperor
5
Podstępny. Właśnie wpadłem na ten i byłem zdumiony, dlaczego string == 0. Muszę o tym pamiętać.
Grzegorz
2
Drapałem się też po głowie, gdy zapętlałem klucze ciągów, ale tablica miała początkowy element indeksu „zero”, który nadal dawał wartość true przy pierwszym porównaniu kluczy ciągu. Byłem jak co? Jak w ... więc, oczywiście, ta odpowiedź wyjaśniła to! Dziwię się, że to całe pytanie nie ma JEDNEJ zaakceptowanej odpowiedzi. Pokazuje tylko, że niektórzy zadający pytania są palantami.
IncredibleHat
48

Wynika to z tego, jak PHP wykonuje operację ==porównania, którą operator porównania oznacza:

Jeśli porównujesz liczbę z ciągiem lub porównanie obejmuje ciągi liczbowe, każdy ciąg jest konwertowany na liczbę, a porównanie jest wykonywane numerycznie. […] Konwersja typu nie ma miejsca, gdy porównanie jest ===lub !==polega na porównaniu zarówno typu, jak i wartości.

Ponieważ pierwszym operandem jest liczba ( 0), a drugim ciągiem znaków ( 'e'), łańcuch jest również konwertowany na liczbę (zobacz także tabelę Porównanie z różnymi typami ). Strona podręcznika dotycząca typu danych łańcuchowych definiuje sposób konwersji ciągu znaków na liczbę :

Gdy ciąg jest oceniany w kontekście liczbowym, wynikowa wartość i typ są określane w następujący sposób.

Jeśli ciąg nie zawiera żadnego ze znaków „ .”, „ e” lub „ E”, a wartość liczbowa mieści się w granicach typu liczby całkowitej (zgodnie z definicją w PHP_INT_MAX), ciąg zostanie oceniony jako liczba całkowita. We wszystkich innych przypadkach będzie oceniany jako zmiennoprzecinkowy.

W tym przypadku ciąg jest 'e'i dlatego zostanie oceniony jako zmiennoprzecinkowy:

Wartość jest podana jako początkowa część ciągu. Jeśli ciąg zaczyna się od prawidłowych danych liczbowych, będzie to użyta wartość. W przeciwnym razie wartość będzie wynosić 0(zero). Prawidłowe dane liczbowe to opcjonalny znak, po którym następuje jedna lub więcej cyfr (opcjonalnie zawierających przecinek dziesiętny), po których następuje opcjonalny wykładnik. Wykładnikiem jest „ e” lub „ E”, po którym następuje jedna lub więcej cyfr.

Ponieważ 'e'nie zaczyna się od prawidłowych danych liczbowych, przyjmuje postać float 0.

Gumbo
źródło
3
php projektuje prawie wszystko tak, aby było łatwo porównywalne, a następnie rzuca kilka pułapek, aby zrujnować nasz dzień. Nie pasuje to do reszty filozofii projektowania PHP. Chyba że oszustwo istnieje filozofia ???
user3338098
1
zwłaszcza, że ​​„e” rzutuje na prawdę, a „” na fałsz
user3338098
20
"ABC" == 0

oblicza, trueponieważ pierwsza "ABC" jest konwertowana na liczbę całkowitą i staje się, 0 a następnie jest porównywana z 0.

Jest to dziwne zachowanie języka PHP: normalnie można by oczekiwać, 0że zostanie promowany do łańcucha znaków, "0"a następnie porównany "ABC"z wynikiem false. Być może dzieje się tak w innych językach, takich jak JavaScript, w przypadku których "ABC" == 0oceniane jest słabe porównanie false.

Wykonanie ścisłego porównania rozwiązuje problem:

"ABC" === 0

ocenia false.

Ale co, jeśli muszę porównać liczby jako ciągi z liczbami?

"123" === 123

ocenia, falseponieważ lewy i prawy termin są różnego typu.

Właściwie potrzebne jest słabe porównanie bez pułapek związanych z żonglowaniem typu PHP.

Rozwiązaniem jest jawne promowanie terminów do łańcuchów, a następnie porównanie (ścisłe lub słabe już nie ma znaczenia).

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

jest

true

podczas

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

jest

false


Zastosowany do oryginalnego kodu:

$item['price'] = 0;
/*code to get item information goes in here*/
if((string)$item['price'] == 'e') {
    $item['price'] = -1;
}
Paolo
źródło
9

Operator == będzie próbował dopasować wartości, nawet jeśli są one różnego typu. Na przykład:

'0' == 0 will be true

Jeśli potrzebujesz również porównania typów, użyj operatora ===:

'0' === 0 will be false
Vincent Mimoun-Prat
źródło
9

Twoim problemem jest operator podwójnej równości, który przypisze prawy element do typu lewego. Jeśli wolisz, użyj ścisłego.

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

Wróćmy do twojego kodu (skopiowanego powyżej). W tym przypadku w większości przypadków $ item ['price'] jest liczbą całkowitą (oczywiście z wyjątkiem sytuacji, gdy jest równe e). W związku z tym, zgodnie z prawami PHP, PHP będzie rzutować "e"na liczbę całkowitą, co daje wynik int(0). (Nie wierzysz mi? <?php $i="e"; echo (int)$i; ?>).

Aby łatwo od tego uciec, użyj operatora potrójnej równości (dokładnego porównania), który sprawdzi typ i nie będzie niejawnie rzutować.

PS: zabawny fakt w PHP: a == bnie oznacza tego b == a. Weź swój przykład i odwróć to: if ("e" == $item['price'])nigdy nie zostanie spełniony, pod warunkiem, że $ item ['price'] jest zawsze liczbą całkowitą.

Sébastien Renauld
źródło
7

W PHP jest dość przydatna metoda sprawdzania poprawności kombinacji „0”, „false”, „off” as == false i „1”, „on”, „true” as == true, która jest często pomijana. Jest to szczególnie przydatne do analizowania argumentów GET / POST:

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

Nie jest to całkowicie istotne dla tego przypadku użycia, ale biorąc pod uwagę podobieństwo i fakt, że jest to wynik wyszukiwania, który zwykle znajduje, zadając pytanie o walidację (ciąg znaków) „0” jako fałsz, myślałem, że pomoże to innym.

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

Jim Morrison
źródło
6

Powinieneś użyć ===zamiast== , ponieważ zwykły operator nie porównuje typów. Zamiast tego podejmie próbę wpisania elementów na maszynie.

Tymczasem ===bierze pod uwagę rodzaj przedmiotów.

  • === oznacza „równa się”,
  • == znaczy „eeeeh .. trochę wygląda jak”
tereško
źródło
1
Widzę. To teraz działa (z typoszeregiem):if((string)$item['price']=='e'){ $item['price'] = -1; }
Sérgio Domingues
ale nie powinieneś tego robić. po prostu użyj ===operatora
tereško
3

Myślę, że najlepiej jest to pokazać na przykładach, które zrobiłem, wpadając w to samo dziwne zachowanie. Zobacz mój przypadek testowy i mam nadzieję, że pomoże ci to lepiej zrozumieć zachowanie:

// 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
źródło
Świetny test, zrobiłem to samo, ale zrobiłem fajny stół. zobacz moją odpowiedź
JESTEM NAJLEPSZY
0

Zasadniczo zawsze używaj ===operatora, aby zagwarantować bezpieczeństwo typu.

Tutaj wprowadź opis obrazu

Tutaj wprowadź opis obrazu

JESTEM NAJLEPSZA
źródło