Curso de PHP gratis · Capítulo 4 de 13 · con Dr. Juggle
Operadores en PHP: == frente a ===, type juggling y floats
PHP convierte tipos por su cuenta (type juggling), y eso tiene trampas. Aquí verás cuándo usar ===, cómo cambió la comparación en PHP 8 y por qué los floats no son exactos.
Aritmética
Los operadores de siempre: +, -, *, /, y algunos más:
%módulo: el resto de la división entera.7 % 3es 1. Muy útil para saber si un número es par ($n % 2 == 0) o para "dar la vuelta" (¡el efecto donut del campo de BIT-8!).potencia:210 es 1024.intdiv($a, $b): división entera (sin decimales).
¿Por qué 8 / 2 da int(4) pero 7 / 2 da float(3.5)? Porque en PHP / devuelve un entero solo si la división es exacta; si no, devuelve un float. Si quieres siempre un entero, usa intdiv().
El signo del resultado de % es el del primer operando: -7 % 3 es -1. Y dividir entre 0 lanza un DivisionByZeroError.
<?php
var_dump(7 / 2, 8 / 2);
var_dump(7 % 3, -7 % 3);
var_dump(2 ** 10, 2 ** -1);
var_dump(intdiv(7, 2));
echo 17 % 5, " ", 10 % 5, "\n";float(3.5) int(4) int(1) int(-1) int(1024) float(0.5) int(3) 2 0
Precedencia: quién va primero
Igual que en matemáticas, * y / van antes que + y -. La tabla completa es larga, pero lo importante:
(y es asociativo por la derecha:23 2 =29).!,++,--, los casts como(int), el menos unario.*,/,%.+,-..(concatenar).- Comparaciones (
<,==...). &&, luego||, luego??, luego?:.- Asignaciones (
=,+=...). and,xor,or(¡las últimas de todas!).
Rareza: -2 2 da -4, porque va antes que el menos: es -(2 ** 2).
Cuando dudes, pon paréntesis. No es de novatos: es de gente que quiere que su código se entienda.
<?php
echo 2 + 3 * 4, "\n";
echo (2 + 3) * 4, "\n";
echo 2 ** 3 ** 2, "\n";
echo -2 ** 2, " ", (-2) ** 2, "\n";
echo 100 / 10 / 2, "\n";14 20 512 -4 4 5
Atajos: +=, ++ y compañía
Operar y guardar en la misma variable es tan común que tiene atajos: $x += 5 equivale a $x = $x + 5. También existen -=, =, /=, %=, *= y .=.
++ suma 1 y -- resta 1, pero hay dos formas:
++$i(preincremento): primero suma y luego devuelve el valor nuevo.$i++(postincremento): devuelve el valor viejo y luego suma.
Solo importa si usas el resultado en la misma expresión. Si es una línea suelta ($i++;), da igual.
<?php
$sacos = 10;
$sacos += 5;
$sacos *= 2;
echo $sacos, "\n";
$i = 5;
echo $i++, " ", $i, "\n";
$j = 5;
echo ++$j, " ", $j, "\n";30 5 6 6 6
¿Qué imprime $i = 5; echo $i++ + ++$i;?
- 10
- 11
- 12
- 13
Ver respuesta
C. 12
$i++ devuelve 5 (y $i pasa a 6). Después ++$i pone $i a 7 y devuelve 7. 5 + 7 = 12. ¡Por eso es mejor no mezclarlos en la misma expresión!
Comparar: == frente a ===
Hay dos formas de comparar igualdad:
==(igualdad débil): convierte los tipos si hace falta y luego compara.5 == "5"estrue.===(igualdad estricta): el tipo también tiene que coincidir.5 === "5"esfalse.
Y sus contrarios: != y !==.
Las conversiones de == dan resultados sorprendentes: "1" == "01" y "10" == "1e1" son true porque, si los dos strings parecen números, PHP los compara como números.
El gran cambio de PHP 8: antes, al comparar un número con un texto no numérico, PHP convertía el texto a número: 0 == "hola" ¡era true! Provocaba agujeros de seguridad. Desde PHP 8, si el texto no es numérico, se compara como texto: 0 == "hola" es false. Si lees código antiguo, recuérdalo.
Usa === por defecto. Usa == solo cuando quieras la conversión a propósito.
<?php
var_dump(5 == "5", 5 === "5");
var_dump("1" == "01", "10" == "1e1", 100 == "1e2");
var_dump(0 == "hola", "abc" == 0);
var_dump(null == false, 0 == "", "0" == false);
var_dump(1.0 === 1);bool(true) bool(false) bool(true) bool(true) bool(true) bool(false) bool(false) bool(true) bool(false) bool(true) bool(false)
Convertir tipos (casting)
Puedes convertir un valor a otro tipo a propósito con un cast: (int), (float), (string), (bool), (array).
(int)"12abc"→ 12 (lee los dígitos del principio).(int)"abc"→ 0.(int)12.9→ 12 (trunca, no redondea).(string)1.0→"1".(string)false→"".
¿Qué es verdadero y qué falso? Al convertir a bool, son false: false, 0, 0.0, "", "0", [] y null. Todo lo demás es true, incluidos "0.0", " " y "false".
Funciones útiles: intval(), floatval(), is_numeric(), is_int()...
Los casts nunca avisan. En cambio, en una operación aritmética, un texto como "5 manzanas" da un Warning, y "hola" * 2 directamente un TypeError en PHP 8.
<?php
var_dump((int)"12abc", (int)"abc", (int)12.9, (int)-12.9);
var_dump((float)"3.14 kg", (string)1.0, (string)false);
var_dump((bool)"0", (bool)"0.0", (bool)" ", (bool)[], (bool)0.0);
var_dump(is_numeric("12.5"), is_numeric("12a"));
var_dump("5 manzanas" + 1);int(12) int(0) int(12) int(-12) float(3.14) string(1) "1" string(0) "" bool(false) bool(true) bool(true) bool(false) bool(false) bool(true) bool(false) int(6)
¿Cuál de estos valores se convierte en true?
"0""""0.0"0
Ver respuesta
C. "0.0"
Solo el string "0" exacto (y el vacío) son falsos. "0.0" es un texto no vacío distinto de "0", así que es verdadero.
Los floats mienten (un poco)
Prueba esto: 0.1 + 0.2 == 0.3 da false. ¿Está roto PHP? No: les pasa a casi todos los lenguajes.
¿Por qué? Los float se guardan en binario (estándar IEEE 754). Igual que 1/3 en decimal es 0,3333... infinito, 0.1 en binario es periódico y no se puede guardar exacto. Se guarda la aproximación más cercana, y al sumar dos aproximaciones el error se nota: 0.30000000000000004.
echo redondea a 14 cifras y te "esconde" el error; var_dump te lo enseña.
Cómo comparar floats:
- Con un margen:
abs($a - $b) < 0.00001(oPHP_FLOAT_EPSILON). - Redondeando:
round($a, 2) == round($b, 2). - Para dinero, trabaja en céntimos con enteros.
<?php
$x = 0.1 + 0.2;
echo $x, "\n";
var_dump($x);
var_dump($x == 0.3);
var_dump(abs($x - 0.3) < PHP_FLOAT_EPSILON);
var_dump(round($x, 2) == 0.3);0.3 float(0.30000000000000004) bool(false) bool(true) bool(true)
Lógica, cortocircuito y ??
Operadores lógicos: && (y), || (o), ! (no). Siempre devuelven un bool.
Cortocircuito: en $a && $b, si $a es falso, PHP ni siquiera evalúa $b (el resultado ya es falso). Igual con || si el primero es verdadero. Se usa para proteger operaciones: $n != 0 && 10 / $n > 2.
El operador ?? (null coalescing) devuelve el de la izquierda si existe y no es null; si no, el de la derecha. ¡Y no da Warning si la variable no existe!
and y or existen, pero tienen una precedencia MÁS BAJA que =. Por eso $r = false or true; guarda false en $r. Usa && y ||.
<?php
var_dump(true && false, true || false, !true);
$n = 0;
var_dump($n != 0 && 10 / $n > 2);
$apodo = null;
echo $apodo ?? "sin apodo", "\n";
echo $noExiste ?? "valor por defecto", "\n";
$r = false or true;
var_dump($r);bool(false) bool(true) bool(false) bool(false) sin apodo valor por defecto bool(false)
Ejercicios de Operadores en PHP resueltos
Intenta resolverlos antes de mirar la solución. En el juego puedes ejecutarlos y comprobarlos automáticamente.
Acertijo 1. Las muestras de Juggle
Juggle ha etiquetado cada frasco con una comparación. Tienes que decir qué resultado da cada una en PHP 8.
Responde a las seis preguntas.
"5" == 5- true
- false
Ver respuesta
A. true
"5" === 5- true
- false
Ver respuesta
B. false
0 == "hola"(en PHP 8)- true
- false
Ver respuesta
B. false
"abc" < "abd"- true
- false
Ver respuesta
A. true
(int)"7 enanitos"- 0
- 7
- Error
Ver respuesta
B. 7
10 % 4 + 2 ** 2- 6
- 18
- 8
Ver respuesta
A. 6
Encargo 2. Carros del mercado
Cada carro del mercado lleva $capacidad sacos. Tienes $sacos sacos. Ambas variables ya existen.
Calcula cuántos carros se llenan del todo y cuántos sacos sobran, e imprime exactamente (con los números calculados):
3 carros llenos y sobran 2 sacos
Pistas
intdiv($sacos, $capacidad)te da los carros llenos.- El resto de la división es
$sacos % $capacidad.
Ver solución
<?php
$carros = intdiv($sacos, $capacidad);
$resto = $sacos % $capacidad;
echo "$carros carros llenos y sobran $resto sacos";
Predicción 3. El reactivo de incrementos
Juggle mezcla incrementos en una pócima. ¿Qué saldrá?
Escribe exactamente lo que imprime el programa.
<?php
$x = 3;
$y = $x++ + 2;
$z = ++$x * 2;
echo "$x $y $z";Ver solución
5 5 10
¡Correcto! $y usó el 3 viejo; $z usó el 5 nuevo.
Caza de bugs 4. La balanza que miente
La balanza del laboratorio suma dos pesadas: 0.1 kg y 0.2 kg. Dice que NO pesan 0.3 kg. Juggle está indignado.
Cambia la comparación para que el programa imprima bool(true), comparando los floats de forma segura. Sigue usando $total.
Código de partida:
<?php
$a = 0.1;
$b = 0.2;
$total = $a + $b;
var_dump($total == 0.3);
Pistas
- Compara la diferencia con un margen pequeño:
abs($total - 0.3) < 0.00001. - O redondea antes de comparar:
round($total, 2) == 0.3.
Ver solución
<?php
$a = 0.1;
$b = 0.2;
$total = $a + $b;
var_dump(abs($total - 0.3) < PHP_FLOAT_EPSILON);
Misión del dron 5. Tornillos por heno
Tuerca cambia 2 de heno por 1 tornillo. BIT-8 está en (0, 1).
Cosecha las 3 hierbas de la fila de BIT-8 (hacia el ESTE). Luego calcula con intdiv() y % cuántos tornillos te dan y cuánto heno sobra, e imprime:
Tornillos: 1, sobra: 1
(calculado a partir de inventario(HENO)).
Esta misión mueve al dron BIT-8 por un campo de cultivo: juégala en PHPlay para ver tu código animado.
Pistas
$heno = inventario(HENO);$tornillos = intdiv($heno, 2);y$sobra = $heno % 2;
Ver solución
<?php
cosechar();
mover(ESTE);
cosechar();
mover(ESTE);
cosechar();
$heno = inventario(HENO);
$tornillos = intdiv($heno, 2);
$sobra = $heno % 2;
echo "Tornillos: $tornillos, sobra: $sobra";
Chuleta: lo esencial
+ - * /,%(resto),**(potencia),intdiv()./da int solo si la división es exacta.- Precedencia:
**> unarios >* / %>+ ->.> comparaciones >&&>||>??>?:>=>and/or. Usa paréntesis. +=,-=,*=,.=...$i++devuelve el viejo;++$iel nuevo.==compara con conversión;===también el tipo. PHP 8:0 == "hola"esfalse.<=>devuelve -1, 0 o 1.- Casts:
(int),(float),(string),(bool). Falsos:false 0 0.0 "" "0" [] null. - Floats:
0.1 + 0.2 != 0.3. Compara conabs($a - $b) < εoround(). &&y||hacen cortocircuito.$x ?? "defecto"sin Warning si no existe.