Dúvida com bitwise e bitshift

9 respostas
tiagomac

Pessoal,

me tirem uma dúvida, estou com um 2 métodos que fazem o seguinte processo: 1 método converte um array de bytes para um Long e o outro converte de um Long para um array de bytes, até o momento o método tem funcionado Ok, no entanto apresentou um problema na hora da conversão quando for um número negativo, ou seja, ele só funciona bem quando é número positivo, já olhei, reolhei e não consigo achar a falha, se alguém poder ajudar, dando uma luz… segue os métodos:

byteArrayToLong:

/**
	 * Converts from byte array to long.
	 * @param b - byte array
	 * @param size - the length
	 * @return
	 */
	public static long byteArrayToLong(byte[] b, Integer size, boolean autoLength){
		int length = 0;
		if (autoLength){
			length = b.length;
		}else{
			length = size;
		}
		long ourLong = 0;
		for (int i = 0; i < length; i++){
			ourLong |= b[i] & 0xFF;
			if (i+1 < length)	ourLong <<= 8;
		}
		return ourLong;
	}

longToByteArray:

/**
	 * Converts long in byte array with the given size.
	 * @param l - the long value
	 * @param size - the size (length) to array.
	 * @return a byte[].
	 */
	public static byte[] longToByteArray(long l, int size){
		byte[] bytes = new byte[size];
		int p = 0;
		for (int i = size-1; i >= 0; i--){
			bytes[i] = (byte) ((l >> p) & 0x0ff);
			p += 8;
		}
		return bytes;
	}

Um exemplo de execução com número positivo e saída ok:

Long ourLong = 65000l;
	byte[] inBytes = Utils.longToByteArray(ourLong, 2);
	Long ourLongRecovered = Utils.byteArrayToLong(inBytes, null, true);
	System.out.println("ourLong: "+ourLong+" ourLongRecovered: "+ourLongRecovered);
// ourLong: 65000 ourLongRecovered: 65000

Agora com número negativo que dá erro:

Long ourLong = -65000l;
		byte[] inBytes = Utils.longToByteArray(ourLong, 2);
		Long ourLongRecovered = Utils.byteArrayToLong(inBytes, null, true);
		System.out.println("ourLong: "+ourLong+" ourLongRecovered: "+ourLongRecovered);
// ourLong: -65000 ourLongRecovered: 536

9 Respostas

tiagomac

pessoal, apenas adicionando aqui, não é 650001 não, é 65000L (no final é L, para transformar em Long), só pra ficar mais visível pra não confundir.

ViniGodoy

O bit de sinal corresponderá ao bit mais significativo do seu long. Portanto, estará no 8 byte. Você está serializando apenas alguns bytes, portanto, não o bit de sinal, ele não entra na conta. 65.000 é serializado corretamente pois cabe num unsigned short (é menor que 2^16-1, que é 65535).

Lembre-se também que números negativos trabalham por complemento de 2.

O número -65.000 é representado em binário por:
1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 0000 0010 0001 1000

Se você quiser um formato mais compacto, terá que alterar a forma como armazena longs.

Aproveitei que estava revisando seus métodos e os simplifiquei. Ginásticas com bits já são suficientemente complicadas, portanto, tente deixar as operações o mais explícitas e simples possíveis:

public static long byteArrayToLong(byte[] b){
      return byteArrayToLong(b, b.length);
   }

   /** Converts from byte array to long.
   * @param b - byte array
   * @param size - the length
   * @return
   */
   public static long byteArrayToLong(byte[] b, int size){
      long ourLong = 0;
      for (int i = 0; i &lt; size; i++){
         ourLong = (ourLong &lt;&lt; 8) + (b[i] & 0xFF);
      }
      return ourLong;
   }

   public static byte[] longToByteArray(long number, int size){
      byte[] bytes = new byte[size];
      for (int i = 0; i &lt; size; i++){
         bytes[size-i-1] = (byte)(number & 0xFF);
         number &gt;&gt;= 8;
      }
      return bytes;
   }
}

Para funcionar com os negativos, use 8 no tamanho.
Também simplifiquei a assinatura das duas funções, evitando passar o null mágico. Esse estilo, com mais parâmetros e menos funções, é adotado pela MS. Eu particularmente sigo as convenções do java quando estou programando em Java (mais funções, menos parâmetros de flags).

tiagomac

Vini, valeuzão pela força, rodei aqui e foi, mas como vc disse eu tive que transformar o em um array de 8 bytes, no caso na aplicação que estou rodando esse valor é super variável, pode ser um array de 1,2,3…15, sendo que o tamanho que vou precisar para armazenar o long eu informo no código, na chamada do método… ai eu fiquei só com esse problema…

Eu vou agora mexer no método para ele executar como se fosse de 8 e no final tentar pegar apenas o valor que informo por parâmetro que é variável (ex. se for 2 retornar bytes[0] e bytes[1]), vou fazer aqui pra tentar, se tiver mais alguma dica quanto a isso pode passar que é bem vinda, to quebrando a cabeça desde cedo com esses bitwise/shift :O. Um forte abraço.

ViniGodoy

E como vc sabe que os 2 bytes armazenados representam um long e não um short?

tiagomac

Deixa eu tentar entender, pra o compilador o tamanho de um Long e um Short é apenas o tamanho de memória que eles ocupam, certo? então o valor 65000 em binário seria:
0000000000000000000000000000000000000000000000000111110101101000

Mas se eu por exemplo desse o valor:
0111110101101000

poderia também dizer que são 65000? certo?

esse mesmo valor só que negativo (-65000) seria a inversão do primeiro valor onde todos os 0 viram 1 e todos os 1 viram 0 ? ficaria:
1111 1111 1111 1111 0000 0010 0001 1000 (em 4bytes)

então o valor “0000 0010 0001 1000” não já seria o negativo de 65000 em 2 bytes?

A

Oi gente, como vai?

Apenas curiosidade, para que eu iria querer usar um desses dois métodos?

Abraço,
André AS

ViniGodoy

É isso mesmo tiagomac.

Uma forma comum de “compressão” de tipos inteiros sem sinal é a seguinte:

Coloque o byte menos significativo.
Se seu bit mais significativo for 1, passe para o próximo byte.
Repita o processo até que o bit mais significativo seja 0, ou o tamanho máximo do maior tipo numérico seja atingido.

Nesse caso, todos os dados são gravados e lidos como unsigned int (ou long, no seu caso), embora sua representação no protocolo possa ser menor de acordo com seu valor.

Para campos com sinal, pode-se usar um bit em algum lugar para representar o sinal, e o número sem sinal ser transmitido usando a compactação acima.

ViniGodoy

Ou André. Esses métodos permitem que você grave somente a quantidade necessária de bytes para representar um determinado valor numérico. Isso otimiza canais de rede, ou pode reduzir o tamanho de arquivos. É especialmente importante caso seu arquivo tenha muitos valores.

tiagomac

andredecotia:
Oi gente, como vai?

Apenas curiosidade, para que eu iria querer usar um desses dois métodos?

Abraço,
André AS

Pra um protocolo de comunicação por exemplo…

Criado 25 de fevereiro de 2011
Ultima resposta 25 de fev. de 2011
Respostas 9
Participantes 3