¿Qué son los operadores de desplazamiento de bits (bit-shift) y cómo funcionan?

He estado intentando aprender C en mi tiempo libre, y otros lenguajes (C#, Java, etc.) tienen el mismo concepto (y a menudo los mismos operadores) ...

Lo que me pregunto es, a un nivel básico, ¿qué hace el cambio de bits (<<, >>, >>), qué problemas puede ayudar a resolver, y qué gotchas acechan a la vuelta de la esquina? En otras palabras, una guía para principiantes absolutos sobre el cambio de bits en toda su bondad.

Solución

Los operadores de desplazamiento de bits hacen exactamente lo que su nombre indica. Cambian los bits. He aquí una breve (o no tan breve) introducción a los diferentes operadores de desplazamiento.

Los operadores

  • >> es el operador aritmético (o con signo) de desplazamiento a la derecha.
  • >>> es el operador lógico (o sin signo) de desplazamiento a la derecha.
  • `)

El desplazamiento aritmético a la derecha es exactamente como el desplazamiento lógico a la derecha, excepto que en lugar de rellenar con cero, rellena con el bit más significativo. Esto se debe a que el bit más significativo es el bit de signo, o el bit que distingue los números positivos y negativos. Al rellenar con el bit más significativo, el desplazamiento aritmético a la derecha preserva el signo.

Por ejemplo, si interpretamos este patrón de bits como un número negativo

10000000 00000000 00000000 01100000

tenemos el número -2,147,483,552. Desplazando esto a la derecha 4 posiciones con el desplazamiento aritmético (-2,147,483,552 >> 4) nos daría:

11111000 00000000 00000000 00000110

o el número -134.217.722.

Así que vemos que hemos preservado el signo de nuestros números negativos mediante el uso del desplazamiento aritmético a la derecha, en lugar del desplazamiento lógico a la derecha. Y una vez más, vemos que estamos realizando la división por potencias de 2.

Comentarios (22)

Digamos que tenemos un solo byte:

0110110

Aplicando un único desplazamiento de bits a la izquierda obtenemos:

1101100

El cero más a la izquierda fue desplazado fuera del byte, y un nuevo cero fue añadido al extremo derecho del byte.

Los bits no se desplazan; se descartan. Esto significa que si se desplaza a la izquierda 1101100 y luego se desplaza a la derecha, no se obtendrá el mismo resultado.

Desplazar a la izquierda por N es equivalente a multiplicar por 2N.

Desplazar a la derecha por N es (si está usando [complemento a unos'][1]) el equivalente a dividir por 2N y redondear a cero.

El desplazamiento de bits se puede utilizar para multiplicar y dividir a una velocidad increíble, siempre que se trabaje con una potencia de 2. Casi todas las rutinas gráficas de bajo nivel utilizan el desplazamiento de bits.

Por ejemplo, en los viejos tiempos, usábamos el modo 13h (320x200 256 colores) para los juegos. En el modo 13h, la memoria de vídeo se distribuía secuencialmente por píxeles. Eso significaba que para calcular la ubicación de un píxel, se utilizaba la siguiente matemática:

memoryOffset = (row * 320) + column

Ahora bien, en esa época, la velocidad era crítica, por lo que se utilizaba el desplazamiento de bits para realizar esta operación.

Sin embargo, 320 no es una potencia de dos, así que para evitarlo tenemos que averiguar cuál es la potencia de dos que sumada da 320:

(row * 320) = (row * 256) + (row * 64)

Ahora podemos convertirlo en desplazamientos a la izquierda:


(row * 320) = (row 
Comentarios (6)

Un inconveniente es que lo siguiente depende de la implementación (según la norma ANSI):

char x = -1;
x >> 1;

x puede ser ahora 127 (01111111) o todavía -1 (11111111).

En la práctica, suele ser esto último.

Comentarios (3)