Pegue um cafézinho, vamos conversar
Tropecei na conversão e continuei (~andando~) programando
Durante meu dia a dia no trabalho, é recorrente trabalhar com conjuntos de dados em bytes. Em um desses dias, armazenei um número inteiro "modelando-o" (casting) em uma variável que esperava receber um byte, semelhantemente ao exemplo da Imagem 01.
Quando fui verificar o valor contido nessa variável, percebi o valor negativo, convertido e armazenado nela.
A princípio, não entendi a razão disso.
Depois de pesquisar, descobri que, internamente, o Java implementa o algoritmo do Complemento de Dois para alguns tipos primitivos de dados, como o de bytes.
Resumidamente, a função dele é converter um número inteiro positivo para outro número inteiro negativo, mantendo fixo o comprimento máximo de sua representação binária.
Acabou o cafézinho, vamos a obra
Definição do algoritmo original do Complemento de Dois
Antes de entender como o Java está implementando o algoritmo, vamos conhecer um pouquinho da definição do algoritmo original, no trecho abaixo.
"Two’s complement is a mathematical operation to reversibly convert a positive binary number into a negative binary number with equivalent negative value, using the binary digit with the greatest place value as the sign to indicate whether the binary number is positive or negative.
It is used in computer science as the most common method of representing signed (positive, negative, and zero) integers on computers, and more generally, fixed point binary values.
When the most significant bit is 1, the number is signed as negative; and when the most significant bit is 0 the number is signed as positive."
Se você não entendeu a definição do algoritmo, não se preocupe. Vamos ver a estrutura lógica, detalhada abaixo.
Lógica do algoritmo original do Complemento de Dois
Observação: Vamos considerar o nome “decimalToParser” para o número inteiro.
O decimalToParser deve ser inteiro (positivo ou negativo);
-
Utilize a Equação 01:
Obtenha o valor binário do resultado da Equação 01;
Inverta o valor binário do resultado;
Identifique o bit mais significativo (MSB) do valor binário invertido. O bit mais significativo está posicionado na extremidade esquerda;
-
Verificar o valor do bit mais significativo:
- Se for zero (“0”), o valor final do segundo complemento deve ser positivo;
- Se for zero (“1”), o valor final do segundo complemento deve ser negativo;
Parâmetros da Equação 01:
- secondComplement é o valor calculado do segundo complemento;
- 2 é o número indicativo de um sistema binário;
- n é o número indicativo do tamanho de bits (explicado abaixo);
Observação: O valor de decimalToParser está em módulo na equação 01.
Ainda não entendeu?
Não se preocupe, eu trouxe exemplos abaixo 😂 😂 😂 😂
Algoritmo original: Exemplo da lógica de cálculo
Utilizando o valor (207 em decimal) que foi convertido (-49 em bytes) na Imagem 01, acompanhe a demonstração de cálculo do algoritmo em seis etapas, conforme mencionado acima, na Imagem 02 abaixo.
Algoritmo original: Exemplo de implementação de cálculo no Java
Utilizando a mesma estrutura da demonstração da Imagem 02, acompanhe abaixo a implementação em Java.
Se você quiser contribuir com o código acima no Gist, acesse:
TwosComplement.java
A implementação do algoritmo utilizada no Java
Definição do tipo de dados primitivo de byte
Primeiramente, é necessário entender a estrutura de dados primitivos, por isso considere a definição de byte na documentação do Java:
“byte: The byte data type is an 8-bit signed two's complement integer. It has a minimum value of -128 and a maximum value of 127 (inclusive).
The byte data type can be useful for saving memory in large arrays, where the memory savings actually matters.
They can also be used in place of int where their limits help to clarify your code; the fact that a variable's range is limited can serve as a form of documentation."
Para ficar mais fácil de perceber a diferença, podemos resumir a definição acima comparando os tamanhos de tipos de dados mencionados:
Então, como diz no trecho citado da documentação, o objetivo da utilização do tipo de dados de byte é para economizar a memória interna disponível no hardware. Além disso, limitar a largura de bits dos dados implica em aumentar a performance do processamento ou de cálculos em baixo nível.
Algoritmo implementado no java: Exemplo em código
Se você quiser contribuir com o código acima no Gist, acesse:
JavaTwosComplement.java
Exemplo executável
A vida do dev não se resume a teoria, não é mesmo? 😂 😂 😂 😂
Por isso, eu também trouxe um exemplo executável para simular a estrutura explicada acima, dos dois algoritmos.
Se você quiser contribuir com o código acima, basta clicar em "Open on Replit".
Tá, mas qual é a vantagem de utilizar esse algoritmo?
1. Padronização para menor consumo de dados
Conforme foi explicado anteriormente, o Java define que o tipo primitivo byte possui o tamanho de oito bits, por padrão.
Ou seja, quando necessários, os cálculos a serem realizados com esses dados serão mais simples de serem realizados e exigirão bem pouco do hardware.
2. Soma e subtração sem verificar o sinal dos operandos
O algoritmo original do Complemento de Dois considera que todo número positivo será representado por um número negativo e vice-versa. Esse número negativo corresponde a outro decimal calculado pela Equação 01.
Considerando a afirmação acima do algoritmo original, a Imagem 04 abaixo mostra o exemplo do cálculo entre um número positivo e outro negativo.
Para realizar esse cálculo, não é necessário verificar o sinal de cada operando. Basta apenas encontrar o complemento de dois do número negativo e realizar a soma com o valor binário do número positivo, considerando as regras de soma binária.
O algoritmo ainda diz que a soma de um valor binário de um número decimal com o binário do seu complemento de dois deve resultar em zero. E essa verificação é demonstrada no item 1.2 da Imagem 04.
Fontes
02 - Java makes use of the "two's complement" for representing signed numbers | by Jean Villete ;
03 - Two's complement solution very detailed explanation | by kapkan ;
Top comments (2)
Gostei muito do detalhamento no seu artigo.
Contudo, há algumas afirmações que eu acho que podem induzir ao erro.
Vc diz:
e
Mas a verdade é que todos os valores integrais são representados usando o complemento de dois, o que inclui byte, short, int e long.
Com isso em mente, a explicação para o por que 207 é representado como -49 quando convertido para byte é bem mais simples.
Ao realizar o casting para byte, o Java simplesmente trunca a forma binária de 207 deixando apenas os 8 bytes menos significativos.
207 em binário é 11001111 ou, considerando toda a extensão de 32 bits, 00000000000000000000000011001111.
Quando vc faz o casting para byte, ele pega apenas os bytes 11001111 e, como o byte mais à esquerda é 1, sempre que ele faz uma operação que envolva números integrais, ele considera que todos os outros 24 bytes à esquerda são 1, 11111111111111111111111111001111.
Eu usei este código para fazer esta observação:
Muito obrigado pela sua contribuição!
Você tem razão.