Lo sé, debí introducir los operadores con anterioridad. Sin embargo, como mencione en el primer post de Rust, estos son apuntes para personas con algo de experiencia en algún lenguaje de programación. Por lo que doy por hecho que entendemos el concepto de operador y como funcionan estos, ya que es algo que funciona de la misma forma en cualquier lenguaje de programación de propósito general.
Un operador es un símbolo que ejecuta operaciones con los valores o las variables de un programa. Por ejemplo, el signo menos (-) es un operador que hace una resta entre dos valores.
En programación Rust, hay varios operadores que se pueden agrupar en las siguientes categorías principales:
Operadores aritméticos
Operadores de asignación
Operadores de comparación
Operadores lógicos
Operadores Aritméticos
La siguiente tabla presenta los operadores aritméticos en Rust y un ejemplo de uso para cada uno:
Operador | Operación | Ejemplo de Uso | Resultado |
+ | Suma | let suma = 5 + 3; | 8 |
- | Resta | let resta = 10 - 2; | 8 |
* | Multiplicación | let multiplicacion = 4 * 6; | 24 |
/ | División | let division = 15 / 3; | 5 |
% | Módulo | let modulo = 7 % 3; | 1 |
Ejemplo:
fn main() {
let a = 10;
let b = 3;
let suma = a + b;
let resta = a - b;
let multiplicacion = a * b;
let division = a / b;
let modulo = a % b;
println!("Suma: {}", suma);
println!("Resta: {}", resta);
println!("Multiplicación: {}", multiplicacion);
println!("División: {}", division);
println!("Módulo: {}", modulo);
}
Operadores de Asignación
La siguiente tabla presenta los operadores de asignación en Rust y un ejemplo de uso para cada uno:
Operador | Operación | Ejemplo de Uso | Equivalente Algebraico |
\= | Asignación | let x = 5; | x = 5 |
+= | Suma y asignación | x += 3; | x = x + 3 |
-= | Resta y asignación | x -= 2; | x = x - 2 |
*= | Multiplicación y asignación | x *= 4; | x = x * 4 |
/= | División y asignación | x /= 2; | x = x / 2 |
%= | Módulo y asignación | x %= 7; | x = x % 7 |
<<= | Desplazamiento a la izquierda y asignación | x <<= 1; | x = x << 1 |
>>= | Desplazamiento a la derecha y asignación | x >>= 2; | x = x >> 2 |
&= | AND bit a bit y asignación | x &= 0b1010; | x = x & 0b1010 |
^= | XOR bit a bit y asignación | x ^= 0b1100; | x = x ^ 0b1100 |
\= | OR bit a bit y asignación | x |
Ejemplo:
fn main() {
let mut x = 5;
x += 3; // Equivalente a: x = x + 3;
x -= 2; // Equivalente a: x = x - 2;
x *= 4; // Equivalente a: x = x * 4;
x /= 2; // Equivalente a: x = x / 2;
x %= 7; // Equivalente a: x = x % 7;
println!("Valor final de x: {}", x);
}
Operadores de Comparación
La siguiente tabla presenta los operadores de comparación en Rust y un ejemplo de uso para cada uno:
Operador | Operación | Ejemplo de Uso | Descripción |
\== | Igualdad | if x == y { /* código */ } | Comprueba si dos valores son iguales. |
!= | Desigualdad | if x != y { /* código */ } | Comprueba si dos valores son diferentes. |
< | Menor que | if x < y { /* código */ } | Comprueba si el valor de la izquierda es menor que el de la derecha. |
> | Mayor que | if x > y { /* código */ } | Comprueba si el valor de la izquierda es mayor que el de la derecha. |
<= | Menor o igual que | if x <= y { /* código */ } | Comprueba si el valor de la izquierda es menor o igual que el de la derecha. |
>= | Mayor o igual que | if x >= y { /* código */ } | Comprueba si el valor de la izquierda es mayor o igual que el de la derecha. |
Ejemplo:
fn main() {
let x = 5;
let y = 10;
if x == y {
println!("x es igual a y");
} else {
println!("x no es igual a y");
}
if x != y {
println!("x no es igual a y");
} else {
println!("x es igual a y");
}
if x < y {
println!("x es menor que y");
} else {
println!("x no es menor que y");
}
if x > y {
println!("x es mayor que y");
} else {
println!("x no es mayor que y");
}
if x <= y {
println!("x es menor o igual que y");
} else {
println!("x no es menor o igual que y");
}
if x >= y {
println!("x es mayor o igual que y");
} else {
println!("x no es mayor o igual que y");
}
}
Operadores Lógicos
La siguiente tabla presenta los operadores lógicos en Rust y un ejemplo de uso para cada uno:
Operador | Operación | Ejemplo de Uso | Descripción |
&& | AND lógico | if x > 0 && y < 10 { /* código */ } | Verifica si ambas condiciones son verdaderas. |
OR lógico | |||
! | NOT lógico | if !(x > 0) { /* código */ } | Niega la condición, es decir, verifica si la condición es falsa. |
Ejemplo:
fn main() {
let a = true;
let b = false;
if a && b {
println!("Ambas condiciones son verdaderas");
} else {
println!("Al menos una de las condiciones es falsa");
}
if a || b {
println!("Al menos una de las condiciones es verdadera");
} else {
println!("Ambas condiciones son falsas");
}
if !a {
println!("La condición 'a' es falsa");
} else {
println!("La condición 'a' es verdadera");
}
}
Operadores Binarios
La siguiente tabla presenta los operadores binarios en Rust y un ejemplo de uso para cada uno:
Operador | Operación | Ejemplo de Uso | Resultado |
& | AND bit a bit | let resultado = 0b1101 & 0b1010; | 0b1000 |
OR bit a bit | let resultado = 0b1101 | ||
^ | XOR bit a bit | let resultado = 0b1101 ^ 0b1010; | 0b0111 |
<< | Desplazamiento a la izquierda | let resultado = 0b1101 << 2; | 0b110100 |
>> | Desplazamiento a la derecha | let resultado = 0b1101 >> 1; | 0b0110 |
Que los operadores sean binarios, no implica que nuestros valores deban estar en binario, este sería el equivalente de la tabla anterior utilizando valores enteros en decimal.
Operador | Operación | Ejemplo de Uso | Resultado |
& | AND bit a bit | let resultado = 13 & 10; | 8 |
OR bit a bit | let resultado = 13 | ||
^ | XOR bit a bit | let resultado = 13 ^ 10; | 7 |
<< | Desplazamiento a la izquierda | let resultado = 13 << 2; | 52 |
>> | Desplazamiento a la derecha | let resultado = 13 >> 1; | 6 |
Ejemplo:
fn main() {
let a: u8 = 0b1010;
let b: u8 = 0b1100;
let result_and = a & b; // AND binario
let result_or = a | b; // OR binario
let result_xor = a ^ b; // XOR binario
let result_shift_left = a << 2; // Desplazamiento a la izquierda
let result_shift_right = a >> 3; // Desplazamiento a la derecha
let result_not = !a; // NOT binario
println!("AND: {:b}", result_and);
println!("OR: {:b}", result_or);
println!("XOR: {:b}", result_xor);
println!("Desplazamiento a la izquierda: {:b}", result_shift_left);
println!("Desplazamiento a la derecha: {:b}", result_shift_right);
println!("NOT: {:b}", result_not);
}
En este punto es importe recordar que la notación 0b1010 representa un número binario. Se utiliza el prefijo 0b para indicar que el número a continuación está en notación binaria, y por ende solo puede estar representado por 0s y 1s. Para resaltar u8, indica que es un numero entero sin signo de 8 bits.
En Rust, el {:b} es un especificador de formato utilizado dentro de la macro println! para imprimir un valor en su representación binaria.
Jugando con Operadores Binarios
En esta sección vamos a jugar un poco más con los operadores binarios de Rust. Vamos a crear una variable que almacene una letra (un carácter) y a partir de este vamos a imprimir su valor en diferentes bases numéricas y vamos a empezar a alternar su valor utilizando operadores binarios. Para la primera parte del código tenemos:
fn main() {
let letra = 'A';
let valor_numerico = letra as u8;
println!("Carácter original: {}", letra);
println!("Valor decimal: {}", valor_numerico);
println!("Valor hexadecimal: {:X}", valor_numerico);
println!("Valor octal: {:o}", valor_numerico);
println!("Valor binario: {:b}", valor_numerico);
}
Realicemos un ejercicio sencillo, súmenos 32 en decimal a la letra e imprimamos nuevamente los valores de la misma.
fn main() {
let letra = 'A';
let mut valor_numerico = letra as u8;
println!("Carácter original: {}", letra);
println!("Valor decimal: {}", valor_numerico);
println!("Valor binario: {:b}", valor_numerico);
valor_numerico += 32;
let nueva_letra = valor_numerico as char;
println!("\nCarácter modificado: {}", nueva_letra);
println!("Valor decimal modificado: {}", valor_numerico);
println!("Valor binario modificado: {:b}", valor_numerico);
}
En esta ocasión nos concentramos en la representación de la letra, tanto como carácter, como número decimal y como número binario. Al valor numérico de la letra 'A' le sumamos el número decimal 32.
Nota: La tabla ASCII presentada se lee de la siguiente manera. Columna, Fila. Que quiere decir esto, que la letra 'A' corresponde al valor numérico 41, pero en hexadecimal. Convirtiendo este valor a decimal correspondería al número 65.
Si revisamos la tabla ASCII o la tabla utf-8 vemos que el valor numérico de la letra 'A' corresponde al número 65 y el valor numérico de la letra 'a' corresponde al número 97, los separa una distancia de 32 (o 20 en hexadecimal).
El alfabeto en mayúscula está separado a una distancia de 32 al alfabeto en minúscula. Ejemplo, si al número 98 le restamos 32 y lo imprimimos como carácter, obtenemos la letra 'B'.
fn main() {
let letra = 'z';
let mut valor_numerico = letra as u8;
println!("Carácter original: {}", letra);
println!("Valor decimal: {}", valor_numerico);
println!("Valor binario: {:b}", valor_numerico);
valor_numerico -= 32;
let nueva_letra = valor_numerico as char;
println!("\nCarácter modificado: {}", nueva_letra);
println!("Valor decimal modificado: {}", valor_numerico);
println!("Valor binario modificado: {:b}", valor_numerico);
}
Este ejemplo, solo cambio el valor de letra por la 'z' minúscula y decidimos restar 32. Lo que nos dio como resultado 'Z' mayúscula.
Podríamos agregar operaciones de corrimiento de bits al ejemplo, pero nos daríamos cuenta de que en muchos casos obtendremos valor de la tabla ASCII que no se pueden imprimir en pantalla, aunque sí podemos validar su valor numérico.
Ahora analicemos el siguiente bloque de código:
fn main() {
let numero: u8 = 204;
let mut resultado:u8;
println!("decimal: {}", numero);
println!("binario: {:b}", numero);
resultado = numero << 2;
println!("\n204 << 2 => {}",resultado);
println!("204 << 2 => {:b}",resultado);
resultado = numero >> 2;
println!("\n204 >> 2 => {}",resultado);
println!("204 << 2 => {:b}",resultado);
}
Tenemos el número 204 en decimal, y declaramos una variable resultado de mutable que luego almacenara el resultado de unas operaciones. Tanto el número, como el resultado de las operaciones, se imprimen en decimal y en binario.
Primero revisemos la siguiente operación. Esta corresponde a un corrimiento de dos bits a la derecha, eso qué significa?
resultado = numero << 2;
Revisemos el siguiente gráfico.
En la representación binaria del número, imaginemos los recuadros azules como la posición del bit que conforma el número 204. Al desplazar el número 2 bits a la izquierda es como si virtualmente moviéramos cada bit unas casillas en el gráfico, el bit en la posición 0 ahora está en la posición 2, el bit en la posición 1 ahora está en la posición 3 y así sucesivamente.
Al llegar a los bits en la posición 6 y 7, estos se desplazan a las posiciones 8 y 9. Pero como nuestra variable fue declarada específicamente como un número sin signo de ocho bits (u8), estas posiciones no existen, por ende estos 2 bits simplemente se pierden.
Ahora, en las posiciones 0 y 1, ya no tenemos bits porque los desplazamos a la izquierda. Por ende, la máquina rellena con 0s.
Un caso similar ocurre con el corrimiento a la derecha. Como lo ilustra el gráfico.
resultado = numero >> 2;
Para este escenario, los bits 0 y 1 (los menos significativos) se pierden. Y los bits 6 y 7 la máquina los rellena con 0s.
Pregunta: Si el tipo de dato fuera u16 en lugar de u8, ¿cuál sería el resultado para este mismo corrimiento de bits a la derecha y a la izquierda? ¿Cambiaria la respuesta?
Referencias
- The Rust Programming Language. 2nd Edition by Steve Klabnik and Carol Nichols, with contributions from the Rust Community. 2023