No artigo anterior, aprendemos a somar e subtrair números inteiros como fazemos no papel, e terminamos com uma pergunta: será que o hardware faz o mesmo, pedindo emprestado a bits vizinhos? E se eu te dissesse que o computador, na verdade, não faz a subtração?
Como vimos ao fechar aquele artigo, o problema não é o preço de um circuito subtrator — ele custa praticamente o mesmo que um somador. O que não faz sentido é manter os dois lado a lado fazendo trabalho quase idêntico. Para que um único circuito dê conta do recado, a engenharia da computação desenvolveu um método para transformar subtrações em adições. Em vez de fazer \(A - B\), o computador realiza \(A + (-B)\).
Mas como representamos um número negativo usando apenas zeros e uns? Se só temos dois símbolos, onde colocamos o sinal de “menos”? É aqui que entra o conceito de Complemento de Dois, a base fundamental da aritmética de números inteiros em processadores modernos.
A Ideia do Complemento #
Antes de pensar em bits, vamos analisar o problema na nossa base decimal comum.
Imagine uma calculadora muito simples que possui um display de apenas 4 dígitos.
O maior número que ela consegue mostrar é 9999.
Se somarmos 1 a esse valor (9999 + 1), o resultado matemático seria 10000.
Mas como o display só tem espaço para 4 números, o 1 da esquerda desaparece
(transborda além do limite do display), e o visor mostra apenas 0000.
Agora, imagine o caminho inverso. Se o visor mostra 0000 e tentamos subtrair
1, a calculadora faz o “giro” contrário e exibe 9999.
Nesse sistema limitado, o número 9999 se comporta matematicamente como se
fosse o -1.
Vamos testar? Se somarmos 5 com 9999 (nosso “-1 fake”):
Descartando o primeiro dígito que não cabe no display, sobra 0004. Ou seja,
\(5 + (-1) = 4\). A conta funcionou perfeitamente usando apenas soma!
Isso é o Complemento à Base: usamos os números “grandes” do topo da contagem para representar os negativos.
Vamos agora formalizar essa ideia.
O complemento de um número \(N\) em uma dada base \(b\) é igual à diferença entre a próxima potência de \(b\) e o número — nessa ordem: a potência em cima, o número embaixo.
Complemento à base 10
O complemento à base 10 de um número \(N\) é dado por \(10^d - N\), onde \(d\) é a quantidade de dígitos do sistema. Vamos representar o equivalente a “-734” num sistema de 3 dígitos, o menor que comporta o 734:
$$ \begin{array}{r r r r} 1 & 0 & 0 & 0\\ -{} & 7 & 3 & 4\\ \hline &2 & 6 & 6 \end{array} $$O número 266 é o “complemento” de 734. Somar 266 terá o mesmo efeito que subtrair 734 (ignorando o estouro, ou seja, o “vai-um” que ultrapassa o limite do display).
O tamanho do sistema é uma escolha, e ele precisa ser respeitado do começo ao fim: no display de 4 dígitos do início do artigo, o complemento de 734 seria \(10^4 - 734 = 9266\), e é o 9266 que se comportaria como \(-734\) por lá. O raciocínio é o mesmo; muda só quantas casas o sistema tem. Vamos visualizar isso um pouco mais adiante neste artigo. Antes, precisamos entender como os números negativos são representados em binário.
Números com Sinal em Binário #
No computador, temos uma quantidade fixa de bits (geralmente 8, 16, 32 ou 64). Para diferenciar positivos de negativos, usamos o Bit Mais Significativo (o bit mais à esquerda, chamado de MSB, do inglês “Most Significant Bit”) como o Bit de Sinal.
- 0 no início indica número Positivo.
- 1 no início indica número Negativo.
Até aqui, tudo bem. A pergunta que fica é: o que fazer com os outros bits? Reservar o MSB para o sinal é só metade da decisão, e historicamente houve três respostas diferentes para a outra metade (signed number representations).
| Esquema | Como escreve \(-5\) em 8 bits | Quantos zeros existem | Um mesmo somador serve para somar e subtrair? |
|---|---|---|---|
| Sinal-magnitude | 10000101 (bit de sinal + o 5 de sempre) |
dois: 00000000 e 10000000 |
não |
| Complemento de um | 11111010 (inverte os bits) |
dois: 00000000 e 11111111 |
não, exige um passo extra na soma |
| Complemento de dois | 11111011 (inverte e soma 1) |
um só | sim |
Os dois primeiros são bem mais intuitivos para um humano — sinal-magnitude é
literalmente “escreva o sinal e depois o número de sempre” — e mesmo assim
perderam a disputa. O motivo tem nome: zero duplo. Nos dois esquemas,
\(+0\) e \(-0\) são padrões de bits diferentes que valem exatamente a mesma
coisa. Isso desperdiça uma das combinações disponíveis e, pior, obriga o
hardware a checar dois padrões toda vez que alguém escreve if (x == 0). Some a
isso o circuito adicional que ambos exigem na soma, e a disputa se resolve
sozinha.
O complemento de dois tem um zero só, dispensa qualquer ajuste na hora de somar e é o que praticamente todo processador usa hoje. Ele cobra um preço, que vamos reencontrar adiante ao falar dos limites da representação: sobra uma combinação do lado negativo — justamente aquela que o \(-0\) ocuparia nos outros esquemas.
Por exemplo, em um sistema de 8 bits:
00000101representa+5.11111011representa-5(em complemento de dois).
Por que 11111011 é -5? Vamos descobrir. Usando a notação posicional:
Interpretando números negativos em complemento de 2
Vamos interpretar o número binário 11111011 como um inteiro com sinal (8 bits).
Expandindo:
Calculando os valores:
$$ 128 + 64 + 32 + 16 + 8 + 0 + 2 + 1 = 251 $$Como o bit mais à esquerda é 1, sabemos que é um número negativo. Para encontrar seu valor real, subtraímos \(256\) (que é \(2^8\), a próxima potência de 2):
$$ 251 - 256 = -5 $$
Portanto, 11111011 representa -5 em complemento de dois.
Esse método funciona para qualquer número de bits.
Uma outra forma, é considerar o notação posicional com o bit de sinal negativo:
$$ -1 \times 2^7 + 1 \times 2^6 + 1 \times 2^5 + 1 \times 2^4 + 1 \times 2^3 + 0 \times 2^2 + 1 \times 2^1 + 1 \times 2^0 $$Calculando os valores:
$$ -128 + 64 + 32 + 16 + 8 + 0 + 2 + 1 = -5 $$Ambas as formas levam ao mesmo resultado.
Essa segunda leitura, a do peso negativo, fica bem mais concreta desenhada. Na régua abaixo, os oito bits aparecem com o peso de cada posição em cima e a parcela correspondente embaixo — exatamente como você faria com um número sem sinal. A única diferença está na ponta esquerda: aquele peso vem com sinal trocado, e é só isso que separa \(251\) de \(-5\).

Complemento de Dois (Base 2) #
O complemento à base 2 de um número \(N\) é matematicamente dado por \(2^d - N\).
Complemento à base 2 (Cálculo Matemático Formal)
Vamos encontrar o negativo de \(0101_2\) (5 decimal) em um sistema de 4 bits. A conta é \(2^4 - 5\), ou seja, \(10000_2 - 0101_2\).
Lembre-se: no algoritmo formal já apresentado nessa série de artigos, quando o dígito de cima é menor, somamos a base (2) a ele e adicionamos 1 ao dígito de baixo da próxima coluna.
$$ \begin{array}{r r r r r r} & 1 & 0^{\scriptscriptstyle+2} & 0^{\scriptscriptstyle+2} & 0^{\scriptscriptstyle+2} & 0^{\scriptscriptstyle+2} \\ -& 0^{\scriptscriptstyle+1} & 0^{\scriptscriptstyle+1} & 1^{\scriptscriptstyle+1} & 0^{\scriptscriptstyle+1} & 1 \\ \hline & 0 & 1 & 0 & 1 & 1 \end{array} $$Passo a passo:
- Col 0: \(0 < 1\). Somamos a base ao topo (\(0+2=2\)). \(2-1=\mathbf{1}\). Compensamos (+1) na próx.
- Col 1: Topo \(0\), Baixo \(0+1=1\). \(0 < 1\). Somamos base (\(2\)). \(2-1=\mathbf{1}\). Compensamos.
- Col 2: Topo \(0\), Baixo \(1+1=2\). \(0 < 2\). Somamos base (\(2\)). \(2-2=\mathbf{0}\). Compensamos.
- Col 3: Topo \(0\), Baixo \(0+1=1\). \(0 < 1\). Somamos base (\(2\)). \(2-1=\mathbf{1}\). Compensamos.
- Col 4: Topo \(1\), Baixo \(0+1=1\). \(1-1=\mathbf{0}\).
Resultado: \(1011_2\) (os 4 bits úteis).
Fazer essa subtração é trabalhoso. Felizmente, existe um algoritmo muito mais simples usado pelo hardware e por programadores: a técnica do “Inverte e Soma 1”.
Técnica: Inverte e Soma 1 #
Esta técnica se baseia no Complemento de 1 (apenas inverter os bits) e depois somar 1 para chegar ao Complemento de 2.
Vamos encontrar o complemento de 2 do número 5 (0101) em um sistema de 4 bits:
- Pegue o número positivo:
0101 - Inverta todos os bits (0 vira 1, 1 vira 0):
1010 - Some 1 ao resultado.
Complemento à base 2 (Inverte e Soma 1)
Após inverter os bits de 0101, obtivemos 1010. Agora, o trabalho é apenas somar 1.
O resultado 1011 representa o -5 em Complemento de 2.
Por que somar 1 funciona? #
Você pode estar se perguntando: por que inverter os bits e somar 1 resulta exatamente no complemento matemático (\(2^d - N\), num sistema de \(d\) bits)?
A explicação está na relação entre uma sequência cheia de “uns” e a próxima potência de 2:
- Inverter os bits é matematicamente equivalente a subtrair o número de uma sequência composta apenas por uns (isso é chamado de Complemento de 1).
- Exemplo em 4 bits: Inverter \(x\) é igual a \(1111_2 - x\).
- A definição formal do Complemento de 2 é subtrair da próxima potência da base (\(10000_2\)).
- Ou seja: \(10000_2 - x\).
Como sabemos que \(10000_2\) é exatamente \(1111_2 + 1\), podemos substituir na fórmula:
$$ \underbrace{(10000_2 - x)}_{\text{Definição Formal}} = \underbrace{(1111_2 + 1) - x}_{\text{Substituição}} = \underbrace{(1111_2 - x)}_{\text{Bits Invertidos}} + 1 $$Resumindo: inverter os bits nos leva “quase” lá (até o valor máximo da base menos 1). Adicionar 1 completa a diferença para chegar à potência de 2 correta. É por isso que o algoritmo funciona perfeitamente.
Regra Prática (O Atalho Visual) #
Para humanos, existe um jeito ainda mais rápido de fazer isso “de cabeça”, sem contas:
Para se obter diretamente o complemento a 2 de um número basta percorrer o número da direita para a esquerda, repetindo-se os dígitos zeros até encontrar o primeiro dígito 1, o qual deve ser mantido. A partir daí, todos os dígitos à esquerda desse primeiro 1 deverão ser invertidos.
Exemplos de aplicação da regra:
-
Número: \(011\mathbf{1}00\)
- Mantém o final
100. - Inverte o começo
011\(\to\)100. - Resultado: \(100\mathbf{1}00\)
- Mantém o final
-
Exemplos adicionais:
- \(1010 \to 0110\)
- \(11001 \to 00111\)
- \(111000 \to 001000\)
- \(1100110 \to 0011010\)
O Truque: Subtração via Adição #
Agora que sabemos converter um número para negativo (complemento de 2), a subtração se torna trivial. Para calcular \(A - B\), o computador simplesmente calcula \(A + \text{Complemento}(B)\).
Subtração na base 10 usando complemento à base
Subtração \(913 - 734 = 179\)
O complemento à base 10 de 734 é \(1000 - 734 = 266\). Assim, a subtração \(913 - 734\) pode ser transformada em uma adição: \(913 + 266 = 1179\).
Observe que o resultado final tem um dígito a mais que os números originais (\(1\)). Esse dígito é o “vai-um” que estoura a capacidade da representação. Ele é ignorado para obter o resultado correto (\(179\)).
Subtração na base 2 usando complemento à base
Vamos calcular \(25 - 19\) em binário (usando 5 bits para simplificar). Daí: \(25 = 11001_2\) e \(19 = 10011_2\)
Queremos fazer: \(11001_2 - 10011_2\).
Passo 1: Achar o complemento de 2 de 19 (\(10011\)). Usando a regra prática: mantém o último 1, inverte o resto à esquerda \(\to\) 01101.
Passo 2: Somar 25 com (-19). Transformamos a subtração em adição: \(11001 + 01101\).
Representando verticalmente a operação, temos:
$$ \begin{array}{c c c c c c} {} & \scriptscriptstyle 1 & & & \scriptscriptstyle 1 & \\ {} & 1 & 1 & 0 & 0 & 1 \\ {} + & 0 & 1 & 1 & 0 & 1 \\ \hline 1) & 0 & 0 & 1 & 1 & 0 \end{array} $$O bit “1)” à esquerda é o carry out que sai do número de bits. Ele é descartado. Resultado final: \(00110_2\) (que é 6 em decimal). Correto!
Limites da Representação (Range) #
Antes de falarmos sobre erros de cálculo, precisamos entender os limites do campo de jogo. Como a quantidade de bits é finita, existe um valor máximo e um mínimo que podemos escrever. Se tentarmos passar desses limites, o número “não cabe”.
O intervalo de valores depende de dois fatores:
- Quantos bits (\(N\)) temos disponíveis (8, 16, 32, 64…).
- Se estamos usando números Sem Sinal (Unsigned) ou Com Sinal (Signed).
Números Sem Sinal (Unsigned) #
Aqui, todos os bits são usados para representar a magnitude (o valor) do número. Não existem números negativos.
- Mínimo: É sempre 0 (todos os bits zero).
- Máximo: Todos os bits são 1. A fórmula é \(2^N - 1\).
Exemplo com 8 bits:
- De \(0\) a \(2^8 - 1\)
- De 0 a 255.
Números Com Sinal (Complemento de 2) #
Aqui, dividimos as combinações possíveis pela metade: metade para os positivos (e o zero) e metade para os negativos. O bit mais à esquerda (MSB) define o sinal, restando \(N-1\) bits para o valor.
A fórmula muda ligeiramente:
- Mínimo (Negativo): \(-2^{N-1}\)
- Máximo (Positivo): \(2^{N-1} - 1\)
Por que o máximo é um a menos que o módulo do mínimo? Porque o zero ocupa um lugar no lado dos “positivos” (onde o bit de sinal é 0). Em 8 bits, temos 256 combinações:
- 128 combinações negativas (de -1 a -128).
- 128 combinações não-negativas (0 a +127).
Há uma imagem que arruma tudo o que vimos até aqui num desenho só, e vale a pena
guardá-la. Em vez de pensar os números como uma reta que vai de \(-8\) a
\(+7\), pense neles como um relógio: as combinações de bits dispostas em
círculo, 0000 no topo, somar 1 sendo um passo no sentido horário. Está usando
4 bits na figura porque 16 posições cabem na página; em 8 bits é o mesmo relógio,
só com 256 marcas.

O relógio responde de uma vez a quatro perguntas que este artigo trata em seções separadas:
- Por que o complemento funciona. Andar 5 passos para trás a partir do zero chega no mesmo lugar que andar 11 para a frente, porque o círculo fecha. Por isso subtrair é somar o complemento.
- Por que a faixa é assimétrica. Existe uma marca só para o zero, no topo. Como as 16 posições se dividem em duas metades iguais, sobra uma combinação do lado negativo — e é por isso que vamos de \(-8\) a \(+7\).
- O que é overflow. É atravessar a costura sem querer: somar dois positivos e cair do lado vermelho do círculo.
- Por que \(-8\) não tem oposto. O \(+8\) simplesmente não existe no relógio. A posição onde ele cairia é ocupada pelo próprio \(-8\).
Vamos voltar a cada um desses pontos adiante, mas guarde o desenho.
Tabela de Referência #
Veja como o alcance muda drasticamente dependendo da interpretação:
| Bits (\(N\)) | Tipo | Valor Mínimo | Valor Máximo |
|---|---|---|---|
| 4 bits | Sem Sinal | 0 | 15 |
| 4 bits | Com Sinal | -8 | +7 |
| 8 bits | Sem Sinal | 0 | 255 |
| 8 bits | Com Sinal | -128 | +127 |
| 16 bits | Sem Sinal | 0 | 65.535 |
| 16 bits | Com Sinal | -32.768 | +32.767 |
Assim, se você tem um sistema de 8 bits com sinal e tenta somar \(100 + 30\), o resultado matemático (130) é maior que o limite permitido (+127). O computador não tem bits suficientes para representar esse valor, causando o que chamamos de Overflow. Vamos entender isso melhor mais adiante. Mas antes, um detalhe importante.
O Paradoxo do Mínimo Valor (Um “Gotcha” Perigoso) #
Se você observar a tabela acima com atenção, notará uma assimetria: em 8 bits, vamos de -128 até +127.
Isso cria um problema curioso: o número -128 não tem um oposto positivo. O valor +128 simplesmente não cabe em 8 bits com sinal.
Essa assimetria não é acidente nem descuido de projeto: é o troco do zero único. Como o complemento de dois não gasta um padrão de bits com o \(-0\), sobra uma combinação do lado negativo — e essa combinação é o \(-128\). Nos esquemas que perderam a disputa lá atrás, a faixa era simétrica, mas ao custo de ter dois zeros.
O que acontece se tentarmos aplicar a regra do “Inverte e Soma 1” justamente no
-128 (10000000)? Vamos ver:
- Número original:
10000000(-128) - Inverte os bits:
01111111(+127) - Soma 1:
Resultado: 10000000. Voltamos para o -128!
Isso significa que, computacionalmente, o inverso de -128 é ele mesmo. Esse comportamento causa bugs silenciosos em programação.
Em C e C++, calcular o valor absoluto abs() ou negar -x do menor inteiro
possível é comportamento indefinido: o padrão da linguagem não diz o que
acontece, e o compilador pode otimizar o código assumindo que essa situação
nunca ocorre.
Java não tem comportamento indefinido, mas isso não o salva do problema:
Math.abs(Integer.MIN_VALUE) é especificado e devolve o próprio
Integer.MIN_VALUE — um valor negativo saindo de uma função de valor absoluto.
O bug é o mesmo nos dois casos; a diferença é que num deles ele é
imprevisível e no outro está documentado.
Aqui falamos de 8 bits, mas o mesmo vale para 16, 32 ou 64 bits. Sempre haverá um número negativo sem oposto positivo.
Overflow (Estouro de Capacidade) #
Nos exemplos acima, descartamos o último “vai-um” e o resultado estava certo. Mas nem sempre isso acontece.
O Overflow ocorre quando o resultado de uma operação é grande demais (positiva ou negativamente) para caber no número de bits disponível.
Lembre-se: em um sistema de 8 bits com sinal, podemos representar números de -128 a +127. Se você tentar somar \(100 + 30\), o resultado (130) não cabe, e invadirá o bit de sinal, parecendo um número negativo.
A detecção de overflow segue duas regras. A regra “humana” (lógica) e a regra “técnica” (hardware).
- Regra Humana: Se somarmos dois números Positivos e o resultado for Negativo, houve Overflow. (O mesmo vale para Negativo + Negativo = Positivo).
- Regra Técnica: Ocorre Overflow se o “vai-um” que entra no bit de sinal for diferente do “vai-um” que sai dele.
- Há um vai-um propagado para o bit de sinal sem vai-um saindo deste.
- Há um vai-um propagado pelo bit de sinal sem este ter recebido vai-um.
Escrevendo de outra forma: em complemento a dois, o overflow ocorre se o carry-in (vai-um que entra no bit de sinal) for diferente do carry-out (vai-um que sai do bit de sinal).
Vamos ver isso acontecendo na prática com números de 8 bits:
Overflow - Positivo + Positivo vira Negativo
Vamos somar \(88 + 46\). O resultado matemático seria 134. Mas em 8 bits com sinal, o máximo é 127. Vai dar erro.
Repare no Bit de Sinal (o mais à esquerda, bit 7).
$$ \begin{array}{c c c c c c c c c} {}&\scriptscriptstyle 1 & \scriptscriptstyle 1 & \scriptscriptstyle 1 & \scriptscriptstyle 1 & & & & & \\ {} & 0 & 1 & 0 & 1 & 1 & 0 & 0 & 0 \\ {} + & 0 & 0 & 1 & 0 & 1 & 1 & 1 & 0 \\ \hline {} & \textcolor{red}{1} & 0 & 0 & 0 & 0 & 1 & 1 & 0 \end{array} $$Aqui o \(\textcolor{red}{1}\) destacado é o bit de sinal do resultado: ele está dentro dos 8 bits, na coluna do bit 7.
Análise:
- Olhe o bit de sinal (primeira coluna). Entrou um “vai-um” (recebeu 1 das posições anteriores).
- Mas não saiu nenhum “vai-um” dele para fora da conta.
- Entrou e não saiu? Overflow!
O resultado binário 10000110 começa com 1, indicando um número negativo (-122). Isso é obviamente um erro, pois somamos dois positivos.
Agora vejamos o caso oposto, com dois números negativos estourando o limite inferior (-128).
Overflow - Negativo + Negativo vira Positivo
Vamos fazer \( -76 + (-68) \). O resultado matemático seria -144. O limite inferior é -128. Vai dar erro.
Representamos em complemento de 2 e somamos:
$$ \begin{array}{c c c c c c c c c} \scriptscriptstyle 1 & & \scriptscriptstyle 1 & \scriptscriptstyle 1 & \scriptscriptstyle 1 & \scriptscriptstyle 1 & & & \\ {} & 1 & 0 & 1 & 1 & 0 & 1 & 0 & 0 \\ {} + & 1 & 0 & 1 & 1 & 1 & 1 & 0 & 0 \\ \hline \textcolor{red}{1} & 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 \end{array} $$
Cuidado: o \(\textcolor{red}{1}\) destacado neste exemplo não é o bit de
sinal. É o vai-um que saiu da conta, numa coluna extra à esquerda dos 8
bits — e que será descartado. O bit de sinal do resultado é o 0 logo à
direita dele.
Análise:
- Olhe o bit de sinal. Ele somou \(1 + 1\), gerando 0 e propagando um “vai-um” para fora (o \(\textcolor{red}{1}\) vermelho).
- Mas ele não recebeu nenhum “vai-um” da coluna anterior (veja o espaço vazio acima da segunda coluna).
- Saiu e não entrou? Overflow!
O resultado 01110000 começa com 0, indicando um número positivo (+112). Erro detectado.
Quando o overflow sai do papel #
Overflow não é curiosidade de sala de aula: está por trás de algumas das falhas mais caras já registradas em software.
Em 4 de junho de 1996, o foguete Ariane 5 explodiu 39 segundos após a decolagem, em seu voo inaugural. A causa: o sistema de referência inercial convertia a velocidade horizontal de um número de ponto flutuante de 64 bits para um inteiro com sinal de 16 bits. No Ariane 4, aquele valor sempre coubera. O Ariane 5 era mais rápido, o número não coube, e a conversão estourou.
O software tratou o erro parando o computador de bordo — e o computador reserva, que rodava exatamente o mesmo código, havia parado pelo mesmo motivo alguns milissegundos antes. Sem referência de atitude, o foguete girou, se partiu sob a pressão aerodinâmica e acionou a autodestruição. O prejuízo é estimado em US$ 370 milhões, causado por um trecho de código que nem precisava estar ativo naquele momento do voo.
Nem todo caso é trágico. Dois exemplos conhecidos do mesmo fenômeno:
- Pac-Man, fase 256. O contador de fases tinha 8 bits, ou seja, ia de 0 a 255. Ao chegar na fase 256, o cálculo que desenha a tela estourou o contador e metade do labirinto virou lixo gráfico. Ficou conhecida como kill screen: a fase é intransponível, e por décadas foi o teto absoluto do jogo.
- O contador do YouTube, em 2014. O número de visualizações era guardado num inteiro com sinal de 32 bits, cujo limite é \(2^{31} - 1 = 2.147.483.647\). Quando o vídeo de Gangnam Style passou dessa marca, o Google migrou o campo para 64 bits — e fez piada oficial sobre o assunto. Com 64 bits, o novo teto passa de 9 quintilhões.
Nos três casos o mecanismo é o mesmo que você acabou de ver nas contas de 8 bits acima. Muda a quantidade de bits e, com ela, o tamanho do estrago.
Dica Extra: Identificando Negativos em Hexadecimal #
Na prática, programadores raramente olham para sequências de zeros e uns. A representação mais comum em depuradores (debuggers) e editores de memória é o Hexadecimal.
Como saber se um número Hex é negativo (em complemento de 2) apenas batendo o olho?
A regra é universal, não importa se o número tem 8, 16, 32 ou 64 bits: basta
olhar para o primeiro dígito da esquerda. Se esse dígito representar um valor
binário que começa com 1 (ou seja, \(\ge 8\)), o número inteiro é negativo.
Os “Dígitos Negativos” em Hexadecimal são: 8, 9, A, B, C, D, E, F
Exemplos:
- 8 bits:
0xFF\(\to\) Começa com F. Negativo (-1). - 16 bits:
0x8000\(\to\) Começa com 8. Negativo (-32.768). - 32 bits:
0xC0000005\(\to\) Começa com C. Negativo. - Qualquer tamanho:
0x7FFF...\(\to\) Começa com 7. Positivo.
Se você estiver depurando um código e vir uma variável inteira (signed)
começando com F, saiba imediatamente que ela é negativa. Só isso: o
primeiro dígito conta o sinal, não o valor. 0xF0000000 também começa com F e
vale \(-268.435.456\), o que é bem diferente de \(-1\).
Existe, porém, um caso particular inconfundível: quando todos os dígitos são
F, como em 0xFFFFFFFF (32 bits), aí sim o valor é -1.
Pode parecer estranho que o “maior” número visualmente (tudo preenchido com F ou 1) represente o -1. Mas podemos provar isso aplicando a regra do “Inverte e Soma 1” que acabamos de aprender.
Se quisermos saber qual é o valor de 0xFFFFFFFF (32 bits), fazemos o caminho reverso:
- Converter para Binário: Uma sequência de trinta e dois 1s (
1111...1111). - Inverter os bits: Todos os 1s viram 0s (
0000...0000). - Somar 1: $$ 0 + 1 = 1 $$
Como o bit de sinal original era 1 (negativo) e a magnitude encontrada foi
1, o valor final é -1.
Portanto, não importa o tamanho da variável (8, 16, 32 ou 64 bits): se todos os
bits forem 1 (ou todos os hexadecimais forem F), o número sempre será -1
em Complemento de Dois.
Na Prática: Visualizando em Python #
Se você tentar verificar o que aprendemos aqui usando o console do Python, terá uma surpresa. O Python lida com inteiros de forma diferente de linguagens como C ou Java: ele tem precisão arbitrária. Isso significa que os números podem ter quantos bits forem necessários, sem um limite fixo (como 8 ou 32 bits).
Por causa disso, a função nativa bin() não mostra o Complemento de Dois por
padrão. Ela apenas coloca um sinal de menos na frente:
x = -5
print(bin(x)) # -0b101
# Isso não é Complemento de Dois! É apenas "5 negativo".Para ver como os bits realmente são armazenados (como 1111...1011), precisamos
forçar o Python a limitar o número a uma quantidade de bits, usando uma
Máscara de Bits (Bitmask) com o operador AND (&).
Se quisermos ver a representação em 8 bits, usamos a máscara 0xFF (que é 11111111):
x = -5
print(bin(x & 0xFF)) # 0b11111011
# Agora sim! Veja os bits ligados indicando o negativo.Se quisermos ver em 32 bits, usamos 0xFFFFFFFF:
x = -5
print(bin(x & 0xFFFFFFFF))
# 0b11111111111111111111111111111011Isso acontece porque o operador & trata o número como uma sequência de bits
pura, revelando a estrutura de Complemento de Dois que está “por baixo do capô”.
Tabela-Resumo #
| Conceito | Regra | Exemplo do artigo |
|---|---|---|
| Complemento à base \(b\) | \(b^d - N\), num sistema de \(d\) dígitos | \(10^3 - 734 = 266\) |
| Complemento de dois | \(2^d - N\), num sistema de \(d\) bits | \(2^4 - 5 = 1011_2\) |
| Atalho aritmético | inverta todos os bits e some 1 | 0101 \(\to\) 1010 \(\to\) 1011 |
| Atalho visual | copie da direita até o primeiro 1, inverta o resto |
011100 \(\to\) 100100 |
| Ler um negativo | subtraia \(2^d\) do valor sem sinal | \(251 - 256 = -5\) |
| Ler um negativo (2ª forma) | dê peso \(-2^{d-1}\) ao MSB | \(-128 + 64 + 32 + 16 + 8 + 2 + 1 = -5\) |
| Subtrair somando | \(A - B = A + \text{comp}_2(B)\), descartando o vai-um final | \(25 - 19 = 6\) |
| Faixa sem sinal | \(0\) a \(2^N - 1\) | 8 bits: 0 a 255 |
| Faixa com sinal | \(-2^{N-1}\) a \(2^{N-1} - 1\) | 8 bits: \(-128\) a \(+127\) |
| Por que a faixa é assimétrica | existe um zero só, então sobra uma combinação negativa | \(-128\) não tem oposto |
| Overflow (regra humana) | positivo + positivo dando negativo, ou o contrário | \(88 + 46 = -122\) |
| Overflow (regra técnica) | o vai-um que entra no bit de sinal difere do que sai | \(-76 + (-68) = +112\) |
| Negativo em hexadecimal | primeiro dígito \(\ge 8\) | 0xF0000000 é negativo |
| O caso do \(-1\) | todos os dígitos iguais a F |
0xFFFFFFFF vale \(-1\) |
| Em Python | inteiros têm precisão arbitrária; use máscara para ver os bits | bin(-5 & 0xFF) \(\to\) '0b11111011' |
Conclusão #
O sistema de Complemento de Dois é uma das soluções mais elegantes da ciência da computação. Graças a ele, processadores podem realizar somas e subtrações usando o mesmo circuito lógico (o somador), apenas invertendo bits quando necessário. Isso simplifica drasticamente o design dos chips e reduz custos.
Com isso, fechamos o ciclo dos números inteiros. Sabemos representá-los, convertê-los e operá-los, inclusive os negativos.
Mas o que acontece quando precisamos de precisão além do inteiro? Como o
computador lida com \(0.5\) ou \(3.14\)? E por que o operador & funciona
como mostrado acima?
Essas perguntas serão respondidas nos próximos artigos da série Do Zero ao Float. Fique ligado!