Ignorancia de operaciones sobre bits

Bueno, primero que nada espero no causar una ofensa o un dolor en los ojos para los usuarios más experimentados de ésta comunidad.

En todo lo que he trabajado nunca he necesitado el uso de operaciones sobre bits, una vez vi que un compañero de trabajo (ingeniero en electrónica) usaba mucho operaciones sobre bits y me quedó la curiosidad.

Últimamente leyendo un poco sobre Scala, en el libro: "Programming in Scala", vi que explicaban sobre el uso de operaciones sobre bits (bitwise operations).

Me gustaría saber que tan necesario es para un programador en general, si existe un documento/artículo por la red sobre como utilizar los bits en una aplicación. En fin, para comprender (creo que este tema es BÁSICO para cualquier programador, ya que si manejamos bits, pero a un nivel no tan bajo).

Espero y me puedan ayudar.

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.

Es muy útil, pero yo por

Es muy útil, pero yo por ejemplo nunca lo he usado ( en realidad, básicamente porque no sé usarlo tampoco y no tanto porque no haya habido oportunidad )

Básicamente los operadores de manipulación de bits son <<,>>,>>> y la idea es muy sencilla, si tienes un número entero, este número tiene una representación binaria ( y ahora con Java 7 es más fácil ver esto ) y cuando le aplicas alguno de estos operadores los bits se recorren a la derecha o izq según sea.

Ejemplo 010 ( número 2 ) al aplicarle >> daría 1 ( 001 ) porque el 1 cambia de posición una a la der.

Acá va un ejemplo:

$ cat Test.java
class Test {
    public static void main( String ... args ) {
         int a = 0b11111111111111111111111111111110; //-2
         printBinary( "-2 ", a );
         printBinary( ">> ",  a  >> 1 );
         printBinary( ">>>", a  >>> 1 );
         printBinary( "<< ", a  << 1 );
    }
    private static void printBinary( String s , int n ) {
        System.out.printf( "%s: %16d = %32s %n", s, n , Integer.toBinaryString( n ) );
    }
}
$ javac Test.java
$ java Test
-2 :               -2 = 11111111111111111111111111111110
>> :               -1 = 11111111111111111111111111111111
>>>:       2147483647 =  1111111111111111111111111111111
<< :               -4 = 11111111111111111111111111111100

Con negativos se ve más claramente.

Esto viene de C directamente y se usa mucho ahí en vez de hacer divisiones o sumas y operaciones aritméticas porque se considera más rápido, sobre todo cuando se usa para sistemas embebidos y demás. Esta percepción de rapidez se quedo en Java pero la diferencia es insignificante, aún así mucha gente los usa, sobr etodo porque es más breve escribir código con él.

Imagen de ezamudio

operadores de bits

No nada más son los shifters. También tienes los operadores bitwise AND, OR, NOT y XOR:

1&1==1
1|1==1
1^1==0
1^0==1
1&0==0
1|0==0
~1 (bitwise-NOT, invierte cada bit, si tienes 00000001 entonces ~00000001=11111110)

En Java tienes los shifters que ya explicó Oscar, sólo hay 3 porque no implementaron el <<< porque no hay enteros sin signo en Java.

Usos comunes de los shifters y de los operadores que menciono aquí son en comunicaciones en red, conversiones entre tipos de datos, compresión, encripción, entre otros. Yo por ejemplo uso shifters y bitwise-AND para convertir un entero a un arreglo de bytes y viceversa. El bitwise-XOR es muy utilizado en encripción, aunque en Java no necesitas usarlo directamente a menos que estés tratando de descifrar datos sin tener la llave o cosas así medio mafufas.

Hay otro uso que ya no es tan común hoy porque todo mundo prefiere declarar un montón de booleans al fin que la RAM es infinita, pero antes cuando tenías que escatimar en los bytes que usaba tu programa porque iba a correr en una máquina que sólo tenía unos cuantos KB y no GB, era común que cuando tenías que guardar varios booleans, los metías en una variable numérica sin signo (fuera unsigned char, short, int o long según el número de banderas que necesites). Un boolean a fin de cuentas es un 0 o 1 y entonces en un byte puedes guardar 8 booleans. Y luego tienes máscaras, que son enteros con un solo bit encendido, para hacerle un bitwise-AND con la variable donde tienes los booleans y así saber si está encendida o no esa bandera (o encenderla o apagarla también). Algo así por ejemplo:

public final static int ESCRITURA=1;
public final static int LECTURA=2;
public final static int APPEND=4;
public final static int BORRAR=8;

short modoArchivo=ESCRITURA | LECTURA; //así prendes dos banderas

if (modoArchivo & APPEND > 0) {
 //así compruebas si tiene la bandera encendida
}
modoArchivo |=BORRAR; //así le prendes todavía otra bandera
modoArchivo &= ~ESCRITURA; //así le apagas la bandera de ESCRITURA
modoArchivo ^= LECTURA; //así le cambias esa bandera; si estaba encendida la apagas pero si estaba apagada la enciendes

Esto por ejemplo lo puedes ver en SelectionKey de java.nio; hay 4 banderas para indicar las operaciones que trae un selector, entonces le pides a la SelectionKey readyOps() que te da un int y eso lo AND-eas con las banderas que te interesan. Pero el diseñador de la clase también pensó en todos los pobrecitos programadores que no saben lo que es un bit, y entonces agregó 4 métodos que devuelven boolean y que internamente AND-ean una de las banderas para indicar si están encendidas o no.

Otra área donde se usan muchos los operadores bitwise es en graficación y animación, manipulación de imágenes, etc. Cuando manejas sprites directamente y cosas así, haces muchas operaciones de bits. También en manejo de eventos: la clase AWTEvent tiene un montón de máscaras para que puedas determinar qué tipo de evento recibes, haciendo la comparación con el ID del mismo. Por qué es común su uso en estos casos? Pues porque es mucho más rápido operar directamente sobre bits, que estar sumando o restando. Por ejemplo 1+4 es más lento que 1|4. Incluso si tienes un boolean... qué código te gusta más?

boolean bandera = false;

public void flipBandera() {
  //esta es una manera
  bandera ^= true;
  //esta es otra, por ser boolean
  bandera = !bandera;
  //y esta es la mas chorera, bueno nomás faltaría que la condición fuera bandera==true
  if (bandera) {
    bandera = false;
  } else {
    bandera = true;
  }
}

Imagen de ezamudio

shifts

Ejemplo de la vida real: esto es algo que hago comúnmente, para comunicación usando j8583:

//creamos un mensaje y lo convertimos a un arreglo de bytes
IsoMessage m = bla;
byte[] buf = m.writeData();
//Ahora necesitamos generar un encabezado con la longitud del mensaje expresada como 16 bits sin signo
byte[] hlen = new byte[2];
hlen[0] = (byte)((buf.length & 0xff00) >> 8);
hlen[1] = (byte)(buf.length & 0xff);
outputStream.write(hlen);
outputStream.write(buf);

Qué hace ese código? Toma el segundo byte menos significativo de la longitud de la trama (largo & 0xff00) y lo recorre un byte a la derecha (>> 8). Y luego toma el byte menos significativo. Ahora tengo un número de 16 bits en un arreglo de 2 bytes. Del otro lado del socket, se decodifica de este modo:

byte[] buf=new byte[2];
inputStream.read(buf);
int largo = ((buf[0] & 0xff) << 8) | (buf[1] & 0xff);
buf = new byte[largo];
inputStream.read(buf);

Es decir, leo dos bytes, luego el primer byte lo convierto a un entero sin signo (& 0xff) porque siendo byte podría traer signo negativo y la conversión a entero va a encender un bit en el byte más significativo. Luego recorro los bits 8 posiciones a la izquierda, y le hago un bitwise-OR con el segundo byte (también pasándolo a positivo).

Nunca te habías preguntado por qué InputStream.read() devuelve un int y no un byte? Pues porque ese int va a valer entre 0 y 255 si es que se leyó un byte, o va a valer -1 si no se pudo leer nada. Con un byte no podrías saber si se leyó o no (solamente que se arroje una excepción, y efectivamente el método declara que throws IOException pero en la práctica ocurre que ese método a veces devuelve -1).

Imagen de neko069

Órale, ése tema yo sólo lo

Órale, ése tema yo sólo lo había tocado 2 veces, una en el Conalep, en una materia que se llamaba "Compuertas lógicas y algebra booleana" y otra en algún libro de Java, de cuyo nombre no recuerdo... pero venía una sección, que leí, pero ya no repasé, gracias una vez más @ezamudio por compartir temas como éste..

Imagen de bferro

Frase célebre

There are 10 kinds of people in the world, those who understand binary and those who don't

Imagen de bferro

Operaciones bitwise: DataInputStream y DataOutputStream

Los métodos para lectura y escritura formateada de los decoradores DataInputStream y DataOutputStream respectivamente hacen uso extensivo de las operaciones bitwise para programar su funcionalidad.
Por ejemplo el método readInt de DataInputStream, necesita interpretar los 4 bytes que lee del flujo decorado como un entero y el método writeInt tiene que hacer lo contrario.
El código de DataInputStream.readInt():

public final int readInt() throws IOException {
        int ch1 = in.read();
        int ch2 = in.read();
        int ch3 = in.read();
        int ch4 = in.read();
        if ((ch1 | ch2 | ch3 | ch4) < 0)
            throw new EOFException();
        return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
    }

El código DataOutputStream.writeInt(int):

   public final void writeInt(int v) throws IOException {
        out.write((v >>> 24) & 0xFF);
        out.write((v >>> 16) & 0xFF);
        out.write((v >>>  8) & 0xFF);
        out.write((v >>>  0) & 0xFF);
        incCount(4);
    }
Imagen de bferro

El método InputStream.read() dispara IOException por otra cosa

El método InputStream.read() siempre devuelve -1 si se llega al fin del flujo. La excepción IOException la dispara si la lectura no se puede realizar por otras razones.

Java decidió que el fin del flujo para los flujos orientados a bytes no se indicara con EOFException.

Gracias

Wow!...Es interesante este tipo de temas que, quizás no son muy usados en nuestro día a día (yo por ejemplo, nunca había tenido la necesidad de usarlos), pero que vaya que nos ayudan a más de uno a optimizar algunos trozos de código y pues a mí que siempre me había llamado la atención esas ondas de los sistemas empotrados, que ya veo que para quienes hacen ese tipo de sistemas es el pan de cada día.

@OscarRyz, una explicación sencilla y muy entendible (sobretodo para mí, uno de los llamados "pobrecitos programadores", de los que se refiere @ezamudio).

@ezamudio, me gustó que nos compartieras un uso que tú le das a las operaciones sobre bits. Muy ilustrativo (y que chido código tiene j8583; algún día hago checkout del código para aprender algo más).

@bferro, Dr. cómo siempre saliendo con cosas que muchos usamos pero que (o por desidia o por falta de curiosidad) nunca hemos dado el vistazo; es interesante ver cosas que para nosotros son un simple "punto método" en realidad tienen muchísimas cosas detrás (bueno, en este caso son 5 y 7 líneas).

Tema, medio comprendido, debo leer al respecto.

Imagen de rugi

El comentario del Dr. me hace

El comentario del Dr. me hace recordar un tema importantísimo (que seguramente desviaría este hilo), pero que tiene que ver con la forma que a veces aprendemos las cosas, y que influye -por obvias razones- en la manera en que resolvemos los problemas.

Hace mucho en algún proyecto, teníamos un problema de integración con una herramienta por demás carísima; ese tipo de herramientas que te permiten (eso sí debemos aceptarlo) acelerar la integración de ciertos componentes dentro de un escenario empresarial.

El problema consistía en que, cuando se solicitaba una imagen desde una base de datos, por alguna razón (desconocida hasta ese momento), los logs indicaban que la imagen estaba ya cargada pero, la herramienta se quejaba de que, "la imagen estaba en un estado inadecuado", después de probar desde otras "clases-clientes" comprobamos que la imagen se encontraba completa o al menos, no importa que otro método usábamos para visualizarla, éste era satisfactorio, el equipo de soporte pagado de la herramienta indicaba como solución: volver a cargar nuevamente las imágenes pero, en esta ocasión usando la misma herramienta (petición no factible ya que dichas imágenes tenían como fuente diferentes puntos de entrada-¿sino dónde estaría la integración? ;) -

Antes de llegar a esta solución última al equipo de integración se nos ocurrío probar una última opción,sobreescribimos el componente de la herramienta y únicamente le agregamos (justamente por que recordamos lo que el Dr. menciona) un -1 al stream que recibiamos.

Todo funcionó a la perfección.

Dr. siempre un gusto leerlo.

¡¡¡ Saludos !!!
---
RuGI

Imagen de ezamudio

Tipos de personas

Me recordaron de los 02 tipos de personas...

Imagen de Sr. Negativo

Operaciones sobre bits

"En todo lo que he trabajado nunca he necesitado el uso de operaciones sobre bits, una vez vi que un compañero de trabajo (ingeniero en electrónica) usaba mucho operaciones sobre bits y me quedó la curiosidad.

Últimamente leyendo un poco sobre Scala, en el libro: "Programming in Scala", vi que explicaban sobre el uso de operaciones sobre bits (bitwise operations).

Me gustaría saber que tan necesario es para un programador en general, si existe un documento/artículo por la red sobre como utilizar los bits en una aplicación. En fin, para comprender (creo que este tema es BÁSICO para cualquier programador, ya que si manejamos bits, pero a un nivel no tan bajo)."

Aprovechando el tiempo sanamente estoy leyendo varios post interesantes (ya sé que son del año pasado).

Las operaciones sobre bits las vi en clases de electrónica (para la simulación de compuertas lógicas,puerto serial y esas cosas) usando el odiado VB.