You are on page 1of 7
arq Engenharia de Computacao comp Professor Juliano COMPLEMENTO DE DOIS Inteiros Negativos em Microprocessadores TRES POSSIBILIDADES DE CALCULO Vou pular aqui as intuigSes iniciais a respeito de complemento de 2, como o odémetro de um carro ou trabalhar com excesso-n (bias de n). Consulte um bom livro de Digital se precisar. CIRCUITARIA © que é normalmente aprendido em Sistemas Digitais é para representar um niimero -x em complemento de 2, faga o binério do x, inverta todos os bits (isso & 0 complemento de 1) e some 1 Isso gera um circuito simples e répido, embora seja meio chato de fazer na mao. Exemplo: como representar -7 em complemento de 2 com 8 bits? 7 = 111; invertendo e somando 1, temos 11111000; + 00000001; = 11111001; =-7. Note que sempre que trabalhamas em complemento de 2 & necessério identifiar todos os bits do rimero ea largura da palavra deve ser indicada. iso se da porque, de outra forma, pode ser ambiguo saber se um niimero é negativo ou nao. 0 nimero 111111; 6 negativo? Se estiver em complemento de 2 com 6 bits, é, sendo nao é. 0000011: COMPLEMENTO PARA A POTENCIA DE 2 Uma forma simples e rapida de calcular no olho é subtrair o mimero da préxima poténcia de 2,¢ 0 bindrio do resultado equivale ao negativo em complemento de 2. Voltando ao nosso exemplo: trabalhamos com 8 bits, entio a poténcia é 2-256, Para calcular o bindrio de -7, fazemos 256-7=249, e o bindrio de 249=11111001,, que é 0 mesmo niimero que obtivemos anteriormente. Portanto -7 em complemento de 2 de 8 bits ¢ isomérfico a 249 bindtio. PESO NEGATIVO NO MSB Outra forma simples de olhar para um niimero em complemento de 2 é simplesmente inverter 0 sinal do peso do bit mais a esquerda, Veja a conversao do nximero -7: 11111001, = 1*(-128)=1"64+1"32+1"16+1*8+0"4+0"2+1*L = -128+64+32+16+8+1 = -7 Para fazer a conta no outro sentido, basta “descontar”: se 0 MSB vale -128, entdo -128+resto- entao o resto=+121-1111001,, completando os sete bits a direita. EXEMPLO Como representar o niimero -78 em complemento de 2 de 8 bits? + 78:.= 01001110;; invertemos e incrementamos para 10110001,+1 = 10110010:2a015 * Perceba: 10110010cqants = 1*(-2’)}+0*25+ 1425+ 1424+0*2+0422+ 142140429 = -12B+32+16+2 = -7B:s + E.ainda: a proxima poténcia ¢ 2°= 256; calculamos 256-78 = 178. Observe que 178,.= 10110010,, que sio os mesmos bits determinados antes. EXPLICAGAO A ideia é simples: como temos um mimero limitado de n bits num processador, sé podemos representar 2° nimeros; todos os mimeros que estouram estes limites sfo representados em médulo 2°! * Portanto um néimero a de n bits representa, na verdade, varios ntimeros b possiveis, caracterizando uma congruénciaz™ b= kaa Por exemplo, novamente em 8 bits, se a = 00110011, (esse bindrio dé 51 em decimal), kf] > -1 | -205 0 | 51 1 | 307 2 | 563 3 | 819 Ou seja, se estamos limitados a 8 bits, a sequéncia 00110011; pode indicar as grandezas -205, 51, 307,... * Claro que esta faltando bit para os ntimeros maiores: 819 - 11001100112, de 10 bits; no cabe em 8. Agora vamios fazer 0 mesmo para 0-7 em 8 bits, que é 11111001: k ] > -2 | -263 -1 | -7 0 | 249 506 2 | 762 Isso levanta a questo: se um registrador do hardware esta com os bits 11111001, qual é 0 nimero representado? E a resposta é: depende do que 0 programa fez e do que o programador quer dizer. A linguagem C! define explicitamente duas possibilidades para qualquer variével integral: signed e unsigned, ou seja, sinalizado e nao sinalizado. Com isso, 0 programador pode definir o que deseja com certo conjunto de bits. Portanto, se a variavel é unsigned, entao 11110001, representa 0 valor decimal 249; se a varidvel é signed, entdo 11110001; est em complemento de 2 ¢ portanto vale -7. Mas isso obrigatoriamente sé vale se nao houve nenhum estouro em uma operacdo anterior. 1 Também dé pra usar a notag3o mod 2°, mais familiar na Ciéncia da Computagio Se alguém quiser, tem uma algebra mais sofisticada embasando essas coisas; grupos ciclicos, parece. Eu acho lindo, mas td fora do meu aleance. 3. Isso € andlogo aos angulos no cfrculo: 45° = -315° = 405° porque o. = k360° + 6 4 Cénosso default em Microcontroladores e Sistemas Embarcados, embora C+ tenha uma fatia crescente CARRY E OVERFLOW Todos os processadores modernos posstiem indicadores (flags) de quando uma operacio aritmética excede a capacidade de bits dos seus registradores. A ideia é bem simples: quando ha estouro unsigned, isso é um carry; quando ha um estouro signed, € um overflow. Note que se a operagio € unsigned, o carry € a flag relevante e 0 overflow pode ser desprezado; de forma simtrica, sé atentamos ao overflow para operacées sinalizadas. a) Unsigned Digamos que precisamos somar 120+80 em 8 bits, tudo sem sinal. 0 resultado é 200, nao ha carry. Se somarmos 200+200, por outro lado, o niimero 400 néo cabe em 8 bits, ento hé carry (6 equivalente ao bit de vai-tm). 0 resultado final no registrador é 144 (pois o bit extra, o “nono bit”, vale 256; 144+256=400). ‘A subtracao é similar: 130-14-116, sem carry. Agora, 20-40=236, que equivale a -20 em complemento de 2. Se o resultado é negativo, ha carry (afinal, 6 um estouro de capacidade). b) Signed Novamente em 8 bits, se somarmos 34+(-53) nao ha overflow: -19 € representavel tranquilamente. Mas se somarmos 105+102, o resultado 207 ndo é representdvel, portanto ha sim overflow. Isso pode ser verificado quando os operandos tém o mesmo sinal (ambos positivos ou ambos negativos) mas o resultado da soma tem o sinal invertido ao deles. Os limites que devem ser obedecidos sao: -2** S resultado < 2° operacao em & bits exceder -128 ott +127, ha estouro. 1, Portanto, se qualquer c) Misturado! Nao é uma boa ideia misturar signed e unsigned na mesma expressio: eles possuem limites diferentes e nem sempre o compilador saber4 como lidar com eles. 0 resultado final pode depender da linguagem, do compilador e até do assembly do processador.? Em C, caso uma operacio ocorra entre um signed int e um unsigned int (32 bits nos PC's atuais) o signed int serd convertido em um unsigned. No cddigo abaixo: #include int main(void) { signed int s_in unsigned int u_int long long Lint; char testel, teste2; 1090; 1000111222; Lint = wint+s_int; teste1 = (s_intu_int); printf ("s11d\n%d\nsd\n", 1_int, teste1, teste2); return 0; } O resultado da compilacao* e execusao é > ./main 3000110222 0 1 Observe os trés resultados, em ordem: 5 Diretamente do Kemighan e Ritchie, “The C Programming Language” segdo A.6.5: “Unexpected results may still ‘occur when an unsigned expression is compared to signed expression of the same size.” Ou seja, solugdes para isso nao tem portabilidade em C90, embora as regras cubram a maior parte dos casos. 6 Usei o Compiler Explorer, com o clang version 7.0,0-3~ubumtu0.18.04.1 (tag/RELEASE_700ffinal) * Asoma na varidvel |_int foi feita corretamente. 0 que ocorreu foi que o mimero -1000 foi convertido de signed para unsigned (virou 4,294,966,296, que & 2°-1000). Em 32 bits isso significa usar médulo 2*, ou seja, (u_int-1000) e (u_int+4294966296) produzem 0 mesmo. resultado, 3000110222. * Acomparagio para teste! resultou falso! Apesar de que até para uma crianga seja Sbvio que ~1000<3000111222, nao foi essa a interpretacio, o que é contraintuitivo. A comparagio percebe que estamos misturando dois ints diferentes, promove o signed para unsigned, 0 que transforma 0 -1000 em 2°*-1000 = 4294966296. A comparacao que acorre agora ¢ testel = (4294966296 < 3000111222), 0 que é falso. * Consistente com isso, teste? resulta em verdadeiro pelo exato mesmo motivo. Ao trabalhar com microcontroladores de baixo custo, estas consideracdes sobre ntimeros hegativos eventualmente acabam tendo de ocorrer. EXEMPLOS DETALHADOS Dada a conta: 11001110.+11000111; com resultado num registrador de 8 bits, quais sao os valores decimais envolvidos? Houve estouro da capacidade de 8 bits ou nao? Qual o resultado final? Chamemos o valor 11001110; de x e 0 valor 11000111, de y. Temos quatro possibilidades interpretativas, usando notaco da linguagem C: tipo x y signed char -50 “57 unsigned char 206 199 0 resultado final da soma sera 10010101, independentemente de haver sinal ou nao, pois a ULA faz as somas sem saber se o mimero é sinalizado ou nao, pois o circuito e a operacio matematica sdo os mesmos. Este valor 10010101; tanto pode ser 149, caso o resultado seja interpretado como unsigned, como pode ser -107, se for signed. Perceba que o resultado final estd correto, exceto quando tanto x quanto y s4o unsigned: -50-57=-107 -50+199-149 206-57=149 206+199=405, ¢ 405=256+149 (0 256 é carry) Isso ocorre porque as contas de 8 bits sio feitas em médulo 256 Observe que, nesta conta 11001110.+11000111:, ha carry e nao hé overflow. O overflow s6 & relevante para o caso 1 e o carry é feito para o caso 4. Os casos 2 € 3 sio “perigosos” para o programador, misturando representacdes sinalizada e no sinalizada; o resultado dé certo por causa do médulo 256 nas operagdes. Mais um exemplo: veja as 4 interpretacdes de uma soma de 8 bits entre os ntimeros 130 (cujo complemento é-126) e 101 (que vale 101 tanito em slgned quanto em unsigned): unsigned 130 signed -126 signed -126 unsigned 130 unsigned +101 signed +101 unsigned +101 signed + 101 231 ~25 ~25 231 Como se pode imaginar, 256-25-234. Nao hé carry, nao hé overflow. ASSEMBLY E COMPLEMENTO DE 2 Por conta das peculiaridades com a sinalizagdo, algumas operagdes precisam ser adaptadas de acordo. a) Negago do maior negativo © circuito para negagao sé inverte os bits e soma 1; portanto, 01110111+1 = 01111000, 120) = (100010002) = 120, em 8 bits. Mas o que ocorre ao calcular -(-128) em 8 bits? -(-128) = -(10000000.) = 01111111+1 = 10000000; = -1281!! Isso faz sentido, pois em 8 bits os ntimeros esto entre -128 e +127, inclusive. Portanto a negacdo pode produzir um resultado incorreto se 0 operando for o menor negativo possivel. b) ComparagSes © programa em C anterior ja deve ter colocado 0 terror algébrico adequado no leitor. Entao nao surpreende que necessitemos de comparagdes diferenciadas para signed e unsigned. Em 8 bits, como de habito, se quisermos comparar 11111111; com 000000012, a questo & estamos comparando sinalizados -1 com +1 ou estamos comparando nao-sinalizados +127 com +1? Se formos comparar 11111111,<00000001., podemos ter (-1<+1=true) e (+127<+1-false). No assembly MIPS, temos diferenciacao; ha a instrugdo slt (set if less than) que compara com sinal (seria -1 e +1 no exemplo) ea instrugao sltu que compara sem sinal (+127 com +1). Nem todas os processadores oferecem essas facilidades, portanto sempre devemos observar a convengao ao iniciar com um processador novo. c) MIPS ASM: add versus addu & Constantes Nao custa avisar logo de cara: 0 nome dessas instrucdes foi mal escolhido e engana. 0 addu significa add unsigned, mas nao tem a ver com sinal - 0 MIPS por default acha que as coisas sfo sinalizadas e ponto. A ideia 6: se houver estouro sinalizado (overflow) num add, o MIPS considera isso um erro e interrompe o programa, Mas se houver um overflow num addu, ele simplesmente ignora o ocorrido sem sinalizar’. Como a linguagem C nao prové nativamente ao programador a capacidade de detextar um estouro, o € sempre compilaré uma soma para instrugdes addu no MIPS. Jé as constantes no assembly sao usualmente interpretadas como sendo sinalizadas de 16 bits. Se a constante nao é representavel como tal (isto é, normalmente num PC, um signed short int), ela é considerada pelo MARS como uma constante de 32 bits. Como os registradores possuem 32 bits ao invés de 16, é necessério completar os 16 bits MSB para a ULA, seja com zeros (cf. instrugdes ori ou andi) ou com extensdo de sinal. Veja um exemplo de instrucdo: addi $t1,$t2,-123 ‘A ULA nao sabe somar os 32 bits de $t2 com os 16 bits de -123, portanto precisamos expressar -123 em 32 bits. Em 16 bits hexa, teriamos FF85:.. Para estender o sinal, copiamos o bit mais significativo para a word superior, obtendo FFFF FF85.s. Portanto nao é sé “preencher com zeros”, precisamos mianter o sinal original inalterado mesmo com estes bits a mais, Se colocassemos zeros, teriamos 0000 FF851.,0 que iria significar +65413;., em vez de -123. ) Deslocamentos de bit Sto as operacdes de shift e, dependendo do processador, rotate’. Os shifts left e right equivalem aproximadamente aos operadores << e >> da linguagem C. Para nimeros unsigned, eles equivalem a multiplicar um mimero por 2# ou dividir por 2°, 7 Allogica é: se vocé estiver com um unsigned inte somar, digamos, 2000000000+2000000000, tem que dar 4000000000 (que ¢ identico ao signed int -294967296); portanto no ha erro. 8 As de rotagao sao identicas as de deslocamento, exceto pelo fato de ao invés de descartarem o bit da ponta, 0 reintroduzem do outro lada. Rotacionar 10010000, para a esquerda resulta em 001000001, por exemplo. sendo n o ntimero de bits; os bits faltantes sero preenchidos com zero. Sempre que houver uma poténcia de 2 numa multiplicagao, um bom compilador usar as instrucdes nativas de shift do processador. Infelizmente, com niimeros signed, os resultados nao sao dbvios: 0 que obtemos se calcularmos -83>>2? Depende do processador. Usualmente, no assembly, haveré uma instrugao de deslocamento a direita que preenche com zero (chamado deslocamento “Idgico”) e outra que preenche com o bit de sinal (o bit MSB, deslocamento “aritmético”),e @ linguagem C nao obriga 0 compilador a escolher um destes para os signed...” Portanto, em 8 bits como sempre (facilita): -83>>2-10101101.+>2; no caso légico, dé 00101011.-53, ¢ 0 sinal mudou! Ja no caso aritmético, dé 11101011,--21, que apesar de nao mudar o sinal, arredonda a divisio para cima! No caso positivo ele faz 83>>2=20, que é exatamente o mesmo que 83/4. ALTERNATIVAS Operagdes feitas em complemento de dois utilizam os mesmos circuites do que as operagdes de ntimeros nao-sinalizados (pois em médulo o resultado é o mesmo), o que é bastante conveniente. Estes circuitos sao simples e podem ser otimizados para serem mais répidos. ‘Uma outra op¢ao que parece simples ¢ utilizar a representagao sinal-magnitude: o bit mais significativo (MSB) indica o sinal + ou - © o restante dos menos significativos representam a magnitude ou o niimero em si. Por exemplo, o ntimero de 8 bits 10010110 significa -22;5 (0 1 esquerda é 0 sinal negativo). Dois inconvenientes: temos +0 e -0, 0 que enche 0 saco, e a circuitaria pra fazer as contas precisa levar em conta o sinal o tempo inteiro (comparar os sinais dos operandos pra descobrir se tem que somar ou subtrair, por exemplo). Portanto s6 computadores histéricos adotam isso. Dependendo do masoquismo de cada um, podemos usar complemento de 1” se for 0 caso; basta inverter todos os bits para achar o negativo. Novamente temos -0 (em 8 bits, 11111111), novamente as operacées precisam de hardware adicional; precisamos ajustar as somas somando 1 se houver carry, por exemplo. Tente somar 13 + (-4), da 00001101, + 11111011, que resulta em (2)00001000. = 8, que deve ser incrementado para 00001001; = 9. A parte destas opges sensatas porém obsoletas, sempre podemos tentar usar base terndria balanceada ou base menos dez (negadecimal), ou coisas ainda mais exéticas™. EXERCICIOS 1. Como é representado o mimero -84 em complemento de 2 com 8 bits? 2. Qual éa diferenca entre Carry e Overflow? 3. Suponha que o processador que estamos trabalhando é de 6 bits. Vamos somar os niimeros bindrios 101110, + 100100;. a) Qual ¢ 0 resultado? b) Qual é o significado deste ntimero? Ou seja, qual 0 valor decimal dele? c) Houve carry? d) Houve overflow? 4, Quais sao as operacées aritméticas que so idénticas tanto para signed quanto para unsigned? (Qu seja, produzem o mesmo resultado independente de estarem usando complemento de 2 ow rximero bindrio). Quais sao as operacSes que sao diferentes? © que 0 €90 (C padrao ANSI 1990) vai fazer com o cédigo abaixo? Hé situagdes que podem gerar um problema? Se sim, quais? 9 Abiblia C90 nos diz: “Right shifting a signed quantity will and with 0-bits (“logical shift") on others.” (Kernighan p.97) 10 “Quanto falta para 1, em cada bit"; seo bit é zero, falta 1 para somar 1; seo bit um, fata 0 para somar 1 11 Usando 2i como base, por exemplo, sendo i= V-1, edigitos de 0 a3, 0 “quatet-imagintio”. Consulte o Knuth Vol 2, Seminumerical Algorithms. swith bit signs (arithmetic shift”) on some machines unsigned char x,z; signed char y,w; x= f10); y = £20); aif(ysx) zewry} else waz Se precisar entender conversdes de tipo em C, consulte o Kernighan®. 0 tipo char tem sempre 8 bits (mas em microcontroladores e C99 provavelmente seria melhor usar uint8_t e sint®_t, se for esse caso). 6. Va no compiler explorer (httpsv/godboltorg/) ¢ compile o programa abaixo em C selecionando como compilador-target 0 "MIPS gec 5.4”. main() { int ni = 2012345, 2; n2 = niénd; 3 Responda: a) Por que o compilador gerou addu ao invés de add? (Na linha 11 do assembly; é 0 ni-n1) b) Como podemos detectar overflow ou carry usando a linguagem C90 padrao? ©) Qual o sinal de n2 ao final do programa? ¢) [Extra] Insira -03 (hifen - letra 6 maitiscula - 3) para ligar todas as otimizag6es. Por que 0 cédigo ficou daquele jeito? 7. Considerando um néimero em complemento de 2 com 6 bits, qual seré o resultado final das somas consecutivas 30+30+30+30+30? 8. Considerando um ntimero binario (unsigned) de 6 bits, qual sera o resultado final das somas consectitivas 50+50+50+50? 9, Na representacéo sinal-magnitude, temos dois zeros: +0 e -0. Nao esquecendo disso, construa com portas Iégicas a operacdo da ULA que compara dois niimeros e determina se @ primeiro é maior ou igual ao segundo.Prove que estender o sinal é equivalente a copiar 0 bit MsB para todos os bits & esquerda. Se quiser facilitar, assuma um niimero fixo de bits, como 8, por exemplo. 10. No MIPS, 0 comando addiu $50,5zero,0x8000 ¢ interpretado como uma pseudoinstrucao. Por qué? 12 Kemighan & Ritchie “The C Programming Language Second Edition” ou, no Brasil, “CA Linguagem de Programacio Padraio ANSI”. Nos apéndices encontra-se uma seco com as regras de conversio.

You might also like