Matemáticas de bits

Tabla de contenido


Introducción

A menudo, cuando se programa en el entorno Arduino (o en cualquier ordenador), la capacidad de manipular bits individuales será útil o incluso necesario. Estas son algunas de las situaciones en las que las matemáticas de bits pueden ser útiles:
  •  Ahorro de memoria por el empaquetado de hasta 8 valores de los datos de verdadero / falso en un solo byte.
  • Encendido / apagado trozos individuales en un registro o registro de control de puerto de hardware.
  • La realización de ciertas operaciones aritméticas que implican multiplicar o dividir por potencias de 2.
En este tutorial, primero exploramos los operadores bit a bit básicos disponibles en el lenguaje C ++. A continuación, se aprende cómo combinarlos para realizar ciertas operaciones útiles comunes.

El sistema binario

Para explicar mejor los operadores bit a bit, en este tutorial se expresarán la mayoría de los valores enteros usando la notación binaria, también conocida como base dos. 

En este sistema, todos los valores enteros utilizan sólo los valores 0 y 1 para cada dígito. Esta es la forma en prácticamente todos los datos se almacenan en los ordenadores modernos. Cada dígito 0 ó 1 se llama un bit, abreviatura de dígito binario. En el sistema decimal familiar (base diez), un número como 572 significa 5 * 102 + 7 * 10 1 + 2 * 10 0 . Del mismo modo, en el sistema binario un número como 11010 significa 1 * 2 4 + 1 * 2 3 + 0 * 2 2 + 1 * 2 1 + 0 * 2 0 = 16 + 8 + 2 = 26.

Es crucial que usted entienda cómo funciona el sistema binario con el fin de seguir el resto de este tutorial. Si necesita ayuda en esta área, un buen punto de partida es el artículo de Wikipedia sobre el sistema binario.

Por desgracia, la mayoría de los compiladores de C ++ no tienen ningún medio para expresar números binarios directamente en el código fuente. Unos pocos permiten el prefijo 0b seguido de una serie de dígitos, por ejemplo 0b11 == 3. El compilador de C ++ utilizado por Arduino no soporta esta sintaxis. Sin embargo, a partir de Arduino 0007, las constantes B0 a través de B11111111 se definen para su conveniencia.


AND bit a bit

El operador AND en C ++ es un solo signo,&, utilizado entre otros dos expresiones enteras. AND bit a bit opera en cada posición de bit de las expresiones que lo rodean de forma independiente, de acuerdo con esta regla: si los dos bits de entrada son 1, la salida resultante es 1, de lo contrario la salida es 0. Otra forma de expresar esto es: 0 y 1 == 0 1 y 0 == 0 1 & 1 == 1


En Arduino, el tipo int  es un valor de 16 bits, por lo que usar & entre dos expresiones int provoca que se produzcan 16 operaciones AND simultáneas. En un fragmento de código como: int b = 101;    // En binario: 0000000001100101 int c = a y b;   // Resultado: 0000000001000100, o 68 en decimal. Cada uno de los 16 bits en a y b son procesados utilizando el AND bit a bit, y todos los 16 bits resultantes se almacenan en c , lo que resulta en el valor 01000100 en binario, que es 68 en decimal.


Uno de los usos más comunes de la función AND bit a bit es seleccionar un bit en particular (o varios bits) de un valor entero, a menudo llamado enmascaramiento. Por ejemplo, si desea acceder al bit menos significativo de una variable, y almacenar el bit en otra variable, podría utilizar el siguiente código: 

  int x = 5;           // Binario 101
  int y = x & 1;    // ahora y == 1
  x = 4;               // Binario 100
  y = x & 1;         // ahora y == 0


OR bit a bit

El operador binario OR en C ++ es el símbolo de barra vertical,|.Al igual que el operador &, opera de manera independiente para cada bit en sus dos expresiones enteras que lo rodean, pero lo que hace es diferente (por supuesto). El operador OR de dos bits es 1 si uno o ambos de los bits de entrada es 1, de lo contrario es 0. En otras palabras: 


  0 | 0 == 0
  0 | 1 == 1
  1 | 0 == 1
  1 | 1 == 1

Aquí hay un ejemplo de la operación OR utiliza en un fragmento de código C ++: 

 int b = 101;              // En binario: 0000000001100101 
 int c = a | segundo; // Resultado: 0000000001111101, o 125 en decimal.

El O-lógico se utiliza a menudo para asegurarse de que un bit determinado está activado (puesto a 1) en una expresión dada. Por ejemplo, para copiar los bits de a en b, mientras se asegura que el bit más bajo se establece en 1, utilice el siguiente código: 
b = a | 1;  



XOR bit a bit



Hay un operador algo inusual en C ++ denominado OR exclusivo bit a bit, también conocido como XOR bit a bit. (En Inglés esto generalmente se pronuncia (EKS-o.) El operador XOR bit a bit se escribe utilizando el símbolo de intercalación ^.Este operador es similar al operador binario OR |, excepto que evalúa a 1 para una posición dada, cuando precisamente uno de los bits de entrada para esa posición es 1. Si ambos son 0 o ambos son 1, el operador XOR evalúa a 0:

    0 ^ 0 == 0     
    0 ^ 1 == 1     
    1 ^ 0 == 1     
    1 ^ 1 == 0

Otra manera de mirar XOR bit a bit es que cada bit del resultado es 1 si los bits de entrada son diferentes, o 0 si son iguales.

Aquí hay un sencillo ejemplo de código:
 
    int x = 12         // Binario 1100
    int y = 10;       // Binario: 1010    
    int z = x ^ y;    // Binario: 0110, o decimal 6

El operador ^ se utiliza a menudo para cambiar (es decir, el cambio de 0 a 1 o 1 a 0) algunos de los bits de una expresión entera, dejando los demás sin tocas. Por ejemplo:

    y = x ^ 1;    // Cambia el bit más bajo en x, y almacena el resultado en y. 

NOT bit a bit

El operador NOT bit a bit es el carácter de tilde ~. A diferencia de & y |, el operador bit a bit NOT se aplica a un solo operando a su derecha. NOT bit a bit cambia cada bit de su opuesto: 0 se convierte en 1, y 1 se convierte en 0. Por ejemplo:

    int b = ~ a; // Binario: 1111111110011000 = -104 

Es posible que se sorprenda al ver un número negativo como -104 como el resultado de esta operación. Esto es debido a que el bit más alto en una variable int es el llamado bit de signo. Si el bit más alto es 1, el número se interpreta como negativo. Esta codificación de los números positivos y negativos se conoce como complemento a dos. Para obtener más información, consulte el artículo de Wikipedia sobre complemento a dos

Como acotación al margen, es interesante observar que para cualquier entero x, ~ x es el mismo que -x-1 

A veces, el bit de signo en un entero con signo de expresión puede causar algunas sorpresas no deseadas, como veremos más adelante. 

Los operadores de desplazamiento de bit

Hay dos operadores de desplazamiento de bit en C ++: el operador de desplazamiento a la izquierda << y el operador de desplazamiento a la derecha >>. Estos operadores hacen que los bits del operando de la izquierda se desplacen a la izquierda o hacia la derecha el número de posiciones especificadas por el operando de la derecha. Por ejemplo: 

      int a = 5; // Binario: 0000000000000101
      int b = a << 3; // Binario: 0000000000101000, o 40 en decimal     
      int c = b >> 3; // Binaria: 0000000000000101, o de nuevo a 5 como empezamos 

Cuando se desplaza un valor de los bits de x en y (x << y), los bits más a la izquierda de y en x se pierden, literalmente dejan de existir. 

    int a = 5;        // Binario: 0000000000000101
    int b = a << 14;    // Binario: 0100000000000000 - el primer 1 en 101 se desecha

Si está seguro de que ninguno de los unos en un valor se está desplazando hacia afuera, una forma sencilla de pensar en el operador de desplazamiento a la izquierda es que se multiplica el operando de la izquierda por 2 elevado a la potencia del operando de la derecha. Por ejemplo, para generar potencias de 2, se pueden emplear las siguientes expresiones:

    1 << 0 = = 1   
    1 << 1 = = 2   
    1 << 2 = = 4     1 << 3 = = 8  
      ...   
    1 <<  8 = = 256  
    1 <<  9 = = 512            
    1 <<  10 = =1024   
     ...

Cuando se desplazan hacia la derecha los bits de x en y (x >> y) y el bit más alto de x es un 1, el comportamiento depende del tipo de datos exacto de x. Si x es del tipo int, el bit más alto es el bit de signo, la determinación de si x es negativo o no, como hemos comentado anteriormente. En ese caso, el bit de signo se copia en los bits inferiores, por razones históricas: 

     int x = -16;      // Binario: 1111111111110000000     
     int y = x >> 3; // Binario: 1111111111111110 

Este comportamiento, denominado extensión de signo, a menudo no es el comportamiento que deseamos. En lugar de ello, es posible que deseemos ceros que se arrastran de la izquierda. Resulta que las reglas de desplazamiento a la derecha son diferentes para las expresiones int sin signo, por lo que podemos utilizar un cambio de tipo para suprimir lo que se está copiando desde la izquierda: 

    int x = -16;                            // Binario: 1111111111110000    
    int y = sin signo (x) >> 3;      // Binario: 00011111111111100

 Si usted tiene cuidado de evitar la extensión de signo, se puede utilizar el operador de desplazamiento a la derecha >> como una manera de dividir por potencias de 2. Por ejemplo:

    int x = 1000;     int y = x >> 3; // División entera de 1000 por 8, haciendo que y = 125.

Operadores de Asignación

A menudo, en la programación, queremos operar en el valor de una variable x, y almacenar el valor modificado en x. En la mayoría de los lenguajes de programación, por ejemplo, puede aumentar el valor de una variable x por 7 usando el siguiente código:
 
    x = x + 7; // Incremento de x en 7

Debido a que este tipo de cosas ocurre tan frecuentemente en la programación, C ++ proporciona una notación abreviada en forma de operadores de asignación especializados. El fragmento de código anterior puede escribirse más concisa como:
 
    x + = 7; // Incremento de x en 7

Resulta que los operadores AND bit a bit, OR bit a bit, desplazamiento a la izquierda, y desplazamiento a la derecha, todos tienen operadores de asignación abreviados. Aquí hay un ejemplo:
 
    int x = 1;      // Binario: 0000000000000001    
    x << x = 3;  // Binario: 0000000000001000    
    x | = 3;        // Binario: 0000000000001011 -  porque 11 es 3 en binario      
    x & = 1;       // Binario: 0000000000000001    
    x ^ = 4;        // Binario: 0000000000000001 - cambia usando la máscara binaria 100     
    x ^ = 4;        // Binario: 0000000000000101 - cambia de nuevo usando la máscara binaria 100

No hay ningún operador de asignación abreviado para el operador NOT bit a bit

Si desea cambiar todos los bits de x, lo que necesita hacer es esto:

x = ~ x; // Cambiar todos los bits de X y almacenar de nuevo en x

Precaución: los operadores de bits frente a los operadores booleanos

Es muy fácil confundir los operadores de bits en C ++ con los operadores booleanos. Por ejemplo, el operador AND & bit a bit no es el mismo que el operador booleano AND &&, por dos razones:
  • Estos no calculan los números de la misma manera. & bit a bit funciona de manera independiente en cada bit de sus operandos, mientras que && convierte sus dos operandos a un valor booleano ( true == 1 o false == 0), entonces devuelve un único valor true o false. Por ejemplo, 4& 2 == 0ya que 4 es 100 en binario y 2 es 010 en binario, y ninguno de los bits son 1 en ambos enteros. Sin embargo, 4 && 2  = = true y true, numéricamente es igual a 1 . Esto se debe a 4 no es 0, y 2 no es 0, por lo tanto se consideran como valores booleanos.
  • En los operadores de bits siempre se evalúan ambos operandos, mientras que en los operadores booleanos se usa la denominada evaluación de cortocircuito (short-cut). Esto es importante sólo si operandos tienen efectos secundarios, tales como causar que se produzca salida o modificación del valor de otra cosa en la memoria. Aquí hay un ejemplo de cómo dos líneas de aspecto similar de código pueden tener un comportamiento muy diferente:
    int fred (int x)     
    {        
      Serial.print ( "fred");        
      Serial.println (x,  DEC);          
      return x ;    
    }
    void setup ()    
    {        
      Serial.begin (9600);    
    }
    void loop ()
    {       
      delay (1000);  // Espera de 1 segundo, para no saturar los datos serie!               
      int x = fred (0) & fred (1);
    }

1 comentario: