Autor Tema: El OR y AND a nivel de bits ¿sólo conviene usarlo para agilizar procesos?

0 Usuarios y 1 Visitante están viendo este tema.

20 Mayo, 2024, 12:34 pm
Leído 1075 veces

Tachikomaia

  • $$\Large \color{#5b61b3}\pi\,\pi\,\pi\,\pi\,\pi$$
  • Mensajes: 1,297
  • País: uy
  • Karma: +0/-0
  • Sexo: Masculino
explícame cómo funciona el desplazamiento | en programación

GPT:
Citar
En programación, el operador de desplazamiento `|` se conoce como "OR a nivel de bits" o "OR bit a bit". Este operador se utiliza principalmente en lenguajes de programación que admiten manipulación de bits, como C, C++, Java, entre otros.

El operador `|` realiza una operación lógica OR (o inclusiva) entre los bits de dos operandos. A nivel de bits, el OR devuelve un 1 si al menos uno de los bits correspondientes en los operandos es 1. Si ambos bits son 0, el resultado será 0.

Aquí hay un ejemplo para ilustrar cómo funciona el operador de desplazamiento `|`:

Supongamos que tenemos dos números en representación binaria:

```
A: 11001010
B: 10110111
```

Si aplicamos el operador `|` entre A y B, el resultado sería:

```
A | B: 11111111
```

Cada bit en la posición correspondiente se compara entre A y B. Si al menos uno de los bits es 1, el resultado tendrá un 1 en esa posición. En este caso, todos los bits son 1 porque hay al menos un 1 en cada posición.

Es importante destacar que el operador `|` se aplica a nivel de bits, lo que significa que opera individualmente en cada bit de los operandos. Esto puede ser útil cuando se trabaja con máscaras de bits, banderas o manipulación de bits específica.

Espero que esta explicación te haya ayudado a comprender cómo funciona el operador de desplazamiento `|` en programación. Si tienes alguna otra pregunta, ¡no dudes en preguntar!

y el and?

Citar
¡Claro! El operador de desplazamiento `&` se conoce como "AND a nivel de bits" o "AND bit a bit" en programación. Este operador se utiliza para realizar una operación lógica AND entre los bits correspondientes de dos operandos.

El operador `&` compara los bits en cada posición de los operandos y devuelve un 1 si y solo si ambos bits correspondientes son 1. Si alguno de los bits es 0, el resultado será 0.

Aquí hay un ejemplo para ilustrar cómo funciona el operador de desplazamiento `&`:

Supongamos que tenemos dos números en representación binaria:

```
A: 11001010
B: 10110111
```

Si aplicamos el operador `&` entre A y B, el resultado sería:

```
A & B: 10000010
```

Cada bit en la posición correspondiente se compara entre A y B. Solo si ambos bits son 1, el resultado tendrá un 1 en esa posición. En este caso, solo el séptimo y el segundo bit son 1 en ambos operandos, por lo que el resultado tiene un 1 solo en esas posiciones.

Al igual que con el operador `|`, el operador `&` se aplica a nivel de bits y es útil para realizar operaciones de máscaras de bits, filtrado de bits o verificación de ciertas propiedades de los bits.

Espero que esta explicación te haya ayudado a comprender cómo funciona el operador de desplazamiento `&` en programación. Si tienes más preguntas, ¡no dudes en hacerlas!

para qué sirven esos desplazamientos, por qué se le llama desplazamientos?

Citar
Los operadores de desplazamiento, como `<<` (desplazamiento a la izquierda) y `>>` (desplazamiento a la derecha), son diferentes de los operadores `|` y `&` que mencionamos anteriormente. Permíteme explicarte cómo funcionan y por qué se les llama "desplazamientos".

Estos operadores de desplazamiento se utilizan para mover los bits de un número hacia la izquierda o hacia la derecha. El desplazamiento se realiza en función de la cantidad de posiciones especificadas.

1. Desplazamiento a la izquierda (`<<`): Este operador desplaza los bits de un número hacia la izquierda en la cantidad de posiciones especificadas. Cada desplazamiento hacia la izquierda duplica el valor del número. Por ejemplo:

   ```
   5 << 2 // Resultado: 20
   ```
  
   En este caso, el número 5 en binario es `101`. Al desplazarlo dos posiciones hacia la izquierda, obtenemos `10100`, que es el número 20 en binario.

2. Desplazamiento a la derecha (`>>`): Este operador desplaza los bits de un número hacia la derecha en la cantidad de posiciones especificadas. Cada desplazamiento hacia la derecha divide el valor del número. Hay dos tipos de desplazamiento a la derecha: desplazamiento a la derecha lógico y desplazamiento a la derecha aritmético.

   - Desplazamiento a la derecha lógico (`>>>`): En este tipo de desplazamiento, los bits se desplazan hacia la derecha y se rellenan con ceros en la posición más significativa. Por ejemplo:

     ```
     20 >>> 2 // Resultado: 5
     ```
    
     En este caso, el número 20 en binario es `10100`. Al desplazarlo dos posiciones hacia la derecha, obtenemos `101`, que es el número 5 en binario.

   - Desplazamiento a la derecha aritmético (`>>`): En este tipo de desplazamiento, los bits se desplazan hacia la derecha y se rellenan con el bit más significativo (el bit de signo) en la posición más significativa. Esto significa que si el número es positivo, se rellena con ceros, y si el número es negativo, se rellena con unos. Por ejemplo:

     ```
     -20 >> 2 // Resultado: -5
     ```
    
     En este caso, el número -20 en binario es `11111111111111111111101100`. Al desplazarlo dos posiciones hacia la derecha, obtenemos `11111111111111111111111011`, que es el número -5 en binario.

Los operadores de desplazamiento son útiles en situaciones donde se necesita manipular bits individuales de un número, realizar cálculos rápidos de potencias de dos, implementar algoritmos de codificación o decodificación, y en otros casos específicos relacionados con la manipulación de bits.

Espero que esta explicación aclare el concepto de desplazamientos y cómo se utilizan en programación. Si tienes más preguntas, no dudes en hacerlas.

dame un ejemplo de un operador or usado en un programa

Citar
Aquí tienes un ejemplo sencillo de cómo se puede utilizar el operador OR (`|`) en un programa:

Supongamos que tienes un programa en C que maneja permisos de usuario representados como combinaciones de banderas o "flags". Cada flag indica un permiso específico, y puedes combinar múltiples flags utilizando el operador OR para obtener un conjunto de permisos completo.

```c
#include <stdio.h>

// Definición de flags de permisos
#define PERM_READ  1  // Permiso de lectura: 0001 en binario
#define PERM_WRITE 2  // Permiso de escritura: 0010 en binario
#define PERM_EXEC  4  // Permiso de ejecución: 0100 en binario

int main() {
    int permisosUsuario = PERM_READ | PERM_WRITE;  // Combinación de permisos de lectura y escritura

    // Verificación de permisos
    if (permisosUsuario & PERM_READ) {
        printf("El usuario tiene permiso de lectura.\n");
    }
    if (permisosUsuario & PERM_WRITE) {
        printf("El usuario tiene permiso de escritura.\n");
    }
    if (permisosUsuario & PERM_EXEC) {
        printf("El usuario tiene permiso de ejecución.\n");
    } else {
        printf("El usuario no tiene permiso de ejecución.\n");
    }

    return 0;
}
```

En este ejemplo, definimos tres flags de permisos (`PERM_READ`, `PERM_WRITE` y `PERM_EXEC`) que se representan mediante valores binarios distintos. Luego, utilizamos el operador OR (`|`) para combinar los flags de permisos de lectura y escritura en la variable `permisosUsuario`.

Después, utilizamos el operador AND (`&`) en una serie de condiciones para verificar los permisos del usuario. Si el resultado de la operación AND entre los permisos del usuario y un flag específico es distinto de cero, significa que el usuario tiene ese permiso y se muestra un mensaje correspondiente.

En este caso, el programa imprimirá los mensajes "El usuario tiene permiso de lectura." y "El usuario tiene permiso de escritura.", ya que esos flags están presentes en la combinación de permisos del usuario. Sin embargo, no imprimirá el mensaje "El usuario tiene permiso de ejecución." porque ese flag no está presente en los permisos del usuario.

Este es solo un ejemplo básico para ilustrar el uso del operador OR en un programa. En aplicaciones reales, la combinación de flags y el uso de operadores OR pueden ser mucho más complejos y variados, dependiendo de los requisitos específicos del programa.

dame otro ejemplo, pero relacionado con videojuegos

Citar
Claro, aquí tienes un ejemplo relacionado con videojuegos. Supongamos que estás desarrollando un videojuego de plataformas y tienes un personaje que puede tener diferentes habilidades. Puedes utilizar el operador OR (`|`) para combinar las habilidades del personaje y determinar qué acciones puede realizar.

```python
# Definición de habilidades del personaje
HABILIDAD_SALTO = 1     # Salto: 0001 en binario
HABILIDAD_CORRER = 2    # Correr: 0010 en binario
HABILIDAD_ATACAR = 4    # Atacar: 0100 en binario
HABILIDAD_PLANEAR = 8   # Planear: 1000 en binario

# Habilidades del personaje
habilidades_personaje = HABILIDAD_SALTO | HABILIDAD_CORRER | HABILIDAD_ATACAR

# Verificación de habilidades
if habilidades_personaje & HABILIDAD_SALTO:
    print("El personaje puede saltar.")

if habilidades_personaje & HABILIDAD_CORRER:
    print("El personaje puede correr.")

if habilidades_personaje & HABILIDAD_ATACAR:
    print("El personaje puede atacar.")

if habilidades_personaje & HABILIDAD_PLANEAR:
    print("El personaje puede planear.")
else:
    print("El personaje no puede planear.")
```

En este ejemplo, el personaje tiene cuatro habilidades diferentes: salto, correr, atacar y planear. Cada habilidad se representa mediante un valor binario diferente. Utilizamos el operador OR (`|`) para combinar las habilidades del personaje en la variable `habilidades_personaje`.

Luego, utilizamos el operador AND (`&`) en una serie de condiciones para verificar las habilidades del personaje. Si el resultado de la operación AND entre las habilidades del personaje y una habilidad específica es distinto de cero, significa que el personaje tiene esa habilidad y se muestra un mensaje correspondiente.

En este caso, el programa imprimirá los mensajes "El personaje puede saltar.", "El personaje puede correr." y "El personaje puede atacar.", ya que esas habilidades están presentes en las habilidades del personaje. Sin embargo, no imprimirá el mensaje "El personaje puede planear." porque esa habilidad no está presente en las habilidades del personaje.

Este es solo un ejemplo básico para ilustrar el uso del operador OR en un contexto de videojuegos. En un juego real, las habilidades y su combinación podrían ser mucho más complejas y permitir una amplia variedad de acciones para el personaje.

No entiendo ¿no es más fácil algo como:
HABILIDAD_SALTO = true
HABILIDAD_CORRER = true
HABILIDAD_ATACAR = true
HABILIDAD_PLANEAR = false
Si HABILIDAD_SALTO == true
  puede saltar
etc? O en vez de true/false, "Sí"/"No", 1/0, etc.

Yo quiero aprender eso del or y and si es algo que no se puede hacer de otro modo o resulta mucho más fácil hacerlo así. Si es por agilizar procesos, entiendo que es importante pero por ahora paso.

A un número le puedo sumar otro, eso es útil y difícilmente insustituible.

El or viene a decir que a 2 binarios los puedo transformar en 1, uno tiene más probabilidad de tener 1s ¿pero eso para qué sirve?

Capaz que los ejemplos que me dió son incorrectos, imagino que si se tienen 2 cosas a comparar, con muchas variables, en vez de hacer:
SumaDe1 = Objeto1TieneCuello+Objeto1TieneManos+Objeto1TienePiernas
SumaDe2 = Objeto2TieneCuello+Objeto2TieneManos+Objeto2TienePiernas
Si SumaDe1 == SumaDe2
  los objetos son iguales
...
usando or se podría hacer la comparación sin sumar ni usar 3 ifs ¿puede ser?

20 Mayo, 2024, 05:50 pm
Respuesta #1

Eren

  • $$\Large \color{#5e8d56}\pi\,\pi\,\pi$$
  • Mensajes: 134
  • País: mx
  • Karma: +1/-0
  • Sexo: Masculino
Hola. Quizá lo tienes ya claro, pero quiero asegurarme de que no estás confundiendo el "or de despazamiento" con el "or" como disyunción lógica. El primero se usa para hacer operaciones "bit a bit" (bitwise en inglés), y es común en la implementación de las llamadas "máscaras" de bits. Estas máscaras tienen varios usos, pero veamos uno basado en la última respuesta que te dio ChatGPT.

En esa respuesta, se definen las constantes siguientes para representar las habilidades de un personaje.
Código: [Seleccionar]
HABILIDAD_SALTO = 1     # Salto: 0001 en binario
HABILIDAD_CORRER = 2    # Correr: 0010 en binario
HABILIDAD_ATACAR = 4    # Atacar: 0100 en binario
HABILIDAD_PLANEAR = 8   # Planear: 1000 en binario

Una forma de definir las habilidades de un personaje es ponerlas en un arreglo:

\(
\mathtt{habilidades\_personaje = [HABILIDAD\_SALTO, HABILIDAD\_CORRER, HABILIDAD\_ATACAR]}
 \)

En ese caso, para consultar si el personaje tiene cierta habilidad, simplemente la buscas en el arreglo. Esa es una solución válida, pero no la más eficiente. Para mejorar la implementación, es posible usar una máscara de bits que te permita fácilmente especificar las habilidades del personaje y consultar eficientemente si el personaje tiene cierta habilidad. Por ejemplo, consideremos el resultado de las operaciones siguientes con el "or" bit a bit:

\(
\mathtt{habilidades\_personaje \leftarrow HABILIDAD\_SALTO | HABILIDAD\_CORRER | HABILIDAD\_ATACAR = 0001 | 0010 | 0100 = 0111}.
 \)

Con esto, comprobar si el personaje tiene una habilidad dada se reduce a verificar si tiene un 1 en la posición correspondiente (el tercer 1, por ejemplo, indica que el personaje tiene la habilidad de atacar). Observa que si el resultado anterior no cambia si vuelves a operar con alguna de las habilidades ya "incluidas", pues, por ejemplo

\(
\mathtt{habilidades\_personaje \leftarrow habilidades\_personaje | HABILIDAD\_CORRER = 0111 | 0010 = 0111}.
 \)

Ahora bien, la comprobación de "si tiene el 1 en el lugar correspondiente" a la habilidad se hace de inmediato con el "and de despazamiento", que no es otra cosa que el operador "and" aplicado "bit a bit":

\(
\mathtt{habilidades\_personaje\ \&\ HABILIDAD\_ATACAR = 0111\ \&\ 0100 = 0100},
 \)

y este valor es equivalente a true por ser distinto de cero, lo que indica que, en efecto, el personaje tiene la habilidad de atacar.

23 Mayo, 2024, 10:18 am
Respuesta #2

Tachikomaia

  • $$\Large \color{#5b61b3}\pi\,\pi\,\pi\,\pi\,\pi$$
  • Mensajes: 1,297
  • País: uy
  • Karma: +0/-0
  • Sexo: Masculino
No entiendo, si ya se tiene una variable que marca si tiene una habilidad o no ¿para qué hacer la operación or o and?

Se tiene estas 4 variables:
Código: [Seleccionar]
HABILIDAD_SALTO = 1     # Salto: 0001 en binario
HABILIDAD_CORRER = 2    # Correr: 0010 en binario
HABILIDAD_ATACAR = 4    # Atacar: 0100 en binario
HABILIDAD_PLANEAR = 8   # Planear: 1000 en binario

Para saber si puede saltar basta hacer:
Código: [Seleccionar]
Si HABILIDAD_SALTO == 1
  // puede saltar

No es necesaria una 5ta ni operar las anteriores. Lo que veo ahora que no había visto es que cuando crea la 5ta no está usando las 4, por eso le da false la habilidad de planear.

Más o menos entendí, pero sigo sin verle mucho sentido, usa 5 variables cuando podría usar 4, o sólo 1 si vamos al caso:
Código: [Seleccionar]
HabilidadesDePersonaje = 0101;
// El 1er caracter indica si puede planear.
// El 2ndo indica si puede atacar.
// El 3ero si puede correr.
// 4to si puede saltar.
Aunque los ifs así no sé si serían más rápìdos.

23 Mayo, 2024, 02:49 pm
Respuesta #3

Abdulai

  • Moderador Global
  • Mensajes: 3,044
  • País: ar
  • Karma: +0/-0
  • Sexo: Masculino
La ventaja está en el código compilado.  Aunque como todo en la vida, a veces la diferencia es drástica y otras es casi igual, todo depende del código.

Podés hacer pruebas en https://godbolt.org/  , donde en un panel escribís tu código y en el otro te aparece compilado. 

23 Mayo, 2024, 03:45 pm
Respuesta #4

geómetracat

  • Moderador Global
  • Mensajes: 4,065
  • País: es
  • Karma: +0/-0
  • Sexo: Masculino
Yo, sinceramente, a no ser que tengas que programar ciertas cosas concretas de bajo nivel o que la eficiencia sea un factor esencial para ti (que a juzgar de lo que pones por aquí no es el caso), no los usaría para nada. Para programación "normal" considero que es mejor usar booleanos y las operaciones con booleanos.

Además, la ganancia en eficiencia (por ejemplo de un desplazamiento a nivel de bits vs una multiplicación por 2) no es tan clara. Hoy en día tanto hardware como compiladores están muy optimizados y son muy sofisticados, de manera que lo normal es que a nivel de hardware ya se implemente de la forma más eficiente posible. Y otro efecto de la complejidad de los compiladores es que es difícil predecir si "complicar" un código usando operaciones a nivel de bit realmente va a ser más rápido que otro que no lo use, más allá de medirlo empíricamente. Por otro lado, el uso de operaciones a nivel de bits en estas situaciones tiene el problema de ofuscar el código, de manera que es menos comprensible.

Así pues, en el ejemplo que te dio ChatGPT yo no usaría bits, sino booleanos. Y restringiría el uso de bits a contextos en los que aparezcan de forma natural (protocolos, criptografía, ciertas operaciones con gráficos, etc)
La ecuación más bonita de las matemáticas: \( d^2=0 \)

23 Mayo, 2024, 06:26 pm
Respuesta #5

Eren

  • $$\Large \color{#5e8d56}\pi\,\pi\,\pi$$
  • Mensajes: 134
  • País: mx
  • Karma: +1/-0
  • Sexo: Masculino
No entiendo, si ya se tiene una variable que marca si tiene una habilidad o no ¿para qué hacer la operación or o and?

Se tiene estas 4 variables:
Código: [Seleccionar]
HABILIDAD_SALTO = 1     # Salto: 0001 en binario
HABILIDAD_CORRER = 2    # Correr: 0010 en binario
HABILIDAD_ATACAR = 4    # Atacar: 0100 en binario
HABILIDAD_PLANEAR = 8   # Planear: 1000 en binario

Para saber si puede saltar basta hacer:
Código: [Seleccionar]
Si HABILIDAD_SALTO == 1
  // puede saltar

Estrictamente hablando, no son variables, sino constantes con las cuales representar los grupos de bits que definen las habilidades de un personaje dado. Veamos todo esto de forma más concreta. Imaginemos que tenemos modelado el personaje mediante el tipo siguiente:

Código: [Seleccionar]
class Personaje {
    nombre: string;
    nivel: int;
    habilidades: int;
}

Lo importante en este ejemplo es el campo habilidades, que nos va a permitir indicar cuáles son las habilidades de un personaje concreto, las cuales serán especificadas mediante las constantes siguientes:
Código: [Seleccionar]
const HABILIDAD_CORRER = 1;
const HABILIDAD_SALTAR = 2;
const HABILIDAD_ATACAR = 4;
const HABILIDAD_PLANEAR = 8;

Luego, en algún punto del programa se crea una instancia concreta de un personaje, digamos, así:
Código: [Seleccionar]
let miPersonaje = new Personaje();
miPersonaje.nombre = "Eren";
miPersonaje.nivel = 1;
miPersonaje.habilidades = HABILIDAD_CORRER | HABILIDAD_SALTAR;

La parte en la que hay que fijarnos es la última línea, que de forma bastante cómoda (y eficiente) nos permite definir todas las habilidades que la instancia miPersonaje posee, utilizando un único campo habilidades. Luego en otra parte del programa, podemos agregar un nueva habilidad de la manera siguiente:
Código: [Seleccionar]
miPersonaje.habilidades = miPersonaje.habilidades | HABILIDAD_ATACAR
O si la sintaxis del lenguaje lo permite, así:
Código: [Seleccionar]
miPersonaje.habilidades |= HABILIDAD_ATACAR

Con esto, la prueba siguiente pasa:
Código: [Seleccionar]
if (miPersonaje.habilidades & HABILIDAD_ATACAR) {
   // Realizar ataque
}

Desde luego, los cálculos con operadores a nivel de bits se pueden abstraer en un método en el tipo Personaje:
Código: [Seleccionar]
class Personaje {
    // Campos...
    tieneHabilidad(habilidad: int): boolean {
        return (boolean)this.habilidades & habilidad;
    }
}

de manera que la prueba anterior podría quedar así:
Código: [Seleccionar]
if (miPersonaje.tieneHabilidad(HABILIDAD_ATACAR)) {
    // Realizar ataque
}

Desde luego, también puedes implementar todo esto mediante booleanos, pero entonces tendrías que crear un campo para cada habilidad en la clase Personaje:
Código: [Seleccionar]
class Personaje {
    nombre: string;
    nivel: int;
    habilidad_correr: boolean;
    habilidad_saltar: boolean;
    habilidad_atacar: boolean;
    habilidad_planear: boolean;
}

Luego, para habilitar habilidades, les asignas valores verdaderos:
Código: [Seleccionar]
miPersonaje.habilidad_correr = true;
miPersonaje.habilidad_saltar = true;

Observa que en este caso las habilidades no tienen nombre fuera de la clase Personaje, de manera que para probar si un personaje tiene o no cierta habilidad tienes que referirte al campo específico que la define:
Código: [Seleccionar]
if (miPersonaje.habilidad_atacar) {
    // Realizar ataque
}

Esto no es ningún problema, desde luego. Lo que sí puede ser un problema desde el punto de vista de la arquitectura de software es que agregar una nueva habilidad implica modificar la clase Personaje, lo cual no ocurre con la primera implementación, aquella con máscaras de bits. Por tanto, esta implementación se adhiere más a las buenas prácticas de la arquitectura de software, una de las cuales es programar de tal manera que el código esté abierto a la extensión pero cerrado a la modificación.

Yo, sinceramente, a no ser que tengas que programar ciertas cosas concretas de bajo nivel o que la eficiencia sea un factor esencial para ti (que a juzgar de lo que pones por aquí no es el caso), no los usaría para nada. Para programación "normal" considero que es mejor usar booleanos y las operaciones con booleanos.

Además, la ganancia en eficiencia (por ejemplo de un desplazamiento a nivel de bits vs una multiplicación por 2) no es tan clara. Hoy en día tanto hardware como compiladores están muy optimizados y son muy sofisticados, de manera que lo normal es que a nivel de hardware ya se implemente de la forma más eficiente posible. Y otro efecto de la complejidad de los compiladores es que es difícil predecir si "complicar" un código usando operaciones a nivel de bit realmente va a ser más rápido que otro que no lo use, más allá de medirlo empíricamente. Por otro lado, el uso de operaciones a nivel de bits en estas situaciones tiene el problema de ofuscar el código, de manera que es menos comprensible.

Así pues, en el ejemplo que te dio ChatGPT yo no usaría bits, sino booleanos. Y restringiría el uso de bits a contextos en los que aparezcan de forma natural (protocolos, criptografía, ciertas operaciones con gráficos, etc)

Coincido en que muchas veces el beneficio de utilizar operadores a nivel de bits en términos de eficiencia es insignificante, pero difiero en que estos operadores introduzcan una complejidad real en el código, pues es un concepto que no cuesta mucho aprender y que, una vez comprendidos, permiten realizar implementaciones bastante legibles y más amenas al mantenimiento. En cuanto a la legibilidad, resulta que, por ejemplo, las máscaras de bits permiten reducir el número de argumentos de una función, además de permitir pasar parámetros que pueden ser especificados mediante constantes que dejan claro el objetivo de los mismos. Por ejemplo, en PHP, existe una función para definir qué errores son reportados, la cual recibe un único argumento, una máscara de bits para activar todos los tipos de errores que serán emitidos durante la ejecución del programa:
Código: [Seleccionar]
error_reporting(E_NOTICE | E_WARNING | E_USER_ERROR);


Esto es mucho más cómodo que definir una función (o una variable global booleana) por cada nivel de error, así como una única función con decenas de argumentos, uno para cada nivel de error.

Saludos.

23 Mayo, 2024, 08:13 pm
Respuesta #6

geómetracat

  • Moderador Global
  • Mensajes: 4,065
  • País: es
  • Karma: +0/-0
  • Sexo: Masculino
Coincido en que muchas veces el beneficio de utilizar operadores a nivel de bits en términos de eficiencia es insignificante, pero difiero en que estos operadores introduzcan una complejidad real en el código, pues es un concepto que no cuesta mucho aprender y que, una vez comprendidos, permiten realizar implementaciones bastante legibles y más amenas al mantenimiento. En cuanto a la legibilidad, resulta que, por ejemplo, las máscaras de bits permiten reducir el número de argumentos de una función, además de permitir pasar parámetros que pueden ser especificados mediante constantes que dejan claro el objetivo de los mismos. Por ejemplo, en PHP, existe una función para definir qué errores son reportados, la cual recibe un único argumento, una máscara de bits para activar todos los tipos de errores que serán emitidos durante la ejecución del programa:
Código: [Seleccionar]
error_reporting(E_NOTICE | E_WARNING | E_USER_ERROR);


Esto es mucho más cómodo que definir una función (o una variable global booleana) por cada nivel de error, así como una única función con decenas de argumentos, uno para cada nivel de error.
No conozco bien PHP, así que ahí no me pronuncio, pero en la mayoría de lenguajes puedes hacer lo mismo con un array o con una lista de booleanos.

Sobre la legibilidad/mantenimiento pues depende de quién lo vaya a hacer. En un contexto en el que la gran mayoría de la gente no es informática y sabe lo justo de programar (lo que me encuentro normalmente en el día a día) este tipo de cosas puede despistar bastante. Y si no estás ahí para explicárselo a la persona que tiene que trabajar con el código, aún más.

Por otro lado, a nivel conceptual, sobre todo a la hora de diseñar un programa más o menos complejo, a mí me resulta mucho más natural pensar valores lógicos como booleanos que como máscaras de bits, por aquello de mantener los tipos "conceptuales" de las entradas y salidas de las funciones. Pero esto puede ir a gustos, no te digo que no.
La ecuación más bonita de las matemáticas: \( d^2=0 \)

23 Mayo, 2024, 11:41 pm
Respuesta #7

Eren

  • $$\Large \color{#5e8d56}\pi\,\pi\,\pi$$
  • Mensajes: 134
  • País: mx
  • Karma: +1/-0
  • Sexo: Masculino
No conozco bien PHP, así que ahí no me pronuncio, pero en la mayoría de lenguajes puedes hacer lo mismo con un array o con una lista de booleanos.
Desde luego, aunque la misma alternativa existe en PHP. Pero la razón por la que incluí ese ejemplo es por que se trata de una función integrada en la biblioteca estándar de un lenguaje altamente popular (con programadores de todos los niveles), lo cual sugiere la expectativa de que el usuario promedio del lenguaje entienda el uso de los operadores booleanos a nivel de bits. Por otra parte, creo que usar un arreglo o una lista de booleanos complicaría un poco más tanto el uso como la implementación de la función error_reporting. Por un lado está la cuestión de que con una lista de booleanos tienes que ligar la posición en la lista al tipo de error, lo cual sería una pesadilla para este caso particular, pues en PHP existen más de 10 tipos de errores, y sería imposible recordar cuál posición corresponde a cuál error. Por otra parte, si quisieras activar el último de los errores, tendrías que pasar como argumento un arreglo con más de 10 falsos antes del verdadero que activa el error deseado. Aunque quizá tengas en mente algún tipo de arreglo asociativo, en cuyo caso los índices pueden ser cadenas de caracteres que den nombre al error que representan. En ese caso, la invocación de la función que doy en el comentario anterior podría verse así:

\( \mathtt{error\_reporting(["E\_NOTICE" => true, "E\_WARNING" => true, "E\_USER\_ERROR" => true]);} \)

Sin embargo, incluso aquí siento que depender de cadenas de caracteres no es deseable, pues la función tiene que aceptar como argumento un arreglo arbitrario, incluyendo uno con llaves mal escritas, dando lugar a errores difíciles de depurar. En la implementación con máscaras de bits, el compilador pude reportar el error de una variable indefinida si la has escrito mal, lo que facilita la depuración. Desde luego, una función que acepta una máscara de bits realmente acepta cualquier entero, por lo que incluso en ese caso existen formas de pasar argumentos inválidos. En cuanto la implementación de una función error_reporting con arreglos de booleanos, me parece que resultaría un poco más compleja que aquella con una máscara de bits, que no requiere más que una sola variable global de tipo entero para almacenar todos los tipos de errores que han de reportarse. En cambio, con un arreglo de booleanos, requieres una entrada en el arreglo para cada tipo de error, cuyos valores son asignados iterando sobre el arreglo que se pase a la función error_reporting.

Sobre la legibilidad/mantenimiento pues depende de quién lo vaya a hacer. En un contexto en el que la gran mayoría de la gente no es informática y sabe lo justo de programar (lo que me encuentro normalmente en el día a día) este tipo de cosas puede despistar bastante. Y si no estás ahí para explicárselo a la persona que tiene que trabajar con el código, aún más.
Bueno, sí, tienes razón. Sin duda hay muchos programadores que quedarían confundidos al encontrarse con operaciones a nivel de bits, y no todos ellos van a tomarse la molestia de investigar y aprender lo necesario (e incluso algunos ni tendrán el tiempo para ello). Se trata de un concepto que no conviene utilizar en código que será utilizado por programadores de niveles variados, sino más bien en contextos más técnicos o de bajo nivel (en el sentido de menor abstracción), donde incluso podría resultar verdaderamente ventajoso en términos de eficiencia y no mera pedantería.

Por otro lado, a nivel conceptual, sobre todo a la hora de diseñar un programa más o menos complejo, a mí me resulta mucho más natural pensar valores lógicos como booleanos que como máscaras de bits, por aquello de mantener los tipos "conceptuales" de las entradas y salidas de las funciones. Pero esto puede ir a gustos, no te digo que no.
La cosa es que, a fin de cuentas, las máscaras de bits no son más que valores booleanos agrupados todos en un solo entero del cual son sus dígitos, luego estas máscaras pueden pensarse como listas de booleanos pero más fáciles de manipular mediante operadores a nivel de bits y constantes con nombres que revelen su intención o su significado. No siempre serán la opción ideal, pero sin duda son una herramienta mas que conviene tener a la mano.

Saludos.