Complemento 1 e a mudança do ponto “zero”

“Complemento 2” não é a única forma de “fazer de conta” que um valor inteiro, do conjunto dos números naturais, tem sinal. De fato, a primeira tentativa de usar “sinais” em aritmética binária foi, ao observar que o bit MSB poderia ser usado como “sinal”, usar o inverso dos bits para especificar o valor sinalizado. Em oposição à noção de “complemento 2”, isso ficou conhecido como “complemento 1″… Eis um exemplo de como funciona:

Se, com 4 bits (por exemplo), 0b0001 equivale ao valor +1, em decimal, invertendo os bits obteríamos 0b1110 que, tendo o MSB setado, “representa” um valor negativo do número natural 1.

Rapidamente essa maneira demonstrou-se ruim por dois motivos:

  1. Ao fazer a soma de +1 com -1 obtém-se 0b1111 (0b0001 + 0b1110);
  2. Ao inverter todos os bits de zero (0b0000), obtém-se 0b1111.

O segundo fato implica que temos dois valores “zero”: +0 e -0. Um com o MSB zerado e outro com o MSB setado. O que é meio estranho e, ainda, 4 bits nos daria a sequência sinalizada simétrica de -7 até +7, mas com dois “zeros” e criaria um problema: Ao subtrair 1 de +0 teríamos -0, ao invés de -1 e, de forma similar, ao adicionar 1 a -0 obteríamos +0.

Assim, o “complemento 1” foi abandonado e só existe para fins lógicos, com valores não sinalizados.

Retornando a faixa dos valores não sinalizados de 4 bits, outra forma de “fazer de conta” que temos sinal é mudando o ponto onde o valor zero se encontra. Note que, com 4 bits, temos valores de 0b0000 até 0b1111, sem sinal. ou seja, 16 combinações binárias que podem ser divididas em dois subconjuntos com 8 elementos cada. O que acontece se dissermos que 0b0111 é o novo “zero”? Ou seja, se v=N-V_{bias}, onde V_{bias} é o valor do meio da faixa (no caso, 7) e N seja o valor inteiro sem sinal que resultará num valor v com sinal. Neste caso, se N=0 obteremos -7, mas se N=15 (valor máximo sem sinal com 4 bits), obteremos 8.

Só para constar: bias, em inglês, é “polarização”. A ideia é que ao adotar esse novo valor “centralizador” estamos dividindo a faixa dos valores inteiros sem sinal em dois “polos” (um negativo e um positivo)…

Por que fazemos V_{bias}=7? Ora, porque se N_{max}=15, o meio da faixa é exatamente N=\left\lfloor\frac{N_{max}}{2}\right\rfloor. Note que, desse jeito, a assimetria dos valores extremos é a inversa da que ocorre no “complemento 2” (o valor positivo máximo é maior que o valor negativo mínimo). Fora isso, todo o resto funciona perfeitamente bem… Se subtrairmos 1 de 0 (que agora é 0b0111), obteremos N=6 (sem sinal), que é -1, com sinal (v=6-7=-1). Ou seja, em toda a faixa continuaremos a ter apenas um zero.

Esse esquema é usado no fator de escala de um valor em ponto flutuante (o expoente e, da equação v=(1+f)\cdot2^E), onde E=e-v_{bias} e e é um valor inteiro sem sinal… Como expliquei em outro artigo, f é uma fração do tipo f=\frac{F}{2^k}, onde F e k são inteiros e k depende da precisão (em bits) da parte fracionária.

Neste caso, o valor superior de e (e_{max}) é usado para expressar valores “não numéricos” (NaN e infinitos), tornando a faixa dos expoentes simétrica para todos os outros valores (adotando também um caso especial, quando e=0, é o menor expoente negativo possível (E), correspondendo à uma escala fixa para valores subnormais)…

De fato, podemos usar qualquer valor para V_{bias} que esteja dentro da faixa permissível para a quantidade de bits em questão, causando apenas uma assimetria maior entre os valores positivos e negativos, mas a aritmética continua perfeitamente válida. Se V_{bias}=0, teremos o zero e somente o polo positivo, mas se V_{bias}=15, teremos o polo negativo, onde o maior valor é o zero!

O último aviso, que acredito que tenha ficado óbvio, é que estou usando apenas 4 bits como exemplo aqui para ficar visualmente mais fácil. O número de bits estipula tanto N quanto V_{bias}.