Como representar números inteiros negativos em binário
Na nossa numeração decimal, estamos habituados a ver o sinal “-” quando temos números negativos. Isto é, para além de termos os 10 algarismos, de 0 a 9, ainda podemos usar o símbolo “-” para indicar que o número é negativo.
Nos computadores só temos mesmo zeros e uns e não há como representar directamente o símbolo “-”. Como poderemos, então, tratar números negativos?
Imagine-se que queremos construir uma calculadora em binário. Para já, temos que ter em conta o número de bits que temos disponíveis na nossa “calculadora”. Comecemos por considerar que temos 8 bits. Neste caso, teremos 28 = 256 combinações, as quais podemos numerar de 0000 0000 a 1111 1111 em binário, ou seja, de 0 a 255 em decimal. E, já agora, de 00h a FFh (hexadecimal) – só para relembrar.
A solução é dedicarmos um bit para identificar se um número é positivo ou negativo e chama-se a esse bit, bit de sinal. Este é situado no MSB (Most Significant Bit) do nosso número binário. Ao usarmos um bit de sinal, restam-nos 7 bits para representar valores, agora positivos e negativos. Para cada parte, ficamos com 27 = 128 combinações.
Existem várias formas de codificar números positivos e números negativos. Neste tópico vou apenas referir a mais usada: O complemento para dois.
No complemento para 2, quando o número é positivo, o bit de sinal (no MSB) fica a 0. Assim, temos os números positivos a variar entre 0000 0000 a 0111 1111 em binário, ou seja, de 0 a 127. E aqui estão as 128 combinações “não negativas”, pois inclui-se o zero. Para representar um número negativo, o bit de sinal fica a 1. Neste caso, podemos ver os números negativos a variar entre 1000 0000 e 1111 1111. Qual a variação em decimal? Irei adiantar que será de -128 a -1 (tendo 128 combinações negativas).
Como chegamos ao valor negativo? Usando a regra de complemento para 2, fazemos:
- Invertemos os bits todos (a este passo também se chama complemento para 1)
- Somamos +1
Vejamos:
- 1000 0000 → (invertendo os bits) 0111 1111 → somando +1 = 1000 0000 (2) = 128 (10)
- 1111 1111 → (invertendo os bits) 0000 0000 → somando +1 = 0000 0001 (2) = 1 (10)
Como temos o bit de sinal a 1, os resultados serão -128 e -1, respectivamente.
Vejamos um exemplo. Converta-se o seguinte número binário para decimal com bit de sinal:
1101 0011 (2)
Para facilitar, usemos uma tabela para aplicar o complemento para 2:
1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 |
0 | 0 | 1 | 0 | 1 | 1 | 0 | 0 |
+ | 1 | ||||||
0 | 0 | 1 | 0 | 1 | 1 | 0 | 1 |
Aplicou-se então uma inversão de bits e somou-se mais 1. Ficámos com o valor 0010 1101 que em decimal é:
1 × 25 + 1 × 23 + 1 × 22 + 1 × 20 = 32 + 8 + 4 + 1 = 45
Ou seja: 1101 0011 (2) = -45 (10)
NOTA: Só usamos a regra do complemento para 2 quando o bit de sinal é 1!
Vejamos a seguinte tabela e compare-se os valores em decimal com e sem sinal:
Número binário | Decimal com sinal (complemento para 2) | Decimal sem sinal |
---|---|---|
0000 0000 | 0 | 0 |
0000 0001 | 1 | 1 |
... | ... | ... |
0111 1110 | 126 | 126 |
0111 1111 | 127 | 127 |
1000 0000 | -128 | 128 |
1000 0001 | -127 | 129 |
1000 0010 | -126 | 130 |
... | ... | ... |
1111 1100 | -4 | 252 |
1111 1101 | -3 | 253 |
1111 1110 | -2 | 254 |
1111 1111 | -1 | 255 |
Uma grande vantagem do complemento para 2 é a sua aplicabilidade directa em operações de adição e subtracção, entre outras, como iremos ver.
Por exemplo, se tivermos em binário 0000 0000, ou seja zero em decimal, e subtraírmos 1, sabemos que depois dos “empresta 1”1, o nosso número binário ficará 1111 1111, ou seja -1. Ao contrário, ao somarmos 1 a 1111 1111, depois de todos os”e lá vai 1″1, ficamos com 0000 0000, ou seja 0.
Vejamos a seguinte operação de adição: 65 + (-45) em binário.
Teremos:
0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | |
+ | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 |
1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
Temos um “bit” extra, que é o carrier (bit de transporte), a 1 e que será ignorado. O carrier é um sinalizador (flag) do processador e está presente na tabela apenas para lembrar que, no fim de uma operação, se ainda faltar um “e lá vai 1”, este fica activo, como acontece neste caso. Assim, temos o número binário 0001 0100 que é 20 em decimal. Repare-se que a operação é directa quando aplicamos o complemento para 2, o que é uma enorme vantagem! Observe-se, também, que o bit de sinal do primeiro membro é 0, pois é um número positivo, e o do segundo membro é 1, pois é um número negativo. O bit de sinal do resultado é 0 pois o resultado é positivo!
Concluindo: 65 + (-45) = 20 em decimal.
Vejamos ainda outro exemplo:
-127 + 15 (o valor -127 está na tabela mais acima e vale 1000 0001)
1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | |
+ | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 |
1 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
Mais uma vez, apenas foi colocado o carrier para lembrar que este bit existe no processador. Aqui é totalmente desprezável, serve apenas como mera indicação. Repare mais uma vez nos bits de sinal. No resultado temos este a 1 o que indica que o resultado é negativo. Aplique-se então o complemento para 2 para determinar o resultado.
1001 0000 → (invertendo os bits) 0110 1111 → +1 = 0111 0000 (2) = 112 (10)
Ou seja: -127 + 15 = -112 em decimal.
Noções de overflow e underflow:
Imagine-se agora que queremos fazer a soma: 96 + 48:
0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | |
+ | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
Repare-se que temos dois números positivos e o resultado surge como negativo. Se estivéssemos a operar sem sinal, o resultado até estaria certo pois 96 + 48 = 144, mas estamos a trabalhar com sinal! Logo, alguma coisa está errada! De facto, se fossemos aplicar a regra do complemento para 2 verificávamos que:
1001 0000 → (invertendo os bits) 0110 0000 → +1 = 0110 0001(2) = 97(10)
Ou seja, seria dizer que 96 + 48 = -97! O que está totalmente errado!
Lembre-se que o limite positivo, neste caso, é 127 e a soma ultrapassa este valor! 144 é maior que 127! Estamos, então, perante um caso de OVERFLOW. Quer isto dizer que, ultrapassámos o limite positivo!
Este caso também aconteceria se estivéssemos a implementar um contador binário de passo 1 (vai somando 1 em 1). Quando este chegasse a 0111 1111 em binário (127 em decimal) passaria para 1000 0000 em binário (-128 em decimal) e diríamos que ocorreu também um overflow.
Considere-se agora que queremos efectuar a seguinte operação: -112 + (-32)
Aproveitamos este exemplo para ver como obtemos a representação de -32 usando o processo inverso do complemento para 2:
32 (10) = 0010 0000 (2) → -1 = 0001 1111 → (invertendo os bits) 1110 0000
1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | |
+ | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
Repare-se que, o facto de termos ambos os membros com sinal negativo e o resultado da soma ser positivo, indica logo que algo está errado! De facto -112 – 32 seria -144, mas o que sai fora do limite negativo de -128. E se pegarmos no resultado, concluímos que -112 + (-32) ≠ 112! Existe por isso um UNDERFLOW! Ultrapassámos o limite negativo!
O mesmo acontece se estivéssemos a implementar um contador binário de passo -1 (vai decrescendo de 1 em 1). Quando chegássemos a 1000 0000 em binário (-128 em decimal) passaria para 0111 1111 em binário (127 em decimal) e diríamos também que ocorreu um underflow.
Mas como podemos testar estes casos?
Existem condições básicas para verificar se ultrapassámos limites positivos ou negativos:
- Se a soma de dois membros positivos (bits de sinal a 0) der um resultado negativo (bit de sinal a 1) estamos perante um overflow!
- Se a soma de dois membros negativos (bits de sinal a 1) der um resultado positivo (bit de sinal a 0) estamos perante um underflow!
Já na subtracção também é possível verificar estes erros, mas com regras diferentes. Vejamos um exemplo equivalente ao do overflow:
96 + 48 = 96 – (-48)
Mais uma vez, determine-se (-48) pelo processo inverso do complemento para 2:
48 (10) = 0011 0000 (2) → -1 = 0010 1111 → (invertendo os bits) 1101 0000
Vejamos os cálculos:
0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | |
- | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 0 |
1 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
Repare-se que voltamos a ter: 96 – (-48) ≠ -97
Daqui surge a regra:
- Se a subtracção de um membro positivo (bit de sinal a 0) com um número negativo (bit de sinal a 1) – e que equivale a uma soma de membros positivos – der um resultado negativo (bit de sinal a 1) então estamos perante um overflow!
No caso equivale a um underflow:
-112 + (-32) = -112 – 32
1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | |
- | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
Voltamos a ter o resultado 112 positivo! O que está errado!
Daqui surge a regra:
- Se a subtracção de um membro negativo (bit de sinal a 1) com um membro positivo (bit de sinal a 0) – e que equivale a somar um número negativo com outro número negativo – der um resultado positivo (bit de sinal a 0) então estamos perante um underflow!
Já agora, quando trabalhamos com código do processador (código máquina ou assembly), podemos verificar também o caso genérico de overflow através de um bit no registo de sinalizadores (flags), implementado pelos processadores. Oportunamente, iremos ver exemplos disso.
Este artigo foi escrito com a antiga grafia.
Notas:
- Leia o artigo “Operações aritméticas em binário” para mais informações sobre a adição e subtracção em números binários.
Oi tudo bem? Você poderia me dizer qual conversão abaixo é correta?
0xA0 / para decimal: -96 / binário: 10100000/ complemento de dois de -96 em decimal: 96
Ou
0xA0 / para decimal: 160/ binário: 10100000/ complemento de dois de 160 em decimal: 96
0x00 / binario 0000 0000/ decimal 0 / complemento de dois 1 0000 0000 em decimal 255
Olá!
Antes demais é mais fácil passar para binário e repare que o bit de sinal está a 1, o que indica que o número é negativo. Complementando o número binário fica 01011111, somando 1 fica 01100000 o que dá 64 + 32 = 96. Logo o resultado é -96! 🙂